diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualnetworkgateway.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualnetworkgateway.py new file mode 100644 index 00000000000..7e82228ae78 --- /dev/null +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualnetworkgateway.py @@ -0,0 +1,384 @@ +#!/usr/bin/python +# 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': 'certified'} + +DOCUMENTATION = ''' +--- +module: azure_rm_virtualnetworkgateway + +version_added: "2.8" + +short_description: Manage Azure virtual network gateways. + +description: + - Create, update or delete a virtual network gateway(VPN Gateway). When creating a VPN Gateway you must provide the name of an + existing virtual network. + +options: + resource_group: + description: + - Name of a resource group where VPN Gateway exists or will be created. + required: true + name: + description: + - Name of VPN Gateway. + required: true + state: + description: + - Assert the state of the VPN Gateway. Use 'present' to create or update VPN gateway and + 'absent' to delete VPN gateway. + default: present + choices: + - absent + - present + required: false + location: + description: + - Valid azure location. Defaults to location of the resource group. + required: false + virtual_network: + description: + - An existing virtual network with which the VPN Gateway will be associated. Required + when creating a VPN Gateway. + - It can be the virtual network's name. + - Make sure your virtual network is in the same resource group as VPN gateway when you give only the name. + - It can be the virtual network's resource id. + - It can be a dict which contains C(name) and C(resource_group) of the virtual network. + aliases: + - virtual_network_name + required: true + ip_configurations: + description: + - List of ip configurations + suboptions: + name: + description: + - Name of the ip configuration. + required: true + private_ip_allocation_method: + description: + - private ip allocation method. + choices: + - dynamic + - static + default: dynamic + public_ip_address_name: + description: + - Name of the public ip address. None for disable ip address. + subnet: + description: + - ID of the gateway subnet for VPN. + default: GatewaySubnet + gateway_type: + description: + - The type of this virtual network gateway + default: vpn + choices: + - vpn + - express_route + vpn_type: + description: + - The type of this virtual network gateway + default: route_based + choices: + - route_based + - policy_based + enable_bgp: + description: + - Whether BGP is enabled for this virtual network gateway or not + default: false + sku: + description: + - The reference of the VirtualNetworkGatewaySku resource which represents the SKU selected for Virtual network gateway. + default: VpnGw1 + choices: + - VpnGw1 + - VpnGw2 + - VpnGw3 + bgp_settings: + description: + - Virtual network gateway's BGP speaker settings. + suboptions: + asn: + description: + - The BGP speaker's ASN. + required: True + +extends_documentation_fragment: + - azure + - azure_tags + +author: + - "Madhura Naniwadekar (@Madhura-CSI)" +''' + +EXAMPLES = ''' + - name: Create virtual network gateway without bgp settings + azure_rm_virtualnetworkgateway: + resource_group: myResourceGroup + name: myVirtualNetworkGateway + ip_configurations: + - name: testipconfig + private_ip_allocation_method: Dynamic + public_ip_address_name: testipaddr + virtual_network: myVirtualNetwork + tags: + common: "xyz" + + - name: Create virtual network gateway with bgp + azure_rm_virtualnetworkgateway: + resource_group: myResourceGroup + name: myVirtualNetworkGateway + sku: vpn_gw1 + ip_configurations: + - name: testipconfig + private_ip_allocation_method: Dynamic + public_ip_address_name: testipaddr + enable_bgp: yes + virtual_network: myVirtualNetwork + bgp_settings: + asn: 65515 + bgp_peering_address: "169.254.54.209" + tags: + common: "xyz" + + - name: Delete instance of virtual network gateway + azure_rm_virtualnetworkgateway: + resource_group: myResourceGroup + name: myVirtualNetworkGateway + state: absent +''' + +RETURN = ''' +id: + description: + - Virtual Network Gateway resource ID + returned: always + type: str + sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworkGateways/myV + irtualNetworkGateway" +''' + +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, CIDR_PATTERN +from ansible.module_utils.common.dict_transformations import _snake_to_camel + + +AZURE_VPN_GATEWAY_OBJECT_CLASS = 'VirtualNetworkGateway' + + +ip_configuration_spec = dict( + name=dict(type='str', required=True), + private_ip_allocation_method=dict(type='str', choices=['Dynamic', 'Static'], default='Dynamic'), + subnet=dict(type='str'), + public_ip_address_name=dict(type='str'), +) + + +sku_spec = dict( + name=dict(type='str', default='VpnGw1'), + tier=dict(type='str', default='VpnGw1') +) + + +bgp_spec = dict( + asn=dict(type='int', required=True), +) + + +def vgw_to_dict(vgw): + results = dict( + id=vgw.id, + name=vgw.name, + location=vgw.location, + gateway_type=vgw.gateway_type, + vpn_type=vgw.vpn_type, + enable_bgp=vgw.enable_bgp, + tags=vgw.tags, + provisioning_state=vgw.provisioning_state, + sku=dict( + name=vgw.sku.name, + tier=vgw.sku.tier + ), + bgp_settings=dict( + asn=vgw.bgp_settings.asn, + bgp_peering_address=vgw.bgp_settings.bgp_peering_address, + peer_weight=vgw.bgp_settings.peer_weight + ) if vgw.bgp_settings else None, + etag=vgw.etag + ) + return results + + +class AzureRMVirtualNetworkGateway(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'), + ip_configurations=dict(type='list', default=None, elements='dict', options=ip_configuration_spec), + gateway_type=dict(type='str', default='vpn', choices=['vpn', 'express_route']), + vpn_type=dict(type='str', default='route_based', choices=['route_based', 'policy_based']), + enable_bgp=dict(type='bool', default=False), + sku=dict(default='VpnGw1', choices=['VpnGw1', 'VpnGw2', 'VpnGw3']), + bgp_settings=dict(type='dict', options=bgp_spec), + virtual_network=dict(type='raw', aliases=['virtual_network_name']) + ) + + self.resource_group = None + self.name = None + self.state = None + self.location = None + self.ip_configurations = None + self.gateway_type = None + self.vpn_type = None + self.enable_bgp = None + self.sku = None + self.bgp_settings = None + + self.results = dict( + changed=False, + state=dict() + ) + + super(AzureRMVirtualNetworkGateway, self).__init__(derived_arg_spec=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]) + + changed = False + results = dict() + vgw = None + + resource_group = self.get_resource_group(self.resource_group) + if not self.location: + self.location = resource_group.location + + self.virtual_network = self.parse_resource_to_dict(self.virtual_network) + resource_group = self.get_resource_group(self.resource_group) + + try: + vgw = self.network_client.virtual_network_gateways.get(self.resource_group, self.name) + if self.state == 'absent': + self.log("CHANGED: vnet exists but requested state is 'absent'") + changed = True + except CloudError: + if self.state == 'present': + self.log("CHANGED: VPN Gateway {0} does not exist but requested state is 'present'".format(self.name)) + changed = True + + if vgw: + results = vgw_to_dict(vgw) + if self.state == 'present': + update_tags, results['tags'] = self.update_tags(results['tags']) + if update_tags: + changed = True + sku = dict(name=self.sku, tier=self.sku) + if sku != results['sku']: + changed = True + if self.enable_bgp != results['enable_bgp']: + changed = True + if self.bgp_settings and self.bgp_settings['asn'] != results['bgp_settings']['asn']: + changed = True + + self.results['changed'] = changed + self.results['id'] = results.get('id') + + if self.check_mode: + return self.results + if changed: + if self.state == 'present': + if not self.sku: + self.fail('Parameter error: sku is required when creating a vpn gateway') + if not self.ip_configurations: + self.fail('Parameter error: ip_configurations required when creating a vpn gateway') + subnet = self.network_models.SubResource( + id='/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/virtualNetworks/{2}/subnets/GatewaySubnet'.format( + self.virtual_network['subscription_id'], + self.virtual_network['resource_group'], + self.virtual_network['name'])) + + public_ip_address = self.network_models.SubResource( + id='/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/publicIPAddresses/{2}'.format( + self.virtual_network['subscription_id'], + self.virtual_network['resource_group'], + self.ip_configurations[0]['public_ip_address_name'])) + + vgw_ip_configurations = [ + self.network_models.VirtualNetworkGatewayIPConfiguration( + private_ip_allocation_method=ip_config.get('private_ip_allocation_method'), + subnet=subnet, + public_ip_address=public_ip_address, + name='default' + ) for ip_config in self.ip_configurations + ] + + vgw_sku = self.network_models.VirtualNetworkGatewaySku( + name=self.sku, + tier=self.sku + ) + + vgw_bgp_settings = self.network_models.BgpSettings( + asn=self.bgp_settings.get('asn'), + ) if self.bgp_settings else None + vgw = self.network_models.VirtualNetworkGateway( + location=self.location, + ip_configurations=vgw_ip_configurations, + gateway_type=_snake_to_camel(self.gateway_type, True), + vpn_type=_snake_to_camel(self.vpn_type, True), + enable_bgp=self.enable_bgp, + sku=vgw_sku, + bgp_settings=vgw_bgp_settings + ) + if self.tags: + vgw.tags = self.tags + results = self.create_or_update_vgw(vgw) + + else: + results = self.delete_vgw() + + if self.state == 'present': + self.results['id'] = results.get('id') + return self.results + + def create_or_update_vgw(self, vgw): + try: + poller = self.network_client.virtual_network_gateways.create_or_update(self.resource_group, self.name, vgw) + new_vgw = self.get_poller_result(poller) + return vgw_to_dict(new_vgw) + except Exception as exc: + self.fail("Error creating or updating virtual network gateway {0} - {1}".format(self.name, str(exc))) + + def delete_vgw(self): + try: + poller = self.network_client.virtual_network_gateways.delete(self.resource_group, self.name) + self.get_poller_result(poller) + except Exception as exc: + self.fail("Error deleting virtual network gateway {0} - {1}".format(self.name, str(exc))) + return True + + +def main(): + AzureRMVirtualNetworkGateway() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/azure_rm_virtualnetworkgateway/aliases b/test/integration/targets/azure_rm_virtualnetworkgateway/aliases new file mode 100644 index 00000000000..aa77c071a84 --- /dev/null +++ b/test/integration/targets/azure_rm_virtualnetworkgateway/aliases @@ -0,0 +1,3 @@ +cloud/azure +shippable/azure/group2 +destructive diff --git a/test/integration/targets/azure_rm_virtualnetworkgateway/meta/main.yml b/test/integration/targets/azure_rm_virtualnetworkgateway/meta/main.yml new file mode 100644 index 00000000000..95e1952f989 --- /dev/null +++ b/test/integration/targets/azure_rm_virtualnetworkgateway/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_azure diff --git a/test/integration/targets/azure_rm_virtualnetworkgateway/tasks/main.yml b/test/integration/targets/azure_rm_virtualnetworkgateway/tasks/main.yml new file mode 100644 index 00000000000..46f345e28f4 --- /dev/null +++ b/test/integration/targets/azure_rm_virtualnetworkgateway/tasks/main.yml @@ -0,0 +1,88 @@ +- name: Prepare random number + set_fact: + vnetname: "vnet{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" + vngname: "vng{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" + +- name: Create virtual network + azure_rm_virtualnetwork: + resource_group: "{{ resource_group }}" + name: "{{ vnetname }}" + address_prefixes: "10.0.0.0/16" + +- name: Add subnet + azure_rm_subnet: + resource_group: "{{ resource_group }}" + name: GatewaySubnet + address_prefix: "10.0.2.0/24" + virtual_network: "{{ vnetname }}" + +- name: Create public IP address + azure_rm_publicipaddress: + resource_group: "{{ resource_group }}" + allocation_method: Dynamic + name: testPublicIP + +- name: Create virtual network gateway without bgp settings + azure_rm_virtualnetworkgateway: + resource_group: "{{ resource_group }}" + name: "{{ vngname }}" + ip_configurations: + - name: testipconfig + private_ip_allocation_method: Dynamic + public_ip_address_name: testPublicIP + virtual_network: "{{ vnetname }}" + tags: + common: "xyz" + register: output + +- assert: + that: output.changed + +- name: Create virtual network gateway without bgp settings - idempotent + azure_rm_virtualnetworkgateway: + resource_group: "{{ resource_group }}" + name: "{{ vngname }}" + ip_configurations: + - name: testipconfig + private_ip_allocation_method: Dynamic + public_ip_address_name: testPublicIP + virtual_network: "{{ vnetname }}" + tags: + common: "xyz" + register: output + +- assert: + that: not output.changed + +- name: Update virtual network gateway + azure_rm_virtualnetworkgateway: + resource_group: "{{ resource_group }}" + name: "{{ vngname }}" + ip_configurations: + - name: testipconfig + private_ip_allocation_method: Dynamic + public_ip_address_name: testPublicIP + virtual_network: "{{ vnetname }}" + tags: + common: "mno" + register: output +- assert: + that: output.changed + +- name: Delete virtual network gateway + azure_rm_virtualnetworkgateway: + resource_group: "{{ resource_group }}" + name: "{{ vngname }}" + state: absent + register: output +- assert: + that: output.changed + +- name: Delete virtual network gateway - idempotent + azure_rm_virtualnetworkgateway: + resource_group: "{{ resource_group }}" + name: "{{ vngname }}" + state: absent + register: output +- assert: + that: not output.changed