View difference between Paste ID: CVyWHYqy and BgZE8Ayd
SHOW: | | - or go back to the newest paste.
1
#!/usr/bin/python
2
#
3
# Copyright (c) 2016 Matt Davis, <mdavis@ansible.com>
4
#                    Chris Houseknecht, <house@redhat.com>
5
#
6
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7
8
from __future__ import absolute_import, division, print_function
9
__metaclass__ = type
10
11
12
ANSIBLE_METADATA = {'metadata_version': '1.1',
13
                    'status': ['preview'],
14
                    'supported_by': 'certified'}
15
16
import base64
17-
DOCUMENTATION = '''
17+
18-
---
18+
19-
module: azure_rm_virtualmachine
19+
20
try:
21-
version_added: "2.1"
21+
22
    from msrestazure.tools import parse_resource_id
23-
short_description: Manage Azure virtual machines.
23+
24
    # This is handled in azure_rm_common
25-
description:
25+
26-
    - Create, update, stop and start a virtual machine. Provide an existing storage account and network interface or
26+
27-
      allow the module to create these for you. If you choose not to provide a network interface, the resource group
27+
#MP
28-
      must contain a virtual network with at least one subnet.
28+
from ansible.utils.display import Display
29-
    - Before Ansible 2.5, this required an image found in the Azure Marketplace which can be discovered with
29+
30-
      M(azure_rm_virtualmachineimage_facts). In Ansible 2.5 and newer, custom images can be used as well, see the
30+
31-
      examples for more details.
31+
32
33-
options:
33+
34-
    resource_group:
34+
35-
        description:
35+
36-
            - Name of the resource group containing the virtual machine.
36+
37-
        required: true
37+
38-
    name:
38+
39-
        description:
39+
40-
            - Name of the virtual machine.
40+
41-
        required: true
41+
42-
    custom_data:
42+
43-
        description:
43+
44-
            - Data which is made available to the virtual machine and used by e.g., cloud-init.
44+
45-
        version_added: "2.5"
45+
46-
    state:
46+
47-
        description:
47+
48-
            - Assert the state of the virtual machine.
48+
49-
            - State 'present' will check that the machine exists with the requested configuration. If the configuration
49+
50-
              of the existing machine does not match, the machine will be updated. Use options started, allocated and restarted to change the machine's power
50+
51-
              state.
51+
52-
            - State 'absent' will remove the virtual machine.
52+
53-
        default: present
53+
54-
        choices:
54+
55-
            - absent
55+
56-
            - present
56+
57-
    started:
57+
58-
        description:
58+
59-
            - Use with state 'present' to start the machine. Set to false to have the machine be 'stopped'.
59+
60-
        default: true
60+
61-
    allocated:
61+
62-
        description:
62+
63-
            - Toggle that controls if the machine is allocated/deallocated, only useful with state='present'.
63+
64-
        default: True
64+
65-
    restarted:
65+
66-
        description:
66+
67-
            - Use with state 'present' to restart a running VM.
67+
68-
    location:
68+
69-
        description:
69+
70-
            - Valid Azure location. Defaults to location of the resource group.
70+
71-
    short_hostname:
71+
72-
        description:
72+
73-
            - Name assigned internally to the host. On a linux VM this is the name returned by the `hostname` command.
73+
74-
              When creating a virtual machine, short_hostname defaults to name.
74+
75-
    vm_size:
75+
76-
        description:
76+
77-
            - A valid Azure VM size value. For example, 'Standard_D4'. The list of choices varies depending on the
77+
78-
              subscription and location. Check your subscription for available choices. Required when creating a VM.
78+
79-
    admin_username:
79+
80-
        description:
80+
81-
            - Admin username used to access the host after it is created. Required when creating a VM.
81+
82-
    admin_password:
82+
83-
        description:
83+
84-
            - Password for the admin username. Not required if the os_type is Linux and SSH password authentication
84+
85-
              is disabled by setting ssh_password_enabled to false.
85+
86-
    ssh_password_enabled:
86+
87-
        description:
87+
88-
            - When the os_type is Linux, setting ssh_password_enabled to false will disable SSH password authentication
88+
89-
              and require use of SSH keys.
89+
90-
        default: true
90+
91-
    ssh_public_keys:
91+
92-
        description:
92+
93-
            - "For os_type Linux provide a list of SSH keys. Each item in the list should be a dictionary where the
93+
94-
              dictionary contains two keys: path and key_data. Set the path to the default location of the
94+
95-
              authorized_keys files. On an Enterprise Linux host, for example, the path will be
95+
96-
              /home/<admin username>/.ssh/authorized_keys. Set key_data to the actual value of the public key."
96+
97-
    image:
97+
98-
        description:
98+
99-
            - Specifies the image used to build the VM.
99+
100-
            - If a string, the image is sourced from a custom image based on the
100+
101-
              name.
101+
102-
            - 'If a dict with the keys C(publisher), C(offer), C(sku), and
102+
103-
              C(version), the image is sourced from a Marketplace image. NOTE:
103+
104-
              set image.version to C(latest) to get the most recent version of a
104+
105-
              given image.'
105+
106-
            - 'If a dict with the keys C(name) and C(resource_group), the image
106+
107-
              is sourced from a custom image based on the C(name) and
107+
108-
              C(resource_group) set. NOTE: the key C(resource_group) is optional
108+
109-
              and if omitted, all images in the subscription will be searched
109+
110-
              for by C(name).'
110+
111-
            - Custom image support was added in Ansible 2.5
111+
112-
        required: true
112+
113-
    availability_set:
113+
114-
        description:
114+
115-
            - Name or ID of an existing availability set to add the VM to. The availability_set should be in the same resource group as VM.
115+
116-
        version_added: "2.5"
116+
117-
    storage_account_name:
117+
118-
        description:
118+
119-
            - Name of an existing storage account that supports creation of VHD blobs. If not specified for a new VM,
119+
120-
              a new storage account named <vm name>01 will be created using storage type 'Standard_LRS'.
120+
121-
    storage_container_name:
121+
122-
        description:
122+
123-
            - Name of the container to use within the storage account to store VHD blobs. If no name is specified a
123+
124-
              default container will created.
124+
125-
        default: vhds
125+
126-
    storage_blob_name:
126+
127-
        description:
127+
128-
            - Name fo the storage blob used to hold the VM's OS disk image. If no name is provided, defaults to
128+
129-
              the VM name + '.vhd'. If you provide a name, it must end with '.vhd'
129+
130-
        aliases:
130+
131-
            - storage_blob
131+
132-
    managed_disk_type:
132+
133-
        description:
133+
134-
            - Managed OS disk type
134+
135-
        choices:
135+
136-
            - Standard_LRS
136+
        display = Display(4)
137-
            - Premium_LRS
137+
138-
        version_added: "2.4"
138+
139-
    os_disk_caching:
139+
140-
        description:
140+
141-
            - Type of OS disk caching.
141+
142-
        choices:
142+
143-
            - ReadOnly
143+
144-
            - ReadWrite
144+
145-
        default: ReadOnly
145+
146-
        aliases:
146+
147-
            - disk_caching
147+
148-
    os_type:
148+
149-
        description:
149+
150-
            - Base type of operating system.
150+
151-
        choices:
151+
152-
            - Windows
152+
153-
            - Linux
153+
154-
        default:
154+
155-
            - Linux
155+
156-
    data_disks:
156+
157-
        description:
157+
158-
            - Describes list of data disks.
158+
159-
        version_added: "2.4"
159+
160-
        suboptions:
160+
161-
            lun:
161+
162-
                description:
162+
163-
                    - The logical unit number for data disk
163+
164-
                default: 0
164+
165-
                version_added: "2.4"
165+
166-
            disk_size_gb:
166+
167-
                description:
167+
168-
                    - The initial disk size in GB for blank data disks
168+
169-
                version_added: "2.4"
169+
170-
            managed_disk_type:
170+
171-
                description:
171+
172-
                    - Managed data disk type
172+
173-
                choices:
173+
174-
                    - Standard_LRS
174+
175-
                    - Premium_LRS
175+
176-
                version_added: "2.4"
176+
177-
            storage_account_name:
177+
178-
                description:
178+
179-
                    - Name of an existing storage account that supports creation of VHD blobs. If not specified for a new VM,
179+
180-
                      a new storage account named <vm name>01 will be created using storage type 'Standard_LRS'.
180+
181-
                version_added: "2.4"
181+
182-
            storage_container_name:
182+
183-
                description:
183+
184-
                    - Name of the container to use within the storage account to store VHD blobs. If no name is specified a
184+
185-
                      default container will created.
185+
186-
                default: vhds
186+
187-
                version_added: "2.4"
187+
188-
            storage_blob_name:
188+
189-
                description:
189+
190-
                    - Name fo the storage blob used to hold the VM's OS disk image. If no name is provided, defaults to
190+
191-
                      the VM name + '.vhd'. If you provide a name, it must end with '.vhd'
191+
192-
                version_added: "2.4"
192+
193-
            caching:
193+
194-
                description:
194+
195-
                    - Type of data disk caching.
195+
196-
                choices:
196+
197-
                    - ReadOnly
197+
198-
                    - ReadWrite
198+
199-
                default: ReadOnly
199+
200-
                version_added: "2.4"
200+
201-
    public_ip_allocation_method:
201+
202-
        description:
202+
203-
            - If a public IP address is created when creating the VM (because a Network Interface was not provided),
203+
204-
              determines if the public IP address remains permanently associated with the Network Interface. If set
204+
205-
              to 'Dynamic' the public IP address may change any time the VM is rebooted or power cycled.
205+
206-
            - The C(Disabled) choice was added in Ansible 2.6.
206+
207-
        choices:
207+
208-
            - Dynamic
208+
209-
            - Static
209+
210-
            - Disabled
210+
211-
        default:
211+
212-
            - Static
212+
213-
        aliases:
213+
214-
            - public_ip_allocation
214+
215-
    open_ports:
215+
216-
        description:
216+
217-
            - If a network interface is created when creating the VM, a security group will be created as well. For
217+
218-
              Linux hosts a rule will be added to the security group allowing inbound TCP connections to the default
218+
219-
              SSH port 22, and for Windows hosts ports 3389 and 5986 will be opened. Override the default open ports by
219+
220-
              providing a list of ports.
220+
221-
    network_interface_names:
221+
222-
        description:
222+
223-
            - List of existing network interface names to add to the VM. If a network interface name is not provided
223+
224-
              when the VM is created, a default network interface will be created. In order for the module to create
224+
225-
              a network interface, at least one Virtual Network with one Subnet must exist.
225+
226-
    virtual_network_resource_group:
226+
227-
        description:
227+
228-
            - When creating a virtual machine, if a specific virtual network from another resource group should be
228+
229-
              used, use this parameter to specify the resource group to use.
229+
230-
        version_added: "2.4"
230+
231-
    virtual_network_name:
231+
232-
        description:
232+
233-
            - When creating a virtual machine, if a network interface name is not provided, one will be created.
233+
234-
              The new network interface will be assigned to the first virtual network found in the resource group.
234+
235-
              Use this parameter to provide a specific virtual network instead.
235+
236-
        aliases:
236+
237-
            - virtual_network
237+
238-
    subnet_name:
238+
239-
        description:
239+
240-
            - When creating a virtual machine, if a network interface name is not provided, one will be created.
240+
241-
              The new network interface will be assigned to the first subnet found in the virtual network.
241+
242-
              Use this parameter to provide a specific subnet instead.
242+
243-
        aliases:
243+
244-
            - subnet
244+
245-
    remove_on_absent:
245+
246-
        description:
246+
247-
            - When removing a VM using state 'absent', also remove associated resources
247+
248-
            - "It can be 'all' or a list with any of the following: ['network_interfaces', 'virtual_storage', 'public_ips']"
248+
249-
            - Any other input will be ignored
249+
250-
        default: ['all']
250+
251-
    plan:
251+
252-
        description:
252+
253-
            - A dictionary describing a third-party billing plan for an instance
253+
254-
        version_added: 2.5
254+
255-
        suboptions:
255+
256-
            name:
256+
257-
                description:
257+
258-
                    - billing plan name
258+
259-
                required: true
259+
260-
            product:
260+
261-
                description:
261+
262-
                    - product name
262+
263-
                required: true
263+
264-
            publisher:
264+
265-
                description:
265+
266-
                    - publisher offering the plan
266+
267-
                required: true
267+
268-
            promotion_code:
268+
269-
                description:
269+
270-
                    - optional promotion code
270+
271
                    vm_dict['properties']['osProfile']['computerName'] = self.short_hostname
272-
extends_documentation_fragment:
272+
273-
    - azure
273+
274-
    - azure_tags
274+
275
                    changed = True
276-
author:
276+
277-
    - "Chris Houseknecht (@chouseknecht)"
277+
278-
    - "Matt Davis (@nitzmahone)"
278+
279
                             .format(self.name, vm_dict['powerstate']))
280-
'''
280+
281-
EXAMPLES = '''
281+
282
                elif self.state == 'present' and not self.allocated and vm_dict['powerstate'] not in ['deallocated', 'deallocating']:
283-
- name: Create VM with defaults
283+
284-
  azure_rm_virtualmachine:
284+
285-
    resource_group: Testing
285+
286-
    name: testvm10
286+
287-
    admin_username: chouseknecht
287+
288-
    admin_password: <your password here>
288+
289-
    image:
289+
290-
      offer: CentOS
290+
291-
      publisher: OpenLogic
291+
292-
      sku: '7.1'
292+
293-
      version: latest
293+
294
            elif self.state == 'absent':
295-
- name: Create a VM with managed disk
295+
296-
  azure_rm_virtualmachine:
296+
297-
    resource_group: Testing
297+
298-
    name: testvm001
298+
299-
    vm_size: Standard_D4
299+
300-
    managed_disk_type: Standard_LRS
300+
301-
    admin_username: adminUser
301+
302-
    ssh_public_keys:
302+
303-
      - path: /home/adminUser/.ssh/authorized_keys
303+
304-
        key_data: < insert yor ssh public key here... >
304+
305-
    image:
305+
306-
      offer: CoreOS
306+
307-
      publisher: CoreOS
307+
308-
      sku: Stable
308+
309-
      version: latest
309+
310
311-
- name: Create a VM with existing storage account and NIC
311+
312-
  azure_rm_virtualmachine:
312+
313-
    resource_group: Testing
313+
314-
    name: testvm002
314+
315-
    vm_size: Standard_D4
315+
316-
    storage_account: testaccount001
316+
317-
    admin_username: adminUser
317+
318-
    ssh_public_keys:
318+
319-
      - path: /home/adminUser/.ssh/authorized_keys
319+
320-
        key_data: < insert yor ssh public key here... >
320+
321-
    network_interfaces: testvm001
321+
322-
    image:
322+
323-
      offer: CentOS
323+
324-
      publisher: OpenLogic
324+
325-
      sku: '7.1'
325+
326-
      version: latest
326+
327
                        if disable_ssh_password and not self.ssh_public_keys:
328-
- name: Create a VM with OS and multiple data managed disks
328+
329-
  azure_rm_virtualmachine:
329+
330-
    resource_group: Testing
330+
331-
    name: testvm001
331+
332-
    vm_size: Standard_D4
332+
333-
    managed_disk_type: Standard_LRS
333+
334-
    admin_username: adminUser
334+
335-
    ssh_public_keys:
335+
336-
      - path: /home/adminUser/.ssh/authorized_keys
336+
337-
        key_data: < insert yor ssh public key here... >
337+
338-
    image:
338+
339-
      offer: CoreOS
339+
340-
      publisher: CoreOS
340+
341-
      sku: Stable
341+
342-
      version: latest
342+
343-
    data_disks:
343+
344-
        - lun: 0
344+
345-
          disk_size_gb: 64
345+
346-
          managed_disk_type: Standard_LRS
346+
347-
        - lun: 1
347+
348-
          disk_size_gb: 128
348+
349-
          managed_disk_type: Premium_LRS
349+
350
                        self.log("storage account:")
351-
- name: Create a VM with OS and multiple data storage accounts
351+
352-
  azure_rm_virtualmachine:
352+
353-
    resource_group: Testing
353+
354-
    name: testvm001
354+
355-
    vm_size: Standard_DS1_v2
355+
356-
    admin_username: adminUser
356+
357-
    ssh_password_enabled: false
357+
358-
    ssh_public_keys:
358+
359-
    - path: /home/adminUser/.ssh/authorized_keys
359+
360-
      key_data: < insert yor ssh public key here... >
360+
361-
    network_interfaces: testvm001
361+
362-
    storage_container: osdisk
362+
363-
    storage_blob: osdisk.vhd
363+
364-
    image:
364+
365-
      offer: CoreOS
365+
366-
      publisher: CoreOS
366+
367-
      sku: Stable
367+
368-
      version: latest
368+
369-
    data_disks:
369+
370-
    - lun: 0
370+
371-
      disk_size_gb: 64
371+
372-
      storage_container_name: datadisk1
372+
373-
      storage_blob_name: datadisk1.vhd
373+
374-
    - lun: 1
374+
375-
      disk_size_gb: 128
375+
376-
      storage_container_name: datadisk2
376+
377-
      storage_blob_name: datadisk2.vhd
377+
378
                                                        publisher=self.plan.get('publisher'),
379-
- name: Create a VM with a custom image
379+
380-
  azure_rm_virtualmachine:
380+
381-
    resource_group: Testing
381+
382-
    name: testvm001
382+
383-
    vm_size: Standard_DS1_v2
383+
384-
    admin_username: adminUser
384+
385-
    admin_password: password01
385+
386-
    image: customimage001
386+
387
                        ),
388-
- name: Create a VM with a custom image from a particular resource group
388+
389-
  azure_rm_virtualmachine:
389+
390-
    resource_group: Testing
390+
391-
    name: testvm001
391+
392-
    vm_size: Standard_DS1_v2
392+
393-
    admin_username: adminUser
393+
394-
    admin_password: password01
394+
395-
    image:
395+
396-
      name: customimage001
396+
397-
      resource_group: Testing
397+
398
                            ),
399-
- name: Power Off
399+
400-
  azure_rm_virtualmachine:
400+
401-
    resource_group: Testing
401+
402-
    name: testvm002
402+
403-
    started: no
403+
404
                        availability_set=availability_set_resource,
405-
- name: Deallocate
405+
406-
  azure_rm_virtualmachine:
406+
407-
    resource_group: Testing
407+
408-
    name: testvm002
408+
409-
    allocated: no
409+
410
411-
- name: Power On
411+
412-
  azure_rm_virtualmachine:
412+
413-
    resource_group:
413+
414-
    name: testvm002
414+
415
                    if self.os_type == 'Linux':
416-
- name: Restart
416+
417-
  azure_rm_virtualmachine:
417+
418-
    resource_group:
418+
419-
    name: testvm002
419+
420-
    restarted: yes
420+
421
                        ssh_config.public_keys = \
422-
- name: remove vm and all resources except public ips
422+
423-
  azure_rm_virtualmachine:
423+
424-
    resource_group: Testing
424+
425-
    name: testvm002
425+
426-
    state: absent
426+
427-
    remove_on_absent:
427+
428-
        - network_interfaces
428+
429-
        - virtual_storage
429+
430-
'''
430+
431
                            if not data_disk.get('managed_disk_type'):
