1#!/usr/bin/python
2#
3# Copyright (c) Ansible Project
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['preview'],
12                    'supported_by': 'community'}
13
14
15DOCUMENTATION = '''
16---
17module: azure_rm_deployment
18
19short_description: Create or destroy Azure Resource Manager template deployments
20
21version_added: "2.1"
22
23description:
24    - Create or destroy Azure Resource Manager template deployments via the Azure SDK for Python.
25    - You can find some quick start templates in GitHub here U(https://github.com/azure/azure-quickstart-templates).
26    - For more information on Azure Resource Manager templates see U(https://azure.microsoft.com/en-us/documentation/articles/resource-group-template-deploy/).
27
28options:
29  resource_group:
30    description:
31        - The resource group name to use or create to host the deployed template.
32    required: true
33    aliases:
34        - resource_group_name
35  name:
36    description:
37        - The name of the deployment to be tracked in the resource group deployment history.
38        - Re-using a deployment name will overwrite the previous value in the resource group's deployment history.
39    default: ansible-arm
40    aliases:
41        - deployment_name
42  location:
43    description:
44        - The geo-locations in which the resource group will be located.
45    default: westus
46  deployment_mode:
47    description:
48        - In incremental mode, resources are deployed without deleting existing resources that are not included in the template.
49        - In complete mode resources are deployed and existing resources in the resource group not included in the template are deleted.
50    default: incremental
51    choices:
52        - complete
53        - incremental
54  template:
55    description:
56        - A hash containing the templates inline. This parameter is mutually exclusive with I(template_link).
57        - Either I(template) or I(template_link) is required if I(state=present).
58    type: dict
59  template_link:
60    description:
61        - Uri of file containing the template body. This parameter is mutually exclusive with I(template).
62        - Either I(template) or I(template_link) is required if I(state=present).
63  parameters:
64    description:
65        - A hash of all the required template variables for the deployment template. This parameter is mutually exclusive with I(parameters_link).
66        - Either I(parameters_link) or I(parameters) is required if I(state=present).
67    type: dict
68  parameters_link:
69    description:
70        - Uri of file containing the parameters body. This parameter is mutually exclusive with I(parameters).
71        - Either I(parameters_link) or I(parameters) is required if I(state=present).
72  wait_for_deployment_completion:
73    description:
74        - Whether or not to block until the deployment has completed.
75    type: bool
76    default: 'yes'
77  wait_for_deployment_polling_period:
78    description:
79        - Time (in seconds) to wait between polls when waiting for deployment completion.
80    default: 10
81  state:
82    description:
83        - If I(state=present), template will be created.
84        - If I(state=present) and deployment exists, it will be updated.
85        - If I(state=absent), stack will be removed.
86    default: present
87    choices:
88        - present
89        - absent
90
91extends_documentation_fragment:
92    - azure
93    - azure_tags
94
95author:
96    - David Justice (@devigned)
97    - Laurent Mazuel (@lmazuel)
98    - Andre Price (@obsoleted)
99
100'''
101
102EXAMPLES = '''
103# Destroy a template deployment
104- name: Destroy Azure Deploy
105  azure_rm_deployment:
106    resource_group: myResourceGroup
107    name: myDeployment
108    state: absent
109
110# Create or update a template deployment based on uris using parameter and template links
111- name: Create Azure Deploy
112  azure_rm_deployment:
113    resource_group: myResourceGroup
114    name: myDeployment
115    template_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-simple-linux/azuredeploy.json'
116    parameters_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-simple-linux/azuredeploy.parameters.json'
117
118# Create or update a template deployment based on a uri to the template and parameters specified inline.
119# This deploys a VM with SSH support for a given public key, then stores the result in 'azure_vms'. The result is then
120# used to create a new host group. This host group is then used to wait for each instance to respond to the public IP SSH.
121---
122- name: Create Azure Deploy
123  azure_rm_deployment:
124    resource_group: myResourceGroup
125    name: myDeployment
126    parameters:
127      newStorageAccountName:
128        value: devopsclestorage1
129      adminUsername:
130        value: devopscle
131      dnsNameForPublicIP:
132        value: devopscleazure
133      location:
134        value: West US
135      vmSize:
136        value: Standard_A2
137      vmName:
138        value: ansibleSshVm
139      sshKeyData:
140        value: YOUR_SSH_PUBLIC_KEY
141    template_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-sshkey/azuredeploy.json'
142  register: azure
143- name: Add new instance to host group
144  add_host:
145    hostname: "{{ item['ips'][0].public_ip }}"
146    groupname: azure_vms
147  loop: "{{ azure.deployment.instances }}"
148
149# Deploy an Azure WebApp running a hello world'ish node app
150- name: Create Azure WebApp Deployment at http://devopscleweb.azurewebsites.net/hello.js
151  azure_rm_deployment:
152    resource_group: myResourceGroup
153    name: myDeployment
154    parameters:
155      repoURL:
156        value: 'https://github.com/devigned/az-roadshow-oss.git'
157      siteName:
158        value: devopscleweb
159      hostingPlanName:
160        value: someplan
161      siteLocation:
162        value: westus
163      sku:
164        value: Standard
165    template_link: 'https://raw.githubusercontent.com/azure/azure-quickstart-templates/master/201-web-app-github-deploy/azuredeploy.json'
166
167# Create or update a template deployment based on an inline template and parameters
168- name: Create Azure Deploy
169  azure_rm_deployment:
170    resource_group: myResourceGroup
171    name: myDeployment
172    template:
173      $schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
174      contentVersion: "1.0.0.0"
175      parameters:
176        newStorageAccountName:
177          type: "string"
178          metadata:
179            description: "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
180        adminUsername:
181          type: "string"
182          metadata:
183            description: "User name for the Virtual Machine."
184        adminPassword:
185          type: "securestring"
186          metadata:
187            description: "Password for the Virtual Machine."
188        dnsNameForPublicIP:
189          type: "string"
190          metadata:
191            description: "Unique DNS Name for the Public IP used to access the Virtual Machine."
192        ubuntuOSVersion:
193          type: "string"
194          defaultValue: "14.04.2-LTS"
195          allowedValues:
196            - "12.04.5-LTS"
197            - "14.04.2-LTS"
198            - "15.04"
199          metadata:
200            description: >
201                         The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version.
202                         Allowed values: 12.04.5-LTS, 14.04.2-LTS, 15.04."
203      variables:
204        location: "West US"
205        imagePublisher: "Canonical"
206        imageOffer: "UbuntuServer"
207        OSDiskName: "osdiskforlinuxsimple"
208        nicName: "myVMNic"
209        addressPrefix: "192.0.2.0/24"
210        subnetName: "Subnet"
211        subnetPrefix: "10.0.0.0/24"
212        storageAccountType: "Standard_LRS"
213        publicIPAddressName: "myPublicIP"
214        publicIPAddressType: "Dynamic"
215        vmStorageAccountContainerName: "vhds"
216        vmName: "MyUbuntuVM"
217        vmSize: "Standard_D1"
218        virtualNetworkName: "MyVNET"
219        vnetID: "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]"
220        subnetRef: "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
221      resources:
222        - type: "Microsoft.Storage/storageAccounts"
223          name: "[parameters('newStorageAccountName')]"
224          apiVersion: "2015-05-01-preview"
225          location: "[variables('location')]"
226          properties:
227            accountType: "[variables('storageAccountType')]"
228        - apiVersion: "2015-05-01-preview"
229          type: "Microsoft.Network/publicIPAddresses"
230          name: "[variables('publicIPAddressName')]"
231          location: "[variables('location')]"
232          properties:
233            publicIPAllocationMethod: "[variables('publicIPAddressType')]"
234            dnsSettings:
235              domainNameLabel: "[parameters('dnsNameForPublicIP')]"
236        - type: "Microsoft.Network/virtualNetworks"
237          apiVersion: "2015-05-01-preview"
238          name: "[variables('virtualNetworkName')]"
239          location: "[variables('location')]"
240          properties:
241            addressSpace:
242              addressPrefixes:
243                - "[variables('addressPrefix')]"
244            subnets:
245              -
246                name: "[variables('subnetName')]"
247                properties:
248                  addressPrefix: "[variables('subnetPrefix')]"
249        - type: "Microsoft.Network/networkInterfaces"
250          apiVersion: "2015-05-01-preview"
251          name: "[variables('nicName')]"
252          location: "[variables('location')]"
253          dependsOn:
254            - "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
255            - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
256          properties:
257            ipConfigurations:
258              -
259                name: "ipconfig1"
260                properties:
261                  privateIPAllocationMethod: "Dynamic"
262                  publicIPAddress:
263                    id: "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
264                  subnet:
265                    id: "[variables('subnetRef')]"
266        - type: "Microsoft.Compute/virtualMachines"
267          apiVersion: "2015-06-15"
268          name: "[variables('vmName')]"
269          location: "[variables('location')]"
270          dependsOn:
271            - "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]"
272            - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
273          properties:
274            hardwareProfile:
275              vmSize: "[variables('vmSize')]"
276            osProfile:
277              computername: "[variables('vmName')]"
278              adminUsername: "[parameters('adminUsername')]"
279              adminPassword: "[parameters('adminPassword')]"
280            storageProfile:
281              imageReference:
282                publisher: "[variables('imagePublisher')]"
283                offer: "[variables('imageOffer')]"
284                sku: "[parameters('ubuntuOSVersion')]"
285                version: "latest"
286              osDisk:
287                name: "osdisk"
288                vhd:
289                  uri: >
290                       [concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',
291                       variables('OSDiskName'),'.vhd')]
292                caching: "ReadWrite"
293                createOption: "FromImage"
294            networkProfile:
295              networkInterfaces:
296                -
297                  id: "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
298            diagnosticsProfile:
299              bootDiagnostics:
300                enabled: "true"
301                storageUri: "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
302    parameters:
303      newStorageAccountName:
304        value: devopsclestorage
305      adminUsername:
306        value: devopscle
307      adminPassword:
308        value: Password1!
309      dnsNameForPublicIP:
310        value: devopscleazure
311'''
312
313RETURN = '''
314deployment:
315    description: Deployment details.
316    type: complex
317    returned: always
318    contains:
319        group_name:
320            description:
321                - Name of the resource group.
322            type: str
323            returned: always
324            sample: myResourceGroup
325        id:
326            description:
327                - The Azure ID of the deployment.
328            type: str
329            returned: always
330            sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Resources/deployments/myDeployment"
331        instances:
332            description:
333                - Provides the public IP addresses for each VM instance.
334            type: list
335            returned: always
336            contains:
337                ips:
338                    description:
339                        - List of Public IP addresses.
340                    type: list
341                    returned: always
342                    contains:
343                        dns_settings:
344                            description:
345                                - DNS Settings.
346                            type: complex
347                            returned: always
348                            contains:
349                                domain_name_label:
350                                    description:
351                                        - Domain Name Label.
352                                    type: str
353                                    returned: always
354                                    sample: myvirtualmachine
355                                fqdn:
356                                    description:
357                                        - Fully Qualified Domain Name.
358                                    type: str
359                                    returned: always
360                                    sample: myvirtualmachine.eastus2.cloudapp.azure.com
361                        id:
362                            description:
363                                - Public IP resource id.
364                            returned: always
365                            type: str
366                            sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/p
367                                     ublicIPAddresses/myPublicIP"
368                        name:
369                            description:
370                                -  Public IP resource name.
371                            returned: always
372                            type: str
373                            sample: myPublicIP
374                        public_ip:
375                            description:
376                                - Public IP address value.
377                            returned: always
378                            type: str
379                            sample: 104.209.244.123
380                        public_ip_allocation_method:
381                            description:
382                                - Public IP allocation method.
383                            returned: always
384                            type: str
385                            sample: Dynamic
386                vm_name:
387                    description:
388                        - Virtual machine name.
389                    returned: always
390                    type: str
391                    sample: myvirtualmachine
392        name:
393          description:
394              - Name of the deployment.
395          type: str
396          returned: always
397          sample: myDeployment
398        outputs:
399          description:
400              - Dictionary of outputs received from the deployment.
401          type: complex
402          returned: always
403          sample: { "hostname": { "type": "String", "value": "myvirtualmachine.eastus2.cloudapp.azure.com" } }
404'''
405
406import time
407
408try:
409    from azure.common.credentials import ServicePrincipalCredentials
410    import time
411    import yaml
412except ImportError as exc:
413    IMPORT_ERROR = "Error importing module prerequisites: %s" % exc
414
415try:
416    from itertools import chain
417    from azure.common.exceptions import CloudError
418    from azure.mgmt.resource.resources import ResourceManagementClient
419    from azure.mgmt.network import NetworkManagementClient
420
421except ImportError:
422    # This is handled in azure_rm_common
423    pass
424
425from ansible.module_utils.azure_rm_common import AzureRMModuleBase
426
427
428class AzureRMDeploymentManager(AzureRMModuleBase):
429
430    def __init__(self):
431
432        self.module_arg_spec = dict(
433            resource_group=dict(type='str', required=True, aliases=['resource_group_name']),
434            name=dict(type='str', default="ansible-arm", aliases=['deployment_name']),
435            state=dict(type='str', default='present', choices=['present', 'absent']),
436            template=dict(type='dict', default=None),
437            parameters=dict(type='dict', default=None),
438            template_link=dict(type='str', default=None),
439            parameters_link=dict(type='str', default=None),
440            location=dict(type='str', default="westus"),
441            deployment_mode=dict(type='str', default='incremental', choices=['complete', 'incremental']),
442            wait_for_deployment_completion=dict(type='bool', default=True),
443            wait_for_deployment_polling_period=dict(type='int', default=10)
444        )
445
446        mutually_exclusive = [('template', 'template_link'),
447                              ('parameters', 'parameters_link')]
448
449        self.resource_group = None
450        self.state = None
451        self.template = None
452        self.parameters = None
453        self.template_link = None
454        self.parameters_link = None
455        self.location = None
456        self.deployment_mode = None
457        self.name = None
458        self.wait_for_deployment_completion = None
459        self.wait_for_deployment_polling_period = None
460        self.tags = None
461        self.append_tags = None
462
463        self.results = dict(
464            deployment=dict(),
465            changed=False,
466            msg=""
467        )
468
469        super(AzureRMDeploymentManager, self).__init__(derived_arg_spec=self.module_arg_spec,
470                                                       mutually_exclusive=mutually_exclusive,
471                                                       supports_check_mode=False)
472
473    def exec_module(self, **kwargs):
474
475        for key in list(self.module_arg_spec.keys()) + ['append_tags', 'tags']:
476            setattr(self, key, kwargs[key])
477
478        if self.state == 'present':
479            deployment = self.deploy_template()
480            if deployment is None:
481                self.results['deployment'] = dict(
482                    name=self.name,
483                    group_name=self.resource_group,
484                    id=None,
485                    outputs=None,
486                    instances=None
487                )
488            else:
489                self.results['deployment'] = dict(
490                    name=deployment.name,
491                    group_name=self.resource_group,
492                    id=deployment.id,
493                    outputs=deployment.properties.outputs,
494                    instances=self._get_instances(deployment)
495                )
496
497            self.results['changed'] = True
498            self.results['msg'] = 'deployment succeeded'
499        else:
500            try:
501                if self.get_resource_group(self.resource_group):
502                    self.destroy_resource_group()
503                    self.results['changed'] = True
504                    self.results['msg'] = "deployment deleted"
505            except CloudError:
506                # resource group does not exist
507                pass
508
509        return self.results
510
511    def deploy_template(self):
512        """
513        Deploy the targeted template and parameters
514        :param module: Ansible module containing the validated configuration for the deployment template
515        :param client: resource management client for azure
516        :param conn_info: connection info needed
517        :return:
518        """
519
520        deploy_parameter = self.rm_models.DeploymentProperties(mode=self.deployment_mode)
521        if not self.parameters_link:
522            deploy_parameter.parameters = self.parameters
523        else:
524            deploy_parameter.parameters_link = self.rm_models.ParametersLink(
525                uri=self.parameters_link
526            )
527        if not self.template_link:
528            deploy_parameter.template = self.template
529        else:
530            deploy_parameter.template_link = self.rm_models.TemplateLink(
531                uri=self.template_link
532            )
533
534        if self.append_tags and self.tags:
535            try:
536                # fetch the RG directly (instead of using the base helper) since we don't want to exit if it's missing
537                rg = self.rm_client.resource_groups.get(self.resource_group)
538                if rg.tags:
539                    self.tags = dict(self.tags, **rg.tags)
540            except CloudError:
541                # resource group does not exist
542                pass
543
544        params = self.rm_models.ResourceGroup(location=self.location, tags=self.tags)
545
546        try:
547            self.rm_client.resource_groups.create_or_update(self.resource_group, params)
548        except CloudError as exc:
549            self.fail("Resource group create_or_update failed with status code: %s and message: %s" %
550                      (exc.status_code, exc.message))
551        try:
552            result = self.rm_client.deployments.create_or_update(self.resource_group,
553                                                                 self.name,
554                                                                 deploy_parameter)
555
556            deployment_result = None
557            if self.wait_for_deployment_completion:
558                deployment_result = self.get_poller_result(result)
559                while deployment_result.properties is None or deployment_result.properties.provisioning_state not in ['Canceled', 'Failed', 'Deleted',
560                                                                                                                      'Succeeded']:
561                    time.sleep(self.wait_for_deployment_polling_period)
562                    deployment_result = self.rm_client.deployments.get(self.resource_group, self.name)
563        except CloudError as exc:
564            failed_deployment_operations = self._get_failed_deployment_operations(self.name)
565            self.log("Deployment failed %s: %s" % (exc.status_code, exc.message))
566            self.fail("Deployment failed with status code: %s and message: %s" % (exc.status_code, exc.message),
567                      failed_deployment_operations=failed_deployment_operations)
568
569        if self.wait_for_deployment_completion and deployment_result.properties.provisioning_state != 'Succeeded':
570            self.log("provisioning state: %s" % deployment_result.properties.provisioning_state)
571            failed_deployment_operations = self._get_failed_deployment_operations(self.name)
572            self.fail('Deployment failed. Deployment id: %s' % deployment_result.id,
573                      failed_deployment_operations=failed_deployment_operations)
574
575        return deployment_result
576
577    def destroy_resource_group(self):
578        """
579        Destroy the targeted resource group
580        """
581        try:
582            result = self.rm_client.resource_groups.delete(self.resource_group)
583            result.wait()  # Blocking wait till the delete is finished
584        except CloudError as e:
585            if e.status_code == 404 or e.status_code == 204:
586                return
587            else:
588                self.fail("Delete resource group and deploy failed with status code: %s and message: %s" %
589                          (e.status_code, e.message))
590
591    def _get_failed_nested_operations(self, current_operations):
592        new_operations = []
593        for operation in current_operations:
594            if operation.properties.provisioning_state == 'Failed':
595                new_operations.append(operation)
596                if operation.properties.target_resource and \
597                   'Microsoft.Resources/deployments' in operation.properties.target_resource.id:
598                    nested_deployment = operation.properties.target_resource.resource_name
599                    try:
600                        nested_operations = self.rm_client.deployment_operations.list(self.resource_group,
601                                                                                      nested_deployment)
602                    except CloudError as exc:
603                        self.fail("List nested deployment operations failed with status code: %s and message: %s" %
604                                  (exc.status_code, exc.message))
605                    new_nested_operations = self._get_failed_nested_operations(nested_operations)
606                    new_operations += new_nested_operations
607        return new_operations
608
609    def _get_failed_deployment_operations(self, name):
610        results = []
611        # time.sleep(15) # there is a race condition between when we ask for deployment status and when the
612        #               # status is available.
613
614        try:
615            operations = self.rm_client.deployment_operations.list(self.resource_group, name)
616        except CloudError as exc:
617            self.fail("Get deployment failed with status code: %s and message: %s" %
618                      (exc.status_code, exc.message))
619        try:
620            results = [
621                dict(
622                    id=op.id,
623                    operation_id=op.operation_id,
624                    status_code=op.properties.status_code,
625                    status_message=op.properties.status_message,
626                    target_resource=dict(
627                        id=op.properties.target_resource.id,
628                        resource_name=op.properties.target_resource.resource_name,
629                        resource_type=op.properties.target_resource.resource_type
630                    ) if op.properties.target_resource else None,
631                    provisioning_state=op.properties.provisioning_state,
632                )
633                for op in self._get_failed_nested_operations(operations)
634            ]
635        except Exception:
636            # If we fail here, the original error gets lost and user receives wrong error message/stacktrace
637            pass
638        self.log(dict(failed_deployment_operations=results), pretty_print=True)
639        return results
640
641    def _get_instances(self, deployment):
642        dep_tree = self._build_hierarchy(deployment.properties.dependencies)
643        vms = self._get_dependencies(dep_tree, resource_type="Microsoft.Compute/virtualMachines")
644        vms_and_nics = [(vm, self._get_dependencies(vm['children'], "Microsoft.Network/networkInterfaces"))
645                        for vm in vms]
646        vms_and_ips = [(vm['dep'], self._nic_to_public_ips_instance(nics))
647                       for vm, nics in vms_and_nics]
648        return [dict(vm_name=vm.resource_name, ips=[self._get_ip_dict(ip)
649                                                    for ip in ips]) for vm, ips in vms_and_ips if len(ips) > 0]
650
651    def _get_dependencies(self, dep_tree, resource_type):
652        matches = [value for value in dep_tree.values() if value['dep'].resource_type == resource_type]
653        for child_tree in [value['children'] for value in dep_tree.values()]:
654            matches += self._get_dependencies(child_tree, resource_type)
655        return matches
656
657    def _build_hierarchy(self, dependencies, tree=None):
658        tree = dict(top=True) if tree is None else tree
659        for dep in dependencies:
660            if dep.resource_name not in tree:
661                tree[dep.resource_name] = dict(dep=dep, children=dict())
662            if isinstance(dep, self.rm_models.Dependency) and dep.depends_on is not None and len(dep.depends_on) > 0:
663                self._build_hierarchy(dep.depends_on, tree[dep.resource_name]['children'])
664
665        if 'top' in tree:
666            tree.pop('top', None)
667            keys = list(tree.keys())
668            for key1 in keys:
669                for key2 in keys:
670                    if key2 in tree and key1 in tree[key2]['children'] and key1 in tree:
671                        tree[key2]['children'][key1] = tree[key1]
672                        tree.pop(key1)
673        return tree
674
675    def _get_ip_dict(self, ip):
676        ip_dict = dict(name=ip.name,
677                       id=ip.id,
678                       public_ip=ip.ip_address,
679                       public_ip_allocation_method=str(ip.public_ip_allocation_method)
680                       )
681        if ip.dns_settings:
682            ip_dict['dns_settings'] = {
683                'domain_name_label': ip.dns_settings.domain_name_label,
684                'fqdn': ip.dns_settings.fqdn
685            }
686        return ip_dict
687
688    def _nic_to_public_ips_instance(self, nics):
689        return [self.network_client.public_ip_addresses.get(public_ip_id.split('/')[4], public_ip_id.split('/')[-1])
690                for nic_obj in (self.network_client.network_interfaces.get(self.resource_group,
691                                                                           nic['dep'].resource_name) for nic in nics)
692                for public_ip_id in [ip_conf_instance.public_ip_address.id
693                                     for ip_conf_instance in nic_obj.ip_configurations
694                                     if ip_conf_instance.public_ip_address]]
695
696
697def main():
698    AzureRMDeploymentManager()
699
700
701if __name__ == '__main__':
702    main()
703