1#!/usr/bin/python
2#
3# Copyright (c) 2018 Yunge Zhu <yungez@microsoft.com>
4#
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['preview'],
12                    'supported_by': 'community'}
13
14DOCUMENTATION = '''
15---
16module: azure_rm_virtualnetworkpeering
17version_added: "2.8"
18short_description: Manage Azure Virtual Network Peering
19description:
20    - Create, update and delete Azure Virtual Network Peering.
21
22options:
23    resource_group:
24        description:
25            - Name of a resource group where the vnet exists.
26        required: true
27    name:
28        description:
29            - Name of the virtual network peering.
30        required: true
31    virtual_network:
32        description:
33            - Name or resource ID of the virtual network to be peered.
34        required: true
35    remote_virtual_network:
36        description:
37            - Remote virtual network to be peered.
38            - It can be name of remote virtual network in same resource group.
39            - It can be remote virtual network resource ID.
40            - It can be a dict which contains I(name) and I(resource_group) of remote virtual network.
41            - Required when creating.
42    allow_virtual_network_access:
43        description:
44            - Allows VMs in the remote VNet to access all VMs in the local VNet.
45        type: bool
46        default: false
47    allow_forwarded_traffic:
48        description:
49            - Allows forwarded traffic from the VMs in the remote VNet.
50        type: bool
51        default: false
52    use_remote_gateways:
53        description:
54            - If remote gateways can be used on this virtual network.
55        type: bool
56        default: false
57    allow_gateway_transit:
58        description:
59            - Allows VNet to use the remote VNet's gateway. Remote VNet gateway must have --allow-gateway-transit enabled for remote peering.
60            - Only 1 peering can have this flag enabled. Cannot be set if the VNet already has a gateway.
61        type: bool
62        default: false
63    state:
64        description:
65            - State of the virtual network peering. Use C(present) to create or update a peering and C(absent) to delete it.
66        default: present
67        choices:
68            - absent
69            - present
70
71extends_documentation_fragment:
72    - azure
73
74author:
75    - Yunge Zhu (@yungezz)
76'''
77
78EXAMPLES = '''
79    - name: Create virtual network peering
80      azure_rm_virtualnetworkpeering:
81        resource_group: myResourceGroup
82        virtual_network: myVirtualNetwork
83        name: myPeering
84        remote_virtual_network:
85          resource_group: mySecondResourceGroup
86          name: myRemoteVirtualNetwork
87        allow_virtual_network_access: false
88        allow_forwarded_traffic: true
89
90    - name: Delete the virtual network peering
91      azure_rm_virtualnetworkpeering:
92        resource_group: myResourceGroup
93        virtual_network: myVirtualNetwork
94        name: myPeering
95        state: absent
96'''
97RETURN = '''
98id:
99    description:
100        - ID of the Azure virtual network peering.
101    returned: always
102    type: str
103    sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVirtualN
104             etwork/virtualNetworkPeerings/myPeering"
105'''
106
107try:
108    from msrestazure.azure_exceptions import CloudError
109    from msrestazure.tools import is_valid_resource_id
110    from msrest.polling import LROPoller
111except ImportError:
112    # This is handled in azure_rm_common
113    pass
114
115from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
116
117
118def virtual_network_to_dict(vnet):
119    '''
120    Convert a virtual network object to a dict.
121    '''
122    results = dict(
123        id=vnet.id,
124        name=vnet.name,
125        location=vnet.location,
126        type=vnet.type,
127        tags=vnet.tags,
128        provisioning_state=vnet.provisioning_state,
129        etag=vnet.etag
130    )
131    if vnet.dhcp_options and len(vnet.dhcp_options.dns_servers) > 0:
132        results['dns_servers'] = []
133        for server in vnet.dhcp_options.dns_servers:
134            results['dns_servers'].append(server)
135    if vnet.address_space and len(vnet.address_space.address_prefixes) > 0:
136        results['address_prefixes'] = []
137        for space in vnet.address_space.address_prefixes:
138            results['address_prefixes'].append(space)
139    return results
140
141
142def vnetpeering_to_dict(vnetpeering):
143    '''
144    Convert a virtual network peering object to a dict.
145    '''
146    results = dict(
147        id=vnetpeering.id,
148        name=vnetpeering.name,
149        remote_virtual_network=vnetpeering.remote_virtual_network.id,
150        remote_address_space=dict(
151            address_prefixes=vnetpeering.remote_address_space.address_prefixes
152        ),
153        peering_state=vnetpeering.peering_state,
154        provisioning_state=vnetpeering.provisioning_state,
155        use_remote_gateways=vnetpeering.use_remote_gateways,
156        allow_gateway_transit=vnetpeering.allow_gateway_transit,
157        allow_forwarded_traffic=vnetpeering.allow_forwarded_traffic,
158        allow_virtual_network_access=vnetpeering.allow_virtual_network_access,
159        etag=vnetpeering.etag
160    )
161    return results
162
163
164class AzureRMVirtualNetworkPeering(AzureRMModuleBase):
165
166    def __init__(self):
167        self.module_arg_spec = dict(
168            resource_group=dict(
169                type='str',
170                required=True
171            ),
172            name=dict(
173                type='str',
174                required=True
175            ),
176            virtual_network=dict(
177                type='raw'
178            ),
179            remote_virtual_network=dict(
180                type='raw'
181            ),
182            allow_virtual_network_access=dict(
183                type='bool',
184                default=False
185            ),
186            allow_forwarded_traffic=dict(
187                type='bool',
188                default=False
189            ),
190            allow_gateway_transit=dict(
191                type='bool',
192                default=False
193            ),
194            use_remote_gateways=dict(
195                type='bool',
196                default=False
197            ),
198            state=dict(
199                type='str',
200                default='present',
201                choices=['present', 'absent']
202            )
203        )
204
205        self.resource_group = None
206        self.name = None
207        self.virtual_network = None
208        self.remote_virtual_network = None
209        self.allow_virtual_network_access = None
210        self.allow_forwarded_traffic = None
211        self.allow_gateway_transit = None
212        self.use_remote_gateways = None
213
214        self.results = dict(changed=False)
215
216        super(AzureRMVirtualNetworkPeering, self).__init__(derived_arg_spec=self.module_arg_spec,
217                                                           supports_check_mode=True,
218                                                           supports_tags=False)
219
220    def exec_module(self, **kwargs):
221        """Main module execution method"""
222
223        for key in list(self.module_arg_spec.keys()):
224            setattr(self, key, kwargs[key])
225
226        to_be_updated = False
227
228        resource_group = self.get_resource_group(self.resource_group)
229
230        # parse virtual_network
231        self.virtual_network = self.parse_resource_to_dict(self.virtual_network)
232        if self.virtual_network['resource_group'] != self.resource_group:
233            self.fail('Resource group of virtual_network is not same as param resource_group')
234
235        # parse remote virtual_network
236        self.remote_virtual_network = self.format_vnet_id(self.remote_virtual_network)
237
238        # get vnet peering
239        response = self.get_vnet_peering()
240
241        if self.state == 'present':
242            if response:
243                # check vnet id not changed
244                existing_vnet = self.parse_resource_to_dict(response['id'])
245                if existing_vnet['resource_group'] != self.virtual_network['resource_group'] or \
246                   existing_vnet['name'] != self.virtual_network['name']:
247                    self.fail("Cannot update virtual_network of Virtual Network Peering!")
248
249                # check remote vnet id not changed
250                if response['remote_virtual_network'].lower() != self.remote_virtual_network.lower():
251                    self.fail("Cannot update remote_virtual_network of Virtual Network Peering!")
252
253                # check if update
254                to_be_updated = self.check_update(response)
255
256            else:
257                # not exists, create new vnet peering
258                to_be_updated = True
259
260                # check if vnet exists
261                virtual_network = self.get_vnet(self.virtual_network['resource_group'], self.virtual_network['name'])
262                if not virtual_network:
263                    self.fail("Virtual network {0} in resource group {1} does not exist!".format(
264                        self.virtual_network['name'], self.virtual_network['resource_group']))
265
266        elif self.state == 'absent':
267            if response:
268                self.log('Delete Azure Virtual Network Peering')
269                self.results['changed'] = True
270                self.results['id'] = response['id']
271
272                if self.check_mode:
273                    return self.results
274
275                response = self.delete_vnet_peering()
276
277            else:
278                self.fail("Azure Virtual Network Peering {0} does not exist in resource group {1}".format(self.name, self.resource_group))
279
280        if to_be_updated:
281            self.results['changed'] = True
282
283            if self.check_mode:
284                return self.results
285
286            response = self.create_or_update_vnet_peering()
287            self.results['id'] = response['id']
288
289        return self.results
290
291    def format_vnet_id(self, vnet):
292        if not vnet:
293            return vnet
294        if isinstance(vnet, dict) and vnet.get('name') and vnet.get('resource_group'):
295            remote_vnet_id = format_resource_id(vnet['name'],
296                                                self.subscription_id,
297                                                'Microsoft.Network',
298                                                'virtualNetworks',
299                                                vnet['resource_group'])
300        elif isinstance(vnet, str):
301            if is_valid_resource_id(vnet):
302                remote_vnet_id = vnet
303            else:
304                remote_vnet_id = format_resource_id(vnet,
305                                                    self.subscription_id,
306                                                    'Microsoft.Network',
307                                                    'virtualNetworks',
308                                                    self.resource_group)
309        else:
310            self.fail("remote_virtual_network could be a valid resource id, dict of name and resource_group, name of virtual network in same resource group.")
311        return remote_vnet_id
312
313    def check_update(self, exisiting_vnet_peering):
314        if self.allow_forwarded_traffic != exisiting_vnet_peering['allow_forwarded_traffic']:
315            return True
316        if self.allow_gateway_transit != exisiting_vnet_peering['allow_gateway_transit']:
317            return True
318        if self.allow_virtual_network_access != exisiting_vnet_peering['allow_virtual_network_access']:
319            return True
320        if self.use_remote_gateways != exisiting_vnet_peering['use_remote_gateways']:
321            return True
322        return False
323
324    def get_vnet(self, resource_group, vnet_name):
325        '''
326        Get Azure Virtual Network
327        :return: deserialized Azure Virtual Network
328        '''
329        self.log("Get the Azure Virtual Network {0}".format(vnet_name))
330        vnet = self.network_client.virtual_networks.get(resource_group, vnet_name)
331
332        if vnet:
333            results = virtual_network_to_dict(vnet)
334            return results
335        return False
336
337    def create_or_update_vnet_peering(self):
338        '''
339        Creates or Update Azure Virtual Network Peering.
340
341        :return: deserialized Azure Virtual Network Peering instance state dictionary
342        '''
343        self.log("Creating or Updating the Azure Virtual Network Peering {0}".format(self.name))
344
345        vnet_id = format_resource_id(self.virtual_network['name'],
346                                     self.subscription_id,
347                                     'Microsoft.Network',
348                                     'virtualNetworks',
349                                     self.virtual_network['resource_group'])
350        peering = self.network_models.VirtualNetworkPeering(
351            id=vnet_id,
352            name=self.name,
353            remote_virtual_network=self.network_models.SubResource(id=self.remote_virtual_network),
354            allow_virtual_network_access=self.allow_virtual_network_access,
355            allow_gateway_transit=self.allow_gateway_transit,
356            allow_forwarded_traffic=self.allow_forwarded_traffic,
357            use_remote_gateways=self.use_remote_gateways)
358
359        try:
360            response = self.network_client.virtual_network_peerings.create_or_update(self.resource_group,
361                                                                                     self.virtual_network['name'],
362                                                                                     self.name,
363                                                                                     peering)
364            if isinstance(response, LROPoller):
365                response = self.get_poller_result(response)
366            return vnetpeering_to_dict(response)
367        except CloudError as exc:
368            self.fail("Error creating Azure Virtual Network Peering: {0}.".format(exc.message))
369
370    def delete_vnet_peering(self):
371        '''
372        Deletes the specified Azure Virtual Network Peering
373
374        :return: True
375        '''
376        self.log("Deleting Azure Virtual Network Peering {0}".format(self.name))
377        try:
378            poller = self.network_client.virtual_network_peerings.delete(
379                self.resource_group, self.virtual_network['name'], self.name)
380            self.get_poller_result(poller)
381            return True
382        except CloudError as e:
383            self.fail("Error deleting the Azure Virtual Network Peering: {0}".format(e.message))
384            return False
385
386    def get_vnet_peering(self):
387        '''
388        Gets the Virtual Network Peering.
389
390        :return: deserialized Virtual Network Peering
391        '''
392        self.log(
393            "Checking if Virtual Network Peering {0} is present".format(self.name))
394        try:
395            response = self.network_client.virtual_network_peerings.get(self.resource_group,
396                                                                        self.virtual_network['name'],
397                                                                        self.name)
398            self.log("Response : {0}".format(response))
399            return vnetpeering_to_dict(response)
400        except CloudError:
401            self.log('Did not find the Virtual Network Peering.')
402            return False
403
404
405def main():
406    """Main execution"""
407    AzureRMVirtualNetworkPeering()
408
409
410if __name__ == '__main__':
411    main()
412