432-
RETURN = '''
432+
433-
powerstate:
433+
434-
    description: Indicates if the state is running, stopped, deallocated
434+
435-
    returned: always
435+
436-
    type: string
436+
437-
    example: running
437+
438-
deleted_vhd_uris:
438+
439-
    description: List of deleted Virtual Hard Disk URIs.
439+
440-
    returned: 'on delete'
440+
441-
    type: list
441+
442-
    example: ["https://testvm104519.blob.core.windows.net/vhds/testvm10.vhd"]
442+
443-
deleted_network_interfaces:
443+
444-
    description: List of deleted NICs.
444+
445-
    returned: 'on delete'
445+
446-
    type: list
446+
447-
    example: ["testvm1001"]
447+
448-
deleted_public_ips:
448+
449-
    description: List of deleted public IP address names.
449+
450-
    returned: 'on delete'
450+
451-
    type: list
451+
452-
    example: ["testvm1001"]
452+
453-
azure_vm:
453+
454-
    description: Facts about the current state of the object. Note that facts are not part of the registered output but available directly.
454+
455-
    returned: always
455+
456-
    type: complex
456+
457-
    contains: {
457+
458-
        "properties": {
458+
459-
            "availabilitySet": {
459+
460-
                    "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Compute/availabilitySets/MYAVAILABILITYSET"
460+
461-
            },
461+
462-
            "hardwareProfile": {
462+
463-
                "vmSize": "Standard_D1"
463+
464-
            },
464+
465-
            "instanceView": {
465+
466-
                "disks": [
466+
467-
                    {
467+
468-
                        "name": "testvm10.vhd",
468+
469-
                        "statuses": [
469+
470-
                            {
470+
471-
                                "code": "ProvisioningState/succeeded",
471+
472-
                                "displayStatus": "Provisioning succeeded",
472+
473-
                                "level": "Info",
473+
474-
                                "time": "2016-03-30T07:11:16.187272Z"
474+
475-
                            }
475+
476-
                        ]
476+
477-
                    }
477+
478-
                ],
478+
479-
                "statuses": [
479+
480-
                    {
480+
481-
                        "code": "ProvisioningState/succeeded",
481+
482-
                        "displayStatus": "Provisioning succeeded",
482+
483-
                        "level": "Info",
483+
484-
                        "time": "2016-03-30T20:33:38.946916Z"
484+
485-
                    },
485+
486-
                    {
486+
487-
                        "code": "PowerState/running",
487+
488-
                        "displayStatus": "VM running",
488+
489-
                        "level": "Info"
489+
490-
                    }
490+
491-
                ],
491+
492-
                "vmAgent": {
492+
493-
                    "extensionHandlers": [],
493+
494-
                    "statuses": [
494+
495-
                        {
495+
496-
                            "code": "ProvisioningState/succeeded",
496+
497-
                            "displayStatus": "Ready",
497+
498-
                            "level": "Info",
498+
499-
                            "message": "GuestAgent is running and accepting new configurations.",
499+
500-
                            "time": "2016-03-30T20:31:16.000Z"
500+
501-
                        }
501+
502-
                    ],
502+
503-
                    "vmAgentVersion": "WALinuxAgent-2.0.16"
503+
504-
                }
504+
505-
            },
505+
506-
            "networkProfile": {
506+
507-
                "networkInterfaces": [
507+
508-
                    {
508+
509-
                        "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/networkInterfaces/testvm10_NIC01",
509+
510-
                        "name": "testvm10_NIC01",
510+
511-
                        "properties": {
511+
512-
                            "dnsSettings": {
512+
513-
                                "appliedDnsServers": [],
513+
514-
                                "dnsServers": []
514+
515-
                            },
515+
516-
                            "enableIPForwarding": false,
516+
517-
                            "ipConfigurations": [
517+
518-
                                {
518+
519-
                                    "etag": 'W/"041c8c2a-d5dd-4cd7-8465-9125cfbe2cf8"',
519+
520-
                                    "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/networkInterfaces/testvm10_NIC01/ipConfigurations/default",
520+
521-
                                    "name": "default",
521+
522-
                                    "properties": {
522+
523-
                                        "privateIPAddress": "10.10.0.5",
523+
524-
                                        "privateIPAllocationMethod": "Dynamic",
524+
525-
                                        "provisioningState": "Succeeded",
525+
526-
                                        "publicIPAddress": {
526+
527-
                                            "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/publicIPAddresses/testvm10_PIP01",
527+
528-
                                            "name": "testvm10_PIP01",
528+
529-
                                            "properties": {
529+
530-
                                                "idleTimeoutInMinutes": 4,
530+
531-
                                                "ipAddress": "13.92.246.197",
531+
532-
                                                "ipConfiguration": {
532+
533-
                                                    "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/networkInterfaces/testvm10_NIC01/ipConfigurations/default"
533+
534-
                                                },
534+
535-
                                                "provisioningState": "Succeeded",
535+
536-
                                                "publicIPAllocationMethod": "Static",
536+
537-
                                                "resourceGuid": "3447d987-ca0d-4eca-818b-5dddc0625b42"
537+
538-
                                            }
538+
539-
                                        }
539+
540-
                                    }
540+
541-
                                }
541+
542-
                            ],
542+
543-
                            "macAddress": "00-0D-3A-12-AA-14",
543+
544-
                            "primary": true,
544+
545-
                            "provisioningState": "Succeeded",
545+
546-
                            "resourceGuid": "10979e12-ccf9-42ee-9f6d-ff2cc63b3844",
546+
547-
                            "virtualMachine": {
547+
548-
                                "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Compute/virtualMachines/testvm10"
548+
549-
                            }
549+
550-
                        }
550+
551-
                    }
551+
552-
                ]
552+
553-
            },
553+
554-
            "osProfile": {
554+
555-
                "adminUsername": "chouseknecht",
555+
556-
                "computerName": "test10",
556+
557-
                "linuxConfiguration": {
557+
558-
                    "disablePasswordAuthentication": false
558+
559-
                },
559+
560-
                "secrets": []
560+
561-
            },
561+
562-
            "provisioningState": "Succeeded",
562+
563-
            "storageProfile": {
563+
564-
                "dataDisks": [
564+
565-
                    {
565+
566-
                        "caching": "ReadWrite",
566+
567-
                        "createOption": "empty",
567+
568-
                        "diskSizeGB": 64,
568+
569-
                        "lun": 0,
569+
570-
                        "name": "datadisk1.vhd",
570+
571-
                        "vhd": {
571+
572-
                            "uri": "https://testvm10sa1.blob.core.windows.net/datadisk/datadisk1.vhd"
572+
573-
                        }
573+
574-
                    }
574+
575-
                ],
575+
576-
                "imageReference": {
576+
577-
                    "offer": "CentOS",
577+
578-
                    "publisher": "OpenLogic",
578+
579-
                    "sku": "7.1",
579+
580-
                    "version": "7.1.20160308"
580+
581-
                },
581+
582-
                "osDisk": {
582+
583-
                    "caching": "ReadOnly",
583+
584-
                    "createOption": "fromImage",
584+
585-
                    "name": "testvm10.vhd",
585+
586-
                    "osType": "Linux",
586+
587-
                    "vhd": {
587+
588-
                        "uri": "https://testvm10sa1.blob.core.windows.net/vhds/testvm10.vhd"
588+
589-
                    }
589+
590-
                }
590+
591-
            }
591+
592-
        },
592+
593-
        "type": "Microsoft.Compute/virtualMachines"
593+
594-
    }
594+
595-
'''  # NOQA
595+
596
                        vm_resource.os_profile.linux_configuration = self.compute_models.LinuxConfiguration(
597
                            disable_password_authentication=linux_config.get('disablePasswordAuthentication', False)
598
                        )
599
                        if ssh_config:
600
                            public_keys = ssh_config.get('publicKeys')
601
                            if public_keys:
602
                                vm_resource.os_profile.linux_configuration.ssh = self.compute_models.SshConfiguration(public_keys=[])
603
                                for key in public_keys:
604
                                    vm_resource.os_profile.linux_configuration.ssh.public_keys.append(
605
                                        self.compute_models.SshPublicKey(path=key['path'], key_data=key['keyData'])
606
                                    )
607
608
                    # data disk
609-
    from __main__ import display
609+
610
                        data_disks = []
611-
    from ansible.utils.display import Display
611+
612-
    display = Display()
612+
613
                            if data_disk.get('managedDisk'):
614
                                managed_disk_type = data_disk['managedDisk']['storageAccountType']
615
                                data_disk_managed_disk = self.compute_models.ManagedDiskParameters(storage_account_type=managed_disk_type)
616
                                data_disk_vhd = None
617
                            else:
618
                                data_disk_vhd = data_disk['vhd']['uri']
619
                                data_disk_managed_disk = None
620
621
                            data_disks.append(self.compute_models.DataDisk(
622
                                lun=int(data_disk['lun']),
623
                                name=data_disk.get('name'),
624
                                vhd=data_disk_vhd,
625
                                caching=data_disk.get('caching'),
626
                                create_option=data_disk.get('createOption'),
627
                                disk_size_gb=int(data_disk['diskSizeGB']),
628
                                managed_disk=data_disk_managed_disk,
629
                            ))
630
                        vm_resource.storage_profile.data_disks = data_disks
631
632
                    self.log("Update virtual machine with parameters:")
633
                    self.create_or_update_vm(vm_resource)
634
635
                # Make sure we leave the machine in requested power state
636
                if (powerstate_change == 'poweron' and
637
                        self.results['ansible_facts']['azure_vm']['powerstate'] != 'running'):
638
                    # Attempt to power on the machine
639
                    self.power_on_vm()
640
641
                elif (powerstate_change == 'poweroff' and
642
                        self.results['ansible_facts']['azure_vm']['powerstate'] == 'running'):
643
                    # Attempt to power off the machine
644
                    self.power_off_vm()
645
646
                elif powerstate_change == 'restarted':
647
                    self.restart_vm()
648
649
                elif powerstate_change == 'deallocated':
650
                    self.deallocate_vm()
651
652
                self.results['ansible_facts']['azure_vm'] = self.serialize_vm(self.get_vm())
653
654
            elif self.state == 'absent':
655
                # delete the VM
656
                self.log("Delete virtual machine {0}".format(self.name))
657
                self.results['ansible_facts']['azure_vm'] = None
658
                self.delete_vm(vm)
659
660
        # until we sort out how we want to do this globally
661
        del self.results['actions']
662
663
        return self.results
664
665
    def get_vm(self):
666
        '''
667
        Get the VM with expanded instanceView
668
669
        :return: VirtualMachine object
670
        '''
671
        try:
672
            vm = self.compute_client.virtual_machines.get(self.resource_group, self.name, expand='instanceview')
673
            return vm
674
        except Exception as exc:
675
            self.fail("Error getting virtual machine {0} - {1}".format(self.name, str(exc)))
676
677
    def serialize_vm(self, vm):
678
        '''
679
        Convert a VirtualMachine object to dict.
680
681
        :param vm: VirtualMachine object
682
        :return: dict
683
        '''
684
685
        result = self.serialize_obj(vm, AZURE_OBJECT_CLASS, enum_modules=AZURE_ENUM_MODULES)
686
        result['id'] = vm.id
687
        result['name'] = vm.name
688
        result['type'] = vm.type
689
        result['location'] = vm.location
690
        result['tags'] = vm.tags
691
692
        result['powerstate'] = dict()
693
        if vm.instance_view:
694
            result['powerstate'] = next((s.code.replace('PowerState/', '')
695
                                         for s in vm.instance_view.statuses if s.code.startswith('PowerState')), None)
696
697
        # Expand network interfaces to include config properties
698
        for interface in vm.network_profile.network_interfaces:
699
            int_dict = azure_id_to_dict(interface.id)
700
            nic = self.get_network_interface(int_dict['networkInterfaces'])
701
            for interface_dict in result['properties']['networkProfile']['networkInterfaces']:
702
                if interface_dict['id'] == interface.id:
703
                    nic_dict = self.serialize_obj(nic, 'NetworkInterface')
704
                    interface_dict['name'] = int_dict['networkInterfaces']
705
                    interface_dict['properties'] = nic_dict['properties']
706
707
        # Expand public IPs to include config properties
708
        for interface in result['properties']['networkProfile']['networkInterfaces']:
709
            for config in interface['properties']['ipConfigurations']:
710
                if config['properties'].get('publicIPAddress'):
711
                    pipid_dict = azure_id_to_dict(config['properties']['publicIPAddress']['id'])
712
                    try:
713
                        pip = self.network_client.public_ip_addresses.get(self.resource_group,
714
                                                                          pipid_dict['publicIPAddresses'])
715
                    except Exception as exc:
716
                        self.fail("Error fetching public ip {0} - {1}".format(pipid_dict['publicIPAddresses'],
717
                                                                              str(exc)))
718
                    pip_dict = self.serialize_obj(pip, 'PublicIPAddress')
719
                    config['properties']['publicIPAddress']['name'] = pipid_dict['publicIPAddresses']
720-
        display = Display(self.module._verbosity)
720+
721
722
        self.log(result, pretty_print=True)
723
        if self.state != 'absent' and not result['powerstate']:
724
            self.fail("Failed to determine PowerState of virtual machine {0}".format(self.name))
725
        return result
726
727
    def power_off_vm(self):
728
        self.log("Powered off virtual machine {0}".format(self.name))
729
        self.results['actions'].append("Powered off virtual machine {0}".format(self.name))
730
        try:
731
            poller = self.compute_client.virtual_machines.power_off(self.resource_group, self.name)
732
            self.get_poller_result(poller)
733
        except Exception as exc:
734
            self.fail("Error powering off virtual machine {0} - {1}".format(self.name, str(exc)))
735
        return True
736
737
    def power_on_vm(self):
738
        self.results['actions'].append("Powered on virtual machine {0}".format(self.name))
739
        self.log("Power on virtual machine {0}".format(self.name))
740
        try:
741
            poller = self.compute_client.virtual_machines.start(self.resource_group, self.name)
742
            self.get_poller_result(poller)
743
        except Exception as exc:
744
            self.fail("Error powering on virtual machine {0} - {1}".format(self.name, str(exc)))
745
        return True
746
747
    def restart_vm(self):
748
        self.results['actions'].append("Restarted virtual machine {0}".format(self.name))
749
        self.log("Restart virtual machine {0}".format(self.name))
750
        try:
751
            poller = self.compute_client.virtual_machines.restart(self.resource_group, self.name)
752
            self.get_poller_result(poller)
753
        except Exception as exc:
754
            self.fail("Error restarting virtual machine {0} - {1}".format(self.name, str(exc)))
755
        return True
756
757
    def deallocate_vm(self):
758
        self.results['actions'].append("Deallocated virtual machine {0}".format(self.name))
759
        self.log("Deallocate virtual machine {0}".format(self.name))
760
        try:
761
            poller = self.compute_client.virtual_machines.deallocate(self.resource_group, self.name)
762
            self.get_poller_result(poller)
763
        except Exception as exc:
764
            self.fail("Error deallocating virtual machine {0} - {1}".format(self.name, str(exc)))
765
        return True
766
767
    def delete_vm(self, vm):
768
        vhd_uris = []
769
        managed_disk_ids = []
770
        nic_names = []
771
        pip_names = []
772
773
        if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
774
            # store the attached vhd info so we can nuke it after the VM is gone
775
            if(vm.storage_profile.os_disk.managed_disk):
776
                self.log('Storing managed disk ID for deletion')
777
                managed_disk_ids.append(vm.storage_profile.os_disk.managed_disk.id)
778
            elif(vm.storage_profile.os_disk.vhd):
779
                self.log('Storing VHD URI for deletion')
780
                vhd_uris.append(vm.storage_profile.os_disk.vhd.uri)
781
782
            data_disks = vm.storage_profile.data_disks
783
            for data_disk in data_disks:
784
                if(data_disk.vhd):
785
                    vhd_uris.append(data_disk.vhd.uri)
786
                elif(data_disk.managed_disk):
787
                    managed_disk_ids.append(data_disk.managed_disk.id)
788
789
            # FUTURE enable diff mode, move these there...
790
            self.log("VHD URIs to delete: {0}".format(', '.join(vhd_uris)))
791
            self.results['deleted_vhd_uris'] = vhd_uris
792
            self.log("Managed disk IDs to delete: {0}".format(', '.join(managed_disk_ids)))
793
            self.results['deleted_managed_disk_ids'] = managed_disk_ids
794
795
        if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
796
            # store the attached nic info so we can nuke them after the VM is gone
797
            self.log('Storing NIC names for deletion.')
798
            for interface in vm.network_profile.network_interfaces:
799
                id_dict = azure_id_to_dict(interface.id)
800
                nic_names.append(id_dict['networkInterfaces'])
801
            self.log('NIC names to delete {0}'.format(', '.join(nic_names)))
802
            self.results['deleted_network_interfaces'] = nic_names
803
            if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
804
                # also store each nic's attached public IPs and delete after the NIC is gone
805
                for name in nic_names:
806
                    nic = self.get_network_interface(name)
807
                    for ipc in nic.ip_configurations:
808
                        if ipc.public_ip_address:
809
                            pip_dict = azure_id_to_dict(ipc.public_ip_address.id)
810
                            pip_names.append(pip_dict['publicIPAddresses'])
811
                self.log('Public IPs to  delete are {0}'.format(', '.join(pip_names)))
812
                self.results['deleted_public_ips'] = pip_names
813
814
        self.log("Deleting virtual machine {0}".format(self.name))
815
        self.results['actions'].append("Deleted virtual machine {0}".format(self.name))
816
        try:
817
            poller = self.compute_client.virtual_machines.delete(self.resource_group, self.name)
818
            # wait for the poller to finish
819
            self.get_poller_result(poller)
820
        except Exception as exc:
821
            self.fail("Error deleting virtual machine {0} - {1}".format(self.name, str(exc)))
822
823
        # TODO: parallelize nic, vhd, and public ip deletions with begin_deleting
824
        # TODO: best-effort to keep deleting other linked resources if we encounter an error
825
        if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
826
            self.log('Deleting VHDs')
827
            self.delete_vm_storage(vhd_uris)
828
            self.log('Deleting managed disks')
829
            self.delete_managed_disks(managed_disk_ids)
830
831
        if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
832
            self.log('Deleting network interfaces')
833
            for name in nic_names:
834
                self.delete_nic(name)
835
836
        if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
837
            self.log('Deleting public IPs')
838
            for name in pip_names:
839
                self.delete_pip(name)
840
        return True
841
842
    def get_network_interface(self, name):
843
        try:
844
            nic = self.network_client.network_interfaces.get(self.resource_group, name)
845
            return nic
846
        except Exception as exc:
847
            self.fail("Error fetching network interface {0} - {1}".format(name, str(exc)))
848
849
    def delete_nic(self, name):
850
        self.log("Deleting network interface {0}".format(name))
851
        self.results['actions'].append("Deleted network interface {0}".format(name))
852
        try:
853
            poller = self.network_client.network_interfaces.delete(self.resource_group, name)
854
        except Exception as exc:
855
            self.fail("Error deleting network interface {0} - {1}".format(name, str(exc)))
856
        self.get_poller_result(poller)
857
        # Delete doesn't return anything. If we get this far, assume success
858
        return True
859
860
    def delete_pip(self, name):
861
        self.results['actions'].append("Deleted public IP {0}".format(name))
862
        try:
863
            poller = self.network_client.public_ip_addresses.delete(self.resource_group, name)
864
            self.get_poller_result(poller)
865
        except Exception as exc:
866
            self.fail("Error deleting {0} - {1}".format(name, str(exc)))
867
        # Delete returns nada. If we get here, assume that all is well.
868
        return True
869
870
    def delete_managed_disks(self, managed_disk_ids):
871
        for mdi in managed_disk_ids:
872
            try:
873
                poller = self.rm_client.resources.delete_by_id(mdi, '2017-03-30')
874
                self.get_poller_result(poller)
875
            except Exception as exc:
876
                self.fail("Error deleting managed disk {0} - {1}".format(mdi, str(exc)))
877
878
    def delete_vm_storage(self, vhd_uris):
879
        # FUTURE: figure out a cloud_env indepdendent way to delete these
880
        for uri in vhd_uris:
881
            self.log("Extracting info from blob uri '{0}'".format(uri))
882
            try:
883
                blob_parts = extract_names_from_blob_uri(uri, self._cloud_environment.suffixes.storage_endpoint)
884
            except Exception as exc:
885
                self.fail("Error parsing blob URI {0}".format(str(exc)))
886
            storage_account_name = blob_parts['accountname']
887
            container_name = blob_parts['containername']
888
            blob_name = blob_parts['blobname']
889
890
            blob_client = self.get_blob_client(self.resource_group, storage_account_name)
891
892
            self.log("Delete blob {0}:{1}".format(container_name, blob_name))
893
            self.results['actions'].append("Deleted blob {0}:{1}".format(container_name, blob_name))
894
            try:
895
                blob_client.delete_blob(container_name, blob_name)
896
            except Exception as exc:
897
                self.fail("Error deleting blob {0}:{1} - {2}".format(container_name, blob_name, str(exc)))
898
899
    def get_marketplace_image_version(self):
900
        try:
901
            versions = self.compute_client.virtual_machine_images.list(self.location,
902
                                                                       self.image['publisher'],
903
                                                                       self.image['offer'],
904
                                                                       self.image['sku'])
905
        except Exception as exc:
906
            self.fail("Error fetching image {0} {1} {2} - {3}".format(self.image['publisher'],
907
                                                                      self.image['offer'],
908
                                                                      self.image['sku'],
909
                                                                      str(exc)))
910
        if versions and len(versions) > 0:
911
            if self.image['version'] == 'latest':
912
                return versions[len(versions) - 1]
913
            for version in versions:
914
                if version.name == self.image['version']:
915
                    return version
916
917
        self.fail("Error could not find image {0} {1} {2} {3}".format(self.image['publisher'],
918
                                                                      self.image['offer'],
919
                                                                      self.image['sku'],
920
                                                                      self.image['version']))
921
922
    def get_custom_image_reference(self, name, resource_group=None):
923
        try:
924
            if resource_group:
925
                vm_images = self.compute_client.images.list_by_resource_group(resource_group)
926
            else:
927
                vm_images = self.compute_client.images.list()
928
        except Exception as exc:
929
            self.fail("Error fetching custom images from subscription - {0}".format(str(exc)))
930
931
        for vm_image in vm_images:
932
            if vm_image.name == name:
933
                self.log("Using custom image id {0}".format(vm_image.id))
934
                return self.compute_models.ImageReference(id=vm_image.id)
935
936
        self.fail("Error could not find image with name {0}".format(name))
937
938
    def get_availability_set(self, resource_group, name):
939
        try:
940
            return self.compute_client.availability_sets.get(resource_group, name)
941
        except Exception as exc:
942
            self.fail("Error fetching availability set {0} - {1}".format(name, str(exc)))
943
944
    def get_storage_account(self, name):
945
        try:
946
            account = self.storage_client.storage_accounts.get_properties(self.resource_group,
947
                                                                          name)
948
            return account
949
        except Exception as exc:
950
            self.fail("Error fetching storage account {0} - {1}".format(name, str(exc)))
951
952
    def create_or_update_vm(self, params):
953
        try:
954
            poller = self.compute_client.virtual_machines.create_or_update(self.resource_group, self.name, params)
955
            self.get_poller_result(poller)
956
        except Exception as exc:
957
            self.fail("Error creating or updating virtual machine {0} - {1}".format(self.name, str(exc)))
958
959
    def vm_size_is_valid(self):
960
        '''
961
        Validate self.vm_size against the list of virtual machine sizes available for the account and location.
962
963
        :return: boolean
964
        '''
965
        try:
966
            sizes = self.compute_client.virtual_machine_sizes.list(self.location)
967
        except Exception as exc:
968
            self.fail("Error retrieving available machine sizes - {0}".format(str(exc)))
969
        for size in sizes:
970
            if size.name == self.vm_size:
971
                return True
972
        return False
973
974
    def create_default_storage_account(self):
975
        '''
976
        Create a default storage account <vm name>XXXX, where XXXX is a random number. If <vm name>XXXX exists, use it.
977
        Otherwise, create one.
978
979
        :return: storage account object
980
        '''
981
        account = None
982
        valid_name = False
983
984
        # Attempt to find a valid storage account name
985
        storage_account_name_base = re.sub('[^a-zA-Z0-9]', '', self.name[:20].lower())
986
        for i in range(0, 5):
987
            rand = random.randrange(1000, 9999)
988
            storage_account_name = storage_account_name_base + str(rand)
989
            if self.check_storage_account_name(storage_account_name):
990
                valid_name = True
991
                break
992
993
        if not valid_name:
994
            self.fail("Failed to create a unique storage account name for {0}. Try using a different VM name."
995
                      .format(self.name))
996
997
        try:
998
            account = self.storage_client.storage_accounts.get_properties(self.resource_group, storage_account_name)
999
        except CloudError:
1000
            pass
1001
1002
        if account:
1003
            self.log("Storage account {0} found.".format(storage_account_name))
1004
            self.check_provisioning_state(account)
1005
            return account
1006
        sku = self.storage_models.Sku(self.storage_models.SkuName.standard_lrs)
1007
        sku.tier = self.storage_models.SkuTier.standard
1008
        kind = self.storage_models.Kind.storage
1009
        parameters = self.storage_models.StorageAccountCreateParameters(sku, kind, self.location)
1010
        self.log("Creating storage account {0} in location {1}".format(storage_account_name, self.location))
1011
        self.results['actions'].append("Created storage account {0}".format(storage_account_name))
1012
        try:
1013
            poller = self.storage_client.storage_accounts.create(self.resource_group, storage_account_name, parameters)
1014
            self.get_poller_result(poller)
1015
        except Exception as exc:
1016
            self.fail("Failed to create storage account: {0} - {1}".format(storage_account_name, str(exc)))
1017
        return self.get_storage_account(storage_account_name)
1018
1019
    def check_storage_account_name(self, name):
1020
        self.log("Checking storage account name availability for {0}".format(name))
1021
        try:
1022
            response = self.storage_client.storage_accounts.check_name_availability(name)
1023
            if response.reason == 'AccountNameInvalid':
1024
                raise Exception("Invalid default storage account name: {0}".format(name))
1025
        except Exception as exc:
1026
            self.fail("Error checking storage account name availability for {0} - {1}".format(name, str(exc)))
1027
1028
        return response.name_available
1029
1030
    def create_default_nic(self):
1031
        '''
1032
        Create a default Network Interface <vm name>01. Requires an existing virtual network
1033
        with one subnet. If NIC <vm name>01 exists, use it. Otherwise, create one.
1034
1035
        :return: NIC object
1036
        '''
1037
1038
        network_interface_name = self.name + '01'
1039
        nic = None
1040
1041
        self.log("Create default NIC {0}".format(network_interface_name))
1042
        self.log("Check to see if NIC {0} exists".format(network_interface_name))
1043
        try:
1044
            nic = self.network_client.network_interfaces.get(self.resource_group, network_interface_name)
1045
        except CloudError:
1046
            pass
1047
1048
        if nic:
1049
            self.log("NIC {0} found.".format(network_interface_name))
1050
            self.check_provisioning_state(nic)
1051
            return nic
1052
1053
        self.log("NIC {0} does not exist.".format(network_interface_name))
1054
1055
        virtual_network_resource_group = None
1056
        if self.virtual_network_resource_group:
1057
            virtual_network_resource_group = self.virtual_network_resource_group
1058
        else:
1059
            virtual_network_resource_group = self.resource_group
1060
1061
        if self.virtual_network_name:
1062
            try:
1063
                self.network_client.virtual_networks.list(virtual_network_resource_group, self.virtual_network_name)
1064
                virtual_network_name = self.virtual_network_name
1065
            except CloudError as exc:
1066
                self.fail("Error: fetching virtual network {0} - {1}".format(self.virtual_network_name, str(exc)))
1067
1068
        else:
1069
            # Find a virtual network
1070
            no_vnets_msg = "Error: unable to find virtual network in resource group {0}. A virtual network " \
1071
                           "with at least one subnet must exist in order to create a NIC for the virtual " \
1072
                           "machine.".format(virtual_network_resource_group)
1073
1074
            virtual_network_name = None
1075
            try:
1076
                vnets = self.network_client.virtual_networks.list(virtual_network_resource_group)
1077
            except CloudError:
1078
                self.log('cloud error!')
1079
                self.fail(no_vnets_msg)
1080
1081
            for vnet in vnets:
1082
                virtual_network_name = vnet.name
1083
                self.log('vnet name: {0}'.format(vnet.name))
1084
                break
1085
1086
            if not virtual_network_name:
1087
                self.fail(no_vnets_msg)
1088
1089
        if self.subnet_name:
1090
            try:
1091
                subnet = self.network_client.subnets.get(virtual_network_resource_group, virtual_network_name, self.subnet_name)
1092
                subnet_id = subnet.id
1093
            except Exception as exc:
1094
                self.fail("Error: fetching subnet {0} - {1}".format(self.subnet_name, str(exc)))
1095
        else:
1096
            no_subnets_msg = "Error: unable to find a subnet in virtual network {0}. A virtual network " \
1097
                             "with at least one subnet must exist in order to create a NIC for the virtual " \
1098
                             "machine.".format(virtual_network_name)
1099
1100
            subnet_id = None
1101
            try:
1102
                subnets = self.network_client.subnets.list(virtual_network_resource_group, virtual_network_name)
1103
            except CloudError:
1104
                self.fail(no_subnets_msg)
1105
1106
            for subnet in subnets:
1107
                subnet_id = subnet.id
1108
                self.log('subnet id: {0}'.format(subnet_id))
1109
                break
1110
1111
            if not subnet_id:
1112
                self.fail(no_subnets_msg)
1113
1114
        pip = None
1115
        if self.public_ip_allocation_method != 'Disabled':
1116
            self.results['actions'].append('Created default public IP {0}'.format(self.name + '01'))
1117
            pip_info = self.create_default_pip(self.resource_group, self.location, self.name + '01', self.public_ip_allocation_method)
1118
            pip = self.network_models.PublicIPAddress(id=pip_info.id, location=pip_info.location, resource_guid=pip_info.resource_guid)
1119
1120
        self.results['actions'].append('Created default security group {0}'.format(self.name + '01'))
1121
        group = self.create_default_securitygroup(self.resource_group, self.location, self.name + '01', self.os_type,
1122
                                                  self.open_ports)
1123
1124
        parameters = self.network_models.NetworkInterface(
1125
            location=self.location,
1126
            ip_configurations=[
1127
                self.network_models.NetworkInterfaceIPConfiguration(
1128
                    private_ip_allocation_method='Dynamic',
1129
                )
1130
            ]
1131
        )
1132
        parameters.ip_configurations[0].subnet = self.network_models.Subnet(id=subnet_id)
1133
        parameters.ip_configurations[0].name = 'default'
1134
        parameters.network_security_group = self.network_models.NetworkSecurityGroup(id=group.id,
1135
                                                                                     location=group.location,
1136
                                                                                     resource_guid=group.resource_guid)
1137
        parameters.ip_configurations[0].public_ip_address = pip
1138
1139
        self.log("Creating NIC {0}".format(network_interface_name))
1140
        self.log(self.serialize_obj(parameters, 'NetworkInterface'), pretty_print=True)
1141
        self.results['actions'].append("Created NIC {0}".format(network_interface_name))
1142
        try:
1143
            poller = self.network_client.network_interfaces.create_or_update(self.resource_group,
1144
                                                                             network_interface_name,
1145
                                                                             parameters)
1146
            new_nic = self.get_poller_result(poller)
1147
        except Exception as exc:
1148
            self.fail("Error creating network interface {0} - {1}".format(network_interface_name, str(exc)))
1149
        return new_nic
1150
1151
1152
def main():
1153
    AzureRMVirtualMachine()
1154
1155
1156
if __name__ == '__main__':
1157
    main()