Guest User

Untitled

a guest
Apr 17th, 2018
302
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 81.60 KB | None | 0 0
  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.  
  17. DOCUMENTATION = '''
  18. ---
  19. module: azure_rm_virtualmachine
  20.  
  21. version_added: "2.1"
  22.  
  23. short_description: Manage Azure virtual machines.
  24.  
  25. description:
  26. - Create, update, stop and start a virtual machine. Provide an existing storage account and network interface or
  27. allow the module to create these for you. If you choose not to provide a network interface, the resource group
  28. must contain a virtual network with at least one subnet.
  29. - Before Ansible 2.5, this required an image found in the Azure Marketplace which can be discovered with
  30. M(azure_rm_virtualmachineimage_facts). In Ansible 2.5 and newer, custom images can be used as well, see the
  31. examples for more details.
  32.  
  33. options:
  34. resource_group:
  35. description:
  36. - Name of the resource group containing the virtual machine.
  37. required: true
  38. name:
  39. description:
  40. - Name of the virtual machine.
  41. required: true
  42. custom_data:
  43. description:
  44. - Data which is made available to the virtual machine and used by e.g., cloud-init.
  45. version_added: "2.5"
  46. state:
  47. description:
  48. - Assert the state of the virtual machine.
  49. - State 'present' will check that the machine exists with the requested configuration. If the configuration
  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
  51. state.
  52. - State 'absent' will remove the virtual machine.
  53. default: present
  54. choices:
  55. - absent
  56. - present
  57. started:
  58. description:
  59. - Use with state 'present' to start the machine. Set to false to have the machine be 'stopped'.
  60. default: true
  61. allocated:
  62. description:
  63. - Toggle that controls if the machine is allocated/deallocated, only useful with state='present'.
  64. default: True
  65. restarted:
  66. description:
  67. - Use with state 'present' to restart a running VM.
  68. location:
  69. description:
  70. - Valid Azure location. Defaults to location of the resource group.
  71. short_hostname:
  72. description:
  73. - Name assigned internally to the host. On a linux VM this is the name returned by the `hostname` command.
  74. When creating a virtual machine, short_hostname defaults to name.
  75. vm_size:
  76. description:
  77. - A valid Azure VM size value. For example, 'Standard_D4'. The list of choices varies depending on the
  78. subscription and location. Check your subscription for available choices. Required when creating a VM.
  79. admin_username:
  80. description:
  81. - Admin username used to access the host after it is created. Required when creating a VM.
  82. admin_password:
  83. description:
  84. - Password for the admin username. Not required if the os_type is Linux and SSH password authentication
  85. is disabled by setting ssh_password_enabled to false.
  86. ssh_password_enabled:
  87. description:
  88. - When the os_type is Linux, setting ssh_password_enabled to false will disable SSH password authentication
  89. and require use of SSH keys.
  90. default: true
  91. ssh_public_keys:
  92. description:
  93. - "For os_type Linux provide a list of SSH keys. Each item in the list should be a dictionary where the
  94. dictionary contains two keys: path and key_data. Set the path to the default location of the
  95. authorized_keys files. On an Enterprise Linux host, for example, the path will be
  96. /home/<admin username>/.ssh/authorized_keys. Set key_data to the actual value of the public key."
  97. image:
  98. description:
  99. - Specifies the image used to build the VM.
  100. - If a string, the image is sourced from a custom image based on the
  101. name.
  102. - 'If a dict with the keys C(publisher), C(offer), C(sku), and
  103. C(version), the image is sourced from a Marketplace image. NOTE:
  104. set image.version to C(latest) to get the most recent version of a
  105. given image.'
  106. - 'If a dict with the keys C(name) and C(resource_group), the image
  107. is sourced from a custom image based on the C(name) and
  108. C(resource_group) set. NOTE: the key C(resource_group) is optional
  109. and if omitted, all images in the subscription will be searched
  110. for by C(name).'
  111. - Custom image support was added in Ansible 2.5
  112. required: true
  113. availability_set:
  114. description:
  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.
  116. version_added: "2.5"
  117. storage_account_name:
  118. description:
  119. - Name of an existing storage account that supports creation of VHD blobs. If not specified for a new VM,
  120. a new storage account named <vm name>01 will be created using storage type 'Standard_LRS'.
  121. storage_container_name:
  122. description:
  123. - Name of the container to use within the storage account to store VHD blobs. If no name is specified a
  124. default container will created.
  125. default: vhds
  126. storage_blob_name:
  127. description:
  128. - Name fo the storage blob used to hold the VM's OS disk image. If no name is provided, defaults to
  129. the VM name + '.vhd'. If you provide a name, it must end with '.vhd'
  130. aliases:
  131. - storage_blob
  132. managed_disk_type:
  133. description:
  134. - Managed OS disk type
  135. choices:
  136. - Standard_LRS
  137. - Premium_LRS
  138. version_added: "2.4"
  139. os_disk_caching:
  140. description:
  141. - Type of OS disk caching.
  142. choices:
  143. - ReadOnly
  144. - ReadWrite
  145. default: ReadOnly
  146. aliases:
  147. - disk_caching
  148. os_type:
  149. description:
  150. - Base type of operating system.
  151. choices:
  152. - Windows
  153. - Linux
  154. default:
  155. - Linux
  156. data_disks:
  157. description:
  158. - Describes list of data disks.
  159. version_added: "2.4"
  160. suboptions:
  161. lun:
  162. description:
  163. - The logical unit number for data disk
  164. default: 0
  165. version_added: "2.4"
  166. disk_size_gb:
  167. description:
  168. - The initial disk size in GB for blank data disks
  169. version_added: "2.4"
  170. managed_disk_type:
  171. description:
  172. - Managed data disk type
  173. choices:
  174. - Standard_LRS
  175. - Premium_LRS
  176. version_added: "2.4"
  177. storage_account_name:
  178. description:
  179. - Name of an existing storage account that supports creation of VHD blobs. If not specified for a new VM,
  180. a new storage account named <vm name>01 will be created using storage type 'Standard_LRS'.
  181. version_added: "2.4"
  182. storage_container_name:
  183. description:
  184. - Name of the container to use within the storage account to store VHD blobs. If no name is specified a
  185. default container will created.
  186. default: vhds
  187. version_added: "2.4"
  188. storage_blob_name:
  189. description:
  190. - Name fo the storage blob used to hold the VM's OS disk image. If no name is provided, defaults to
  191. the VM name + '.vhd'. If you provide a name, it must end with '.vhd'
  192. version_added: "2.4"
  193. caching:
  194. description:
  195. - Type of data disk caching.
  196. choices:
  197. - ReadOnly
  198. - ReadWrite
  199. default: ReadOnly
  200. version_added: "2.4"
  201. public_ip_allocation_method:
  202. description:
  203. - If a public IP address is created when creating the VM (because a Network Interface was not provided),
  204. determines if the public IP address remains permanently associated with the Network Interface. If set
  205. to 'Dynamic' the public IP address may change any time the VM is rebooted or power cycled.
  206. - The C(Disabled) choice was added in Ansible 2.6.
  207. choices:
  208. - Dynamic
  209. - Static
  210. - Disabled
  211. default:
  212. - Static
  213. aliases:
  214. - public_ip_allocation
  215. open_ports:
  216. description:
  217. - If a network interface is created when creating the VM, a security group will be created as well. For
  218. Linux hosts a rule will be added to the security group allowing inbound TCP connections to the default
  219. SSH port 22, and for Windows hosts ports 3389 and 5986 will be opened. Override the default open ports by
  220. providing a list of ports.
  221. network_interface_names:
  222. description:
  223. - List of existing network interface names to add to the VM. If a network interface name is not provided
  224. when the VM is created, a default network interface will be created. In order for the module to create
  225. a network interface, at least one Virtual Network with one Subnet must exist.
  226. virtual_network_resource_group:
  227. description:
  228. - When creating a virtual machine, if a specific virtual network from another resource group should be
  229. used, use this parameter to specify the resource group to use.
  230. version_added: "2.4"
  231. virtual_network_name:
  232. description:
  233. - When creating a virtual machine, if a network interface name is not provided, one will be created.
  234. The new network interface will be assigned to the first virtual network found in the resource group.
  235. Use this parameter to provide a specific virtual network instead.
  236. aliases:
  237. - virtual_network
  238. subnet_name:
  239. description:
  240. - When creating a virtual machine, if a network interface name is not provided, one will be created.
  241. The new network interface will be assigned to the first subnet found in the virtual network.
  242. Use this parameter to provide a specific subnet instead.
  243. aliases:
  244. - subnet
  245. remove_on_absent:
  246. description:
  247. - When removing a VM using state 'absent', also remove associated resources
  248. - "It can be 'all' or a list with any of the following: ['network_interfaces', 'virtual_storage', 'public_ips']"
  249. - Any other input will be ignored
  250. default: ['all']
  251. plan:
  252. description:
  253. - A dictionary describing a third-party billing plan for an instance
  254. version_added: 2.5
  255. suboptions:
  256. name:
  257. description:
  258. - billing plan name
  259. required: true
  260. product:
  261. description:
  262. - product name
  263. required: true
  264. publisher:
  265. description:
  266. - publisher offering the plan
  267. required: true
  268. promotion_code:
  269. description:
  270. - optional promotion code
  271.  
  272. extends_documentation_fragment:
  273. - azure
  274. - azure_tags
  275.  
  276. author:
  277. - "Chris Houseknecht (@chouseknecht)"
  278. - "Matt Davis (@nitzmahone)"
  279.  
  280. '''
  281. EXAMPLES = '''
  282.  
  283. - name: Create VM with defaults
  284. azure_rm_virtualmachine:
  285. resource_group: Testing
  286. name: testvm10
  287. admin_username: chouseknecht
  288. admin_password: <your password here>
  289. image:
  290. offer: CentOS
  291. publisher: OpenLogic
  292. sku: '7.1'
  293. version: latest
  294.  
  295. - name: Create a VM with managed disk
  296. azure_rm_virtualmachine:
  297. resource_group: Testing
  298. name: testvm001
  299. vm_size: Standard_D4
  300. managed_disk_type: Standard_LRS
  301. admin_username: adminUser
  302. ssh_public_keys:
  303. - path: /home/adminUser/.ssh/authorized_keys
  304. key_data: < insert yor ssh public key here... >
  305. image:
  306. offer: CoreOS
  307. publisher: CoreOS
  308. sku: Stable
  309. version: latest
  310.  
  311. - name: Create a VM with existing storage account and NIC
  312. azure_rm_virtualmachine:
  313. resource_group: Testing
  314. name: testvm002
  315. vm_size: Standard_D4
  316. storage_account: testaccount001
  317. admin_username: adminUser
  318. ssh_public_keys:
  319. - path: /home/adminUser/.ssh/authorized_keys
  320. key_data: < insert yor ssh public key here... >
  321. network_interfaces: testvm001
  322. image:
  323. offer: CentOS
  324. publisher: OpenLogic
  325. sku: '7.1'
  326. version: latest
  327.  
  328. - name: Create a VM with OS and multiple data managed disks
  329. azure_rm_virtualmachine:
  330. resource_group: Testing
  331. name: testvm001
  332. vm_size: Standard_D4
  333. managed_disk_type: Standard_LRS
  334. admin_username: adminUser
  335. ssh_public_keys:
  336. - path: /home/adminUser/.ssh/authorized_keys
  337. key_data: < insert yor ssh public key here... >
  338. image:
  339. offer: CoreOS
  340. publisher: CoreOS
  341. sku: Stable
  342. version: latest
  343. data_disks:
  344. - lun: 0
  345. disk_size_gb: 64
  346. managed_disk_type: Standard_LRS
  347. - lun: 1
  348. disk_size_gb: 128
  349. managed_disk_type: Premium_LRS
  350.  
  351. - name: Create a VM with OS and multiple data storage accounts
  352. azure_rm_virtualmachine:
  353. resource_group: Testing
  354. name: testvm001
  355. vm_size: Standard_DS1_v2
  356. admin_username: adminUser
  357. ssh_password_enabled: false
  358. ssh_public_keys:
  359. - path: /home/adminUser/.ssh/authorized_keys
  360. key_data: < insert yor ssh public key here... >
  361. network_interfaces: testvm001
  362. storage_container: osdisk
  363. storage_blob: osdisk.vhd
  364. image:
  365. offer: CoreOS
  366. publisher: CoreOS
  367. sku: Stable
  368. version: latest
  369. data_disks:
  370. - lun: 0
  371. disk_size_gb: 64
  372. storage_container_name: datadisk1
  373. storage_blob_name: datadisk1.vhd
  374. - lun: 1
  375. disk_size_gb: 128
  376. storage_container_name: datadisk2
  377. storage_blob_name: datadisk2.vhd
  378.  
  379. - name: Create a VM with a custom image
  380. azure_rm_virtualmachine:
  381. resource_group: Testing
  382. name: testvm001
  383. vm_size: Standard_DS1_v2
  384. admin_username: adminUser
  385. admin_password: password01
  386. image: customimage001
  387.  
  388. - name: Create a VM with a custom image from a particular resource group
  389. azure_rm_virtualmachine:
  390. resource_group: Testing
  391. name: testvm001
  392. vm_size: Standard_DS1_v2
  393. admin_username: adminUser
  394. admin_password: password01
  395. image:
  396. name: customimage001
  397. resource_group: Testing
  398.  
  399. - name: Power Off
  400. azure_rm_virtualmachine:
  401. resource_group: Testing
  402. name: testvm002
  403. started: no
  404.  
  405. - name: Deallocate
  406. azure_rm_virtualmachine:
  407. resource_group: Testing
  408. name: testvm002
  409. allocated: no
  410.  
  411. - name: Power On
  412. azure_rm_virtualmachine:
  413. resource_group:
  414. name: testvm002
  415.  
  416. - name: Restart
  417. azure_rm_virtualmachine:
  418. resource_group:
  419. name: testvm002
  420. restarted: yes
  421.  
  422. - name: remove vm and all resources except public ips
  423. azure_rm_virtualmachine:
  424. resource_group: Testing
  425. name: testvm002
  426. state: absent
  427. remove_on_absent:
  428. - network_interfaces
  429. - virtual_storage
  430. '''
  431.  
  432. RETURN = '''
  433. powerstate:
  434. description: Indicates if the state is running, stopped, deallocated
  435. returned: always
  436. type: string
  437. example: running
  438. deleted_vhd_uris:
  439. description: List of deleted Virtual Hard Disk URIs.
  440. returned: 'on delete'
  441. type: list
  442. example: ["https://testvm104519.blob.core.windows.net/vhds/testvm10.vhd"]
  443. deleted_network_interfaces:
  444. description: List of deleted NICs.
  445. returned: 'on delete'
  446. type: list
  447. example: ["testvm1001"]
  448. deleted_public_ips:
  449. description: List of deleted public IP address names.
  450. returned: 'on delete'
  451. type: list
  452. example: ["testvm1001"]
  453. azure_vm:
  454. description: Facts about the current state of the object. Note that facts are not part of the registered output but available directly.
  455. returned: always
  456. type: complex
  457. contains: {
  458. "properties": {
  459. "availabilitySet": {
  460. "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Compute/availabilitySets/MYAVAILABILITYSET"
  461. },
  462. "hardwareProfile": {
  463. "vmSize": "Standard_D1"
  464. },
  465. "instanceView": {
  466. "disks": [
  467. {
  468. "name": "testvm10.vhd",
  469. "statuses": [
  470. {
  471. "code": "ProvisioningState/succeeded",
  472. "displayStatus": "Provisioning succeeded",
  473. "level": "Info",
  474. "time": "2016-03-30T07:11:16.187272Z"
  475. }
  476. ]
  477. }
  478. ],
  479. "statuses": [
  480. {
  481. "code": "ProvisioningState/succeeded",
  482. "displayStatus": "Provisioning succeeded",
  483. "level": "Info",
  484. "time": "2016-03-30T20:33:38.946916Z"
  485. },
  486. {
  487. "code": "PowerState/running",
  488. "displayStatus": "VM running",
  489. "level": "Info"
  490. }
  491. ],
  492. "vmAgent": {
  493. "extensionHandlers": [],
  494. "statuses": [
  495. {
  496. "code": "ProvisioningState/succeeded",
  497. "displayStatus": "Ready",
  498. "level": "Info",
  499. "message": "GuestAgent is running and accepting new configurations.",
  500. "time": "2016-03-30T20:31:16.000Z"
  501. }
  502. ],
  503. "vmAgentVersion": "WALinuxAgent-2.0.16"
  504. }
  505. },
  506. "networkProfile": {
  507. "networkInterfaces": [
  508. {
  509. "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/networkInterfaces/testvm10_NIC01",
  510. "name": "testvm10_NIC01",
  511. "properties": {
  512. "dnsSettings": {
  513. "appliedDnsServers": [],
  514. "dnsServers": []
  515. },
  516. "enableIPForwarding": false,
  517. "ipConfigurations": [
  518. {
  519. "etag": 'W/"041c8c2a-d5dd-4cd7-8465-9125cfbe2cf8"',
  520. "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/networkInterfaces/testvm10_NIC01/ipConfigurations/default",
  521. "name": "default",
  522. "properties": {
  523. "privateIPAddress": "10.10.0.5",
  524. "privateIPAllocationMethod": "Dynamic",
  525. "provisioningState": "Succeeded",
  526. "publicIPAddress": {
  527. "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/publicIPAddresses/testvm10_PIP01",
  528. "name": "testvm10_PIP01",
  529. "properties": {
  530. "idleTimeoutInMinutes": 4,
  531. "ipAddress": "13.92.246.197",
  532. "ipConfiguration": {
  533. "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/networkInterfaces/testvm10_NIC01/ipConfigurations/default"
  534. },
  535. "provisioningState": "Succeeded",
  536. "publicIPAllocationMethod": "Static",
  537. "resourceGuid": "3447d987-ca0d-4eca-818b-5dddc0625b42"
  538. }
  539. }
  540. }
  541. }
  542. ],
  543. "macAddress": "00-0D-3A-12-AA-14",
  544. "primary": true,
  545. "provisioningState": "Succeeded",
  546. "resourceGuid": "10979e12-ccf9-42ee-9f6d-ff2cc63b3844",
  547. "virtualMachine": {
  548. "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Compute/virtualMachines/testvm10"
  549. }
  550. }
  551. }
  552. ]
  553. },
  554. "osProfile": {
  555. "adminUsername": "chouseknecht",
  556. "computerName": "test10",
  557. "linuxConfiguration": {
  558. "disablePasswordAuthentication": false
  559. },
  560. "secrets": []
  561. },
  562. "provisioningState": "Succeeded",
  563. "storageProfile": {
  564. "dataDisks": [
  565. {
  566. "caching": "ReadWrite",
  567. "createOption": "empty",
  568. "diskSizeGB": 64,
  569. "lun": 0,
  570. "name": "datadisk1.vhd",
  571. "vhd": {
  572. "uri": "https://testvm10sa1.blob.core.windows.net/datadisk/datadisk1.vhd"
  573. }
  574. }
  575. ],
  576. "imageReference": {
  577. "offer": "CentOS",
  578. "publisher": "OpenLogic",
  579. "sku": "7.1",
  580. "version": "7.1.20160308"
  581. },
  582. "osDisk": {
  583. "caching": "ReadOnly",
  584. "createOption": "fromImage",
  585. "name": "testvm10.vhd",
  586. "osType": "Linux",
  587. "vhd": {
  588. "uri": "https://testvm10sa1.blob.core.windows.net/vhds/testvm10.vhd"
  589. }
  590. }
  591. }
  592. },
  593. "type": "Microsoft.Compute/virtualMachines"
  594. }
  595. ''' # NOQA
  596.  
  597. import base64
  598. import random
  599. import re
  600.  
  601. try:
  602. from msrestazure.azure_exceptions import CloudError
  603. from msrestazure.tools import parse_resource_id
  604. except ImportError:
  605. # This is handled in azure_rm_common
  606. pass
  607.  
  608. try:
  609. from __main__ import display
  610. except ImportError:
  611. from ansible.utils.display import Display
  612. display = Display()
  613.  
  614. from ansible.module_utils.basic import to_native, to_bytes
  615. from ansible.module_utils.azure_rm_common import AzureRMModuleBase, azure_id_to_dict
  616.  
  617.  
  618. AZURE_OBJECT_CLASS = 'VirtualMachine'
  619.  
  620. AZURE_ENUM_MODULES = ['azure.mgmt.compute.models']
  621.  
  622.  
  623. def extract_names_from_blob_uri(blob_uri, storage_suffix):
  624. # HACK: ditch this once python SDK supports get by URI
  625. m = re.match(r'^https://(?P<accountname>[^.]+)\.blob\.{0}/'
  626. r'(?P<containername>[^/]+)/(?P<blobname>.+)$'.format(storage_suffix), blob_uri)
  627. if not m:
  628. raise Exception("unable to parse blob uri '%s'" % blob_uri)
  629. extracted_names = m.groupdict()
  630. return extracted_names
  631.  
  632.  
  633. class AzureRMVirtualMachine(AzureRMModuleBase):
  634.  
  635. def __init__(self):
  636.  
  637. self.module_arg_spec = dict(
  638. resource_group=dict(type='str', required=True),
  639. name=dict(type='str', required=True),
  640. custom_data=dict(type='str'),
  641. state=dict(choices=['present', 'absent'], default='present', type='str'),
  642. location=dict(type='str'),
  643. short_hostname=dict(type='str'),
  644. vm_size=dict(type='str'),
  645. admin_username=dict(type='str'),
  646. admin_password=dict(type='str', no_log=True),
  647. ssh_password_enabled=dict(type='bool', default=True),
  648. ssh_public_keys=dict(type='list'),
  649. image=dict(type='raw'),
  650. availability_set=dict(type='str'),
  651. storage_account_name=dict(type='str', aliases=['storage_account']),
  652. storage_container_name=dict(type='str', aliases=['storage_container'], default='vhds'),
  653. storage_blob_name=dict(type='str', aliases=['storage_blob']),
  654. os_disk_caching=dict(type='str', aliases=['disk_caching'], choices=['ReadOnly', 'ReadWrite'],
  655. default='ReadOnly'),
  656. managed_disk_type=dict(type='str', choices=['Standard_LRS', 'Premium_LRS']),
  657. os_type=dict(type='str', choices=['Linux', 'Windows'], default='Linux'),
  658. public_ip_allocation_method=dict(type='str', choices=['Dynamic', 'Static', 'Disabled'], default='Static',
  659. aliases=['public_ip_allocation']),
  660. open_ports=dict(type='list'),
  661. network_interface_names=dict(type='list', aliases=['network_interfaces']),
  662. remove_on_absent=dict(type='list', default=['all']),
  663. virtual_network_resource_group=dict(type='str'),
  664. virtual_network_name=dict(type='str', aliases=['virtual_network']),
  665. subnet_name=dict(type='str', aliases=['subnet']),
  666. allocated=dict(type='bool', default=True),
  667. restarted=dict(type='bool', default=False),
  668. started=dict(type='bool', default=True),
  669. data_disks=dict(type='list'),
  670. plan=dict(type='dict')
  671. )
  672.  
  673. self.resource_group = None
  674. self.name = None
  675. self.custom_data = None
  676. self.state = None
  677. self.location = None
  678. self.short_hostname = None
  679. self.vm_size = None
  680. self.admin_username = None
  681. self.admin_password = None
  682. self.ssh_password_enabled = None
  683. self.ssh_public_keys = None
  684. self.image = None
  685. self.availability_set = None
  686. self.storage_account_name = None
  687. self.storage_container_name = None
  688. self.storage_blob_name = None
  689. self.os_type = None
  690. self.os_disk_caching = None
  691. self.managed_disk_type = None
  692. self.network_interface_names = None
  693. self.remove_on_absent = set()
  694. self.tags = None
  695. self.force = None
  696. self.public_ip_allocation_method = None
  697. self.open_ports = None
  698. self.virtual_network_resource_group = None
  699. self.virtual_network_name = None
  700. self.subnet_name = None
  701. self.allocated = None
  702. self.restarted = None
  703. self.started = None
  704. self.differences = None
  705. self.data_disks = None
  706. self.plan = None
  707.  
  708. self.results = dict(
  709. changed=False,
  710. actions=[],
  711. powerstate_change=None,
  712. ansible_facts=dict(azure_vm=None)
  713. )
  714.  
  715. super(AzureRMVirtualMachine, self).__init__(derived_arg_spec=self.module_arg_spec,
  716. supports_check_mode=True)
  717.  
  718. def exec_module(self, **kwargs):
  719.  
  720. display = Display(self.module._verbosity)
  721.  
  722. for key in list(self.module_arg_spec.keys()) + ['tags']:
  723. setattr(self, key, kwargs[key])
  724.  
  725. # make sure options are lower case
  726. self.remove_on_absent = set([resource.lower() for resource in self.remove_on_absent])
  727.  
  728. display.v(u"test")
  729. display.v("test")
  730. display.vv(u"first hello world")
  731. display.vvvv(u"first four hello world")
  732.  
  733. changed = False
  734. powerstate_change = None
  735. results = dict()
  736. vm = None
  737. network_interfaces = []
  738. requested_vhd_uri = None
  739. data_disk_requested_vhd_uri = None
  740. disable_ssh_password = None
  741. vm_dict = None
  742. image_reference = None
  743. custom_image = False
  744.  
  745. resource_group = self.get_resource_group(self.resource_group)
  746. if not self.location:
  747. # Set default location
  748. self.location = resource_group.location
  749.  
  750. if self.state == 'present':
  751. # Verify parameters and resolve any defaults
  752.  
  753. if self.vm_size and not self.vm_size_is_valid():
  754. self.fail("Parameter error: vm_size {0} is not valid for your subscription and location.".format(
  755. self.vm_size
  756. ))
  757.  
  758. if self.network_interface_names:
  759. for name in self.network_interface_names:
  760. nic = self.get_network_interface(name)
  761. network_interfaces.append(nic.id)
  762.  
  763. if self.ssh_public_keys:
  764. msg = "Parameter error: expecting ssh_public_keys to be a list of type dict where " \
  765. "each dict contains keys: path, key_data."
  766. for key in self.ssh_public_keys:
  767. if not isinstance(key, dict):
  768. self.fail(msg)
  769. if not key.get('path') or not key.get('key_data'):
  770. self.fail(msg)
  771.  
  772. if self.image and isinstance(self.image, dict):
  773. if all(key in self.image for key in ('publisher', 'offer', 'sku', 'version')):
  774. marketplace_image = self.get_marketplace_image_version()
  775. if self.image['version'] == 'latest':
  776. self.image['version'] = marketplace_image.name
  777. self.log("Using image version {0}".format(self.image['version']))
  778.  
  779. image_reference = self.compute_models.ImageReference(
  780. publisher=self.image['publisher'],
  781. offer=self.image['offer'],
  782. sku=self.image['sku'],
  783. version=self.image['version']
  784. )
  785. elif self.image.get('name'):
  786. custom_image = True
  787. image_reference = self.get_custom_image_reference(
  788. self.image.get('name'),
  789. self.image.get('resource_group'))
  790. else:
  791. self.fail("parameter error: expecting image to contain [publisher, offer, sku, version] or [name, resource_group]")
  792. elif self.image and isinstance(self.image, str):
  793. custom_image = True
  794. image_reference = self.get_custom_image_reference(self.image)
  795. elif self.image:
  796. self.fail("parameter error: expecting image to be a string or dict not {0}".format(type(self.image).__name__))
  797.  
  798. if self.plan:
  799. if not self.plan.get('name') or not self.plan.get('product') or not self.plan.get('publisher'):
  800. self.fail("parameter error: plan must include name, product, and publisher")
  801.  
  802. if not self.storage_blob_name and not self.managed_disk_type:
  803. self.storage_blob_name = self.name + '.vhd'
  804. elif self.managed_disk_type:
  805. self.storage_blob_name = self.name
  806.  
  807. if self.storage_account_name and not self.managed_disk_type:
  808. properties = self.get_storage_account(self.storage_account_name)
  809.  
  810. requested_vhd_uri = '{0}{1}/{2}'.format(properties.primary_endpoints.blob,
  811. self.storage_container_name,
  812. self.storage_blob_name)
  813.  
  814. disable_ssh_password = not self.ssh_password_enabled
  815.  
  816. try:
  817. self.log("Fetching virtual machine {0}".format(self.name))
  818. vm = self.compute_client.virtual_machines.get(self.resource_group, self.name, expand='instanceview')
  819. self.check_provisioning_state(vm, self.state)
  820. vm_dict = self.serialize_vm(vm)
  821.  
  822. if self.state == 'present':
  823. differences = []
  824. current_nics = []
  825. results = vm_dict
  826.  
  827. # Try to determine if the VM needs to be updated
  828. if self.network_interface_names:
  829. for nic in vm_dict['properties']['networkProfile']['networkInterfaces']:
  830. current_nics.append(nic['id'])
  831.  
  832. if set(current_nics) != set(network_interfaces):
  833. self.log('CHANGED: virtual machine {0} - network interfaces are different.'.format(self.name))
  834. differences.append('Network Interfaces')
  835. updated_nics = [dict(id=id) for id in network_interfaces]
  836. vm_dict['properties']['networkProfile']['networkInterfaces'] = updated_nics
  837. changed = True
  838.  
  839. if self.os_disk_caching and \
  840. self.os_disk_caching != vm_dict['properties']['storageProfile']['osDisk']['caching']:
  841. self.log('CHANGED: virtual machine {0} - OS disk caching'.format(self.name))
  842. differences.append('OS Disk caching')
  843. changed = True
  844. vm_dict['properties']['storageProfile']['osDisk']['caching'] = self.os_disk_caching
  845.  
  846. update_tags, vm_dict['tags'] = self.update_tags(vm_dict.get('tags', dict()))
  847. if update_tags:
  848. differences.append('Tags')
  849. changed = True
  850.  
  851. if self.short_hostname and self.short_hostname != vm_dict['properties']['osProfile']['computerName']:
  852. self.log('CHANGED: virtual machine {0} - short hostname'.format(self.name))
  853. differences.append('Short Hostname')
  854. changed = True
  855. vm_dict['properties']['osProfile']['computerName'] = self.short_hostname
  856.  
  857. if self.started and vm_dict['powerstate'] not in ['starting', 'running'] and self.allocated:
  858. self.log("CHANGED: virtual machine {0} not running and requested state 'running'".format(self.name))
  859. changed = True
  860. powerstate_change = 'poweron'
  861. elif self.state == 'present' and vm_dict['powerstate'] == 'running' and self.restarted:
  862. self.log("CHANGED: virtual machine {0} {1} and requested state 'restarted'"
  863. .format(self.name, vm_dict['powerstate']))
  864. changed = True
  865. powerstate_change = 'restarted'
  866. elif self.state == 'present' and not self.allocated and vm_dict['powerstate'] not in ['deallocated', 'deallocating']:
  867. self.log("CHANGED: virtual machine {0} {1} and requested state 'deallocated'"
  868. .format(self.name, vm_dict['powerstate']))
  869. changed = True
  870. powerstate_change = 'deallocated'
  871. elif not self.started and vm_dict['powerstate'] == 'running':
  872. self.log("CHANGED: virtual machine {0} running and requested state 'stopped'".format(self.name))
  873. changed = True
  874. powerstate_change = 'poweroff'
  875.  
  876. self.differences = differences
  877.  
  878. elif self.state == 'absent':
  879. self.log("CHANGED: virtual machine {0} exists and requested state is 'absent'".format(self.name))
  880. results = dict()
  881. changed = True
  882.  
  883. except CloudError:
  884. self.log('Virtual machine {0} does not exist'.format(self.name))
  885. if self.state == 'present':
  886. self.log("CHANGED: virtual machine {0} does not exist but state is 'present'.".format(self.name))
  887. changed = True
  888.  
  889. self.results['changed'] = changed
  890. self.results['ansible_facts']['azure_vm'] = results
  891. self.results['powerstate_change'] = powerstate_change
  892. display.vv("outside hello world")
  893. display.vvvv("four outside hello world")
  894.  
  895. if self.check_mode:
  896. return self.results
  897.  
  898. if changed:
  899. if self.state == 'present':
  900. default_storage_account = None
  901. if not vm:
  902. # Create the VM
  903. self.log("Create virtual machine {0}".format(self.name))
  904. self.results['actions'].append('Created VM {0}'.format(self.name))
  905.  
  906. # Validate parameters
  907. if not self.admin_username:
  908. self.fail("Parameter error: admin_username required when creating a virtual machine.")
  909.  
  910. if self.os_type == 'Linux':
  911. if disable_ssh_password and not self.ssh_public_keys:
  912. self.fail("Parameter error: ssh_public_keys required when disabling SSH password.")
  913.  
  914. if not image_reference:
  915. self.fail("Parameter error: an image is required when creating a virtual machine.")
  916.  
  917. availability_set_resource = None
  918. if self.availability_set:
  919. parsed_availability_set = parse_resource_id(self.availability_set)
  920. availability_set = self.get_availability_set(parsed_availability_set.get('resource_group', self.resource_group),
  921. parsed_availability_set.get('name'))
  922. availability_set_resource = self.compute_models.SubResource(availability_set.id)
  923.  
  924. # Get defaults
  925. if not self.network_interface_names:
  926. default_nic = self.create_default_nic()
  927. self.log("network interface:")
  928. self.log(self.serialize_obj(default_nic, 'NetworkInterface'), pretty_print=True)
  929. network_interfaces = [default_nic.id]
  930.  
  931. # os disk
  932. if not self.storage_account_name and not self.managed_disk_type:
  933. storage_account = self.create_default_storage_account()
  934. self.log("storage account:")
  935. self.log(self.serialize_obj(storage_account, 'StorageAccount'), pretty_print=True)
  936. requested_vhd_uri = 'https://{0}.blob.{1}/{2}/{3}'.format(
  937. storage_account.name,
  938. self._cloud_environment.suffixes.storage_endpoint,
  939. self.storage_container_name,
  940. self.storage_blob_name)
  941. default_storage_account = storage_account # store for use by data disks if necessary
  942.  
  943. if not self.short_hostname:
  944. self.short_hostname = self.name
  945.  
  946. nics = [self.compute_models.NetworkInterfaceReference(id=id) for id in network_interfaces]
  947.  
  948. # os disk
  949. if self.managed_disk_type:
  950. vhd = None
  951. managed_disk = self.compute_models.ManagedDiskParameters(storage_account_type=self.managed_disk_type)
  952. elif custom_image:
  953. vhd = None
  954. managed_disk = None
  955. else:
  956. vhd = self.compute_models.VirtualHardDisk(uri=requested_vhd_uri)
  957. managed_disk = None
  958.  
  959. plan = None
  960. if self.plan:
  961. plan = self.compute_models.Plan(name=self.plan.get('name'), product=self.plan.get('product'),
  962. publisher=self.plan.get('publisher'),
  963. promotion_code=self.plan.get('promotion_code'))
  964.  
  965. vm_resource = self.compute_models.VirtualMachine(
  966. self.location,
  967. tags=self.tags,
  968. os_profile=self.compute_models.OSProfile(
  969. admin_username=self.admin_username,
  970. computer_name=self.short_hostname,
  971. ),
  972. hardware_profile=self.compute_models.HardwareProfile(
  973. vm_size=self.vm_size
  974. ),
  975. storage_profile=self.compute_models.StorageProfile(
  976. os_disk=self.compute_models.OSDisk(
  977. name=self.storage_blob_name,
  978. vhd=vhd,
  979. managed_disk=managed_disk,
  980. create_option=self.compute_models.DiskCreateOptionTypes.from_image,
  981. caching=self.os_disk_caching,
  982. ),
  983. image_reference=image_reference,
  984. ),
  985. network_profile=self.compute_models.NetworkProfile(
  986. network_interfaces=nics
  987. ),
  988. availability_set=availability_set_resource,
  989. plan=plan
  990. )
  991.  
  992. if self.admin_password:
  993. vm_resource.os_profile.admin_password = self.admin_password
  994.  
  995. if self.custom_data:
  996. # Azure SDK (erroneously?) wants native string type for this
  997. vm_resource.os_profile.custom_data = to_native(base64.b64encode(to_bytes(self.custom_data)))
  998.  
  999. if self.os_type == 'Linux':
  1000. vm_resource.os_profile.linux_configuration = self.compute_models.LinuxConfiguration(
  1001. disable_password_authentication=disable_ssh_password
  1002. )
  1003. if self.ssh_public_keys:
  1004. ssh_config = self.compute_models.SshConfiguration()
  1005. ssh_config.public_keys = \
  1006. [self.compute_models.SshPublicKey(path=key['path'], key_data=key['key_data']) for key in self.ssh_public_keys]
  1007. vm_resource.os_profile.linux_configuration.ssh = ssh_config
  1008.  
  1009. # data disk
  1010. if self.data_disks:
  1011. data_disks = []
  1012. count = 0
  1013.  
  1014. for data_disk in self.data_disks:
  1015. if not data_disk.get('managed_disk_type'):
  1016. if not data_disk.get('storage_blob_name'):
  1017. data_disk['storage_blob_name'] = self.name + '-data-' + str(count) + '.vhd'
  1018. count += 1
  1019.  
  1020. if data_disk.get('storage_account_name'):
  1021. data_disk_storage_account = self.get_storage_account(data_disk['storage_account_name'])
  1022. else:
  1023. if(not default_storage_account):
  1024. data_disk_storage_account = self.create_default_storage_account()
  1025. self.log("data disk storage account:")
  1026. self.log(self.serialize_obj(data_disk_storage_account, 'StorageAccount'), pretty_print=True)
  1027. default_storage_account = data_disk_storage_account # store for use by future data disks if necessary
  1028. else:
  1029. data_disk_storage_account = default_storage_account
  1030.  
  1031. if not data_disk.get('storage_container_name'):
  1032. data_disk['storage_container_name'] = 'vhds'
  1033.  
  1034. data_disk_requested_vhd_uri = 'https://{0}.blob.{1}/{2}/{3}'.format(
  1035. data_disk_storage_account.name,
  1036. self._cloud_environment.suffixes.storage_endpoint,
  1037. data_disk['storage_container_name'],
  1038. data_disk['storage_blob_name']
  1039. )
  1040.  
  1041. if not data_disk.get('managed_disk_type'):
  1042. data_disk_managed_disk = None
  1043. disk_name = data_disk['storage_blob_name']
  1044. data_disk_vhd = self.compute_models.VirtualHardDisk(uri=data_disk_requested_vhd_uri)
  1045. else:
  1046. data_disk_vhd = None
  1047. data_disk_managed_disk = self.compute_models.ManagedDiskParameters(storage_account_type=data_disk['managed_disk_type'])
  1048. disk_name = self.name + "-datadisk-" + str(count)
  1049. count += 1
  1050.  
  1051. data_disk['caching'] = data_disk.get(
  1052. 'caching', 'ReadOnly'
  1053. )
  1054.  
  1055. data_disks.append(self.compute_models.DataDisk(
  1056. lun=data_disk['lun'],
  1057. name=disk_name,
  1058. vhd=data_disk_vhd,
  1059. caching=data_disk['caching'],
  1060. create_option=self.compute_models.DiskCreateOptionTypes.empty,
  1061. disk_size_gb=data_disk['disk_size_gb'],
  1062. managed_disk=data_disk_managed_disk,
  1063. ))
  1064.  
  1065. vm_resource.storage_profile.data_disks = data_disks
  1066.  
  1067. self.log("Create virtual machine with parameters:")
  1068. self.create_or_update_vm(vm_resource)
  1069.  
  1070. elif self.differences and len(self.differences) > 0:
  1071. # Update the VM based on detected config differences
  1072.  
  1073. self.log("Update virtual machine {0}".format(self.name))
  1074. self.results['actions'].append('Updated VM {0}'.format(self.name))
  1075.  
  1076. nics = [self.compute_models.NetworkInterfaceReference(id=interface['id'])
  1077. for interface in vm_dict['properties']['networkProfile']['networkInterfaces']]
  1078.  
  1079. # os disk
  1080. #display.vv("hello world")
  1081. #display.vvvv("four hello world")
  1082. #display.vv(vm_dict['properties']['storageProfile']['osDisk'])
  1083. if not vm_dict['properties']['storageProfile']['osDisk'].get('managedDisk'):
  1084. managed_disk = None
  1085. vhd = self.compute_models.VirtualHardDisk(uri=vm_dict['properties']['storageProfile']['osDisk']['vhd']['uri'])
  1086. else:
  1087. vhd = None
  1088. managed_disk = self.compute_models.ManagedDiskParameters(
  1089. storage_account_type=vm_dict['properties']['storageProfile']['osDisk']['managedDisk']['storageAccountType']
  1090. #storage_account_type="Standard_LRS"
  1091. )
  1092.  
  1093. availability_set_resource = None
  1094. try:
  1095. availability_set_resource = self.compute_models.SubResource(vm_dict['properties']['availabilitySet']['id'])
  1096. except Exception:
  1097. # pass if the availability set is not set
  1098. pass
  1099.  
  1100. if vm_dict['properties']['storageProfile']['imageReference'].get('publisher'):
  1101. vm_resource = self.compute_models.VirtualMachine(
  1102. vm_dict['location'],
  1103. os_profile=self.compute_models.OSProfile(
  1104. admin_username=vm_dict['properties']['osProfile']['adminUsername'],
  1105. computer_name=vm_dict['properties']['osProfile']['computerName']
  1106. ),
  1107. hardware_profile=self.compute_models.HardwareProfile(
  1108. vm_size=vm_dict['properties']['hardwareProfile']['vmSize']
  1109. ),
  1110. storage_profile=self.compute_models.StorageProfile(
  1111. os_disk=self.compute_models.OSDisk(
  1112. name=vm_dict['properties']['storageProfile']['osDisk']['name'],
  1113. vhd=vhd,
  1114. managed_disk=managed_disk,
  1115. create_option=vm_dict['properties']['storageProfile']['osDisk']['createOption'],
  1116. os_type=vm_dict['properties']['storageProfile']['osDisk']['osType'],
  1117. caching=vm_dict['properties']['storageProfile']['osDisk']['caching'],
  1118. ),
  1119. image_reference=self.compute_models.ImageReference(
  1120. publisher=vm_dict['properties']['storageProfile']['imageReference']['publisher'],
  1121. offer=vm_dict['properties']['storageProfile']['imageReference']['offer'],
  1122. sku=vm_dict['properties']['storageProfile']['imageReference']['sku'],
  1123. version=vm_dict['properties']['storageProfile']['imageReference']['version']
  1124. ),
  1125. ),
  1126. availability_set=availability_set_resource,
  1127. network_profile=self.compute_models.NetworkProfile(
  1128. network_interfaces=nics
  1129. ),
  1130. )
  1131. else:
  1132. vm_resource = self.compute_models.VirtualMachine(
  1133. vm_dict['location'],
  1134. os_profile=self.compute_models.OSProfile(
  1135. admin_username=vm_dict['properties']['osProfile']['adminUsername'],
  1136. computer_name=vm_dict['properties']['osProfile']['computerName']
  1137. ),
  1138. hardware_profile=self.compute_models.HardwareProfile(
  1139. vm_size=vm_dict['properties']['hardwareProfile']['vmSize']
  1140. ),
  1141. storage_profile=self.compute_models.StorageProfile(
  1142. os_disk=self.compute_models.OSDisk(
  1143. name=vm_dict['properties']['storageProfile']['osDisk']['name'],
  1144. vhd=vhd,
  1145. managed_disk=managed_disk,
  1146. create_option=vm_dict['properties']['storageProfile']['osDisk']['createOption'],
  1147. os_type=vm_dict['properties']['storageProfile']['osDisk']['osType'],
  1148. caching=vm_dict['properties']['storageProfile']['osDisk']['caching'],
  1149. ),
  1150. image_reference=self.get_custom_image_reference(
  1151. name=self.image,
  1152. #name=vm_dict['properties']['storageProfile']['imageReference']['name'],
  1153. resource_group=self.resource_group
  1154. #resource_group=vm_dict['resource_group']
  1155. ),
  1156. ),
  1157. availability_set=availability_set_resource,
  1158. network_profile=self.compute_models.NetworkProfile(
  1159. network_interfaces=nics
  1160. ),
  1161. )
  1162.  
  1163. if vm_dict.get('tags'):
  1164. vm_resource.tags = vm_dict['tags']
  1165.  
  1166. # Add custom_data, if provided
  1167. if vm_dict['properties']['osProfile'].get('customData'):
  1168. custom_data = vm_dict['properties']['osProfile']['customData']
  1169. # Azure SDK (erroneously?) wants native string type for this
  1170. vm_resource.os_profile.custom_data = to_native(base64.b64encode(to_bytes(custom_data)))
  1171.  
  1172. # Add admin password, if one provided
  1173. if vm_dict['properties']['osProfile'].get('adminPassword'):
  1174. vm_resource.os_profile.admin_password = vm_dict['properties']['osProfile']['adminPassword']
  1175.  
  1176. # Add linux configuration, if applicable
  1177. linux_config = vm_dict['properties']['osProfile'].get('linuxConfiguration')
  1178. if linux_config:
  1179. ssh_config = linux_config.get('ssh', None)
  1180. vm_resource.os_profile.linux_configuration = self.compute_models.LinuxConfiguration(
  1181. disable_password_authentication=linux_config.get('disablePasswordAuthentication', False)
  1182. )
  1183. if ssh_config:
  1184. public_keys = ssh_config.get('publicKeys')
  1185. if public_keys:
  1186. vm_resource.os_profile.linux_configuration.ssh = self.compute_models.SshConfiguration(public_keys=[])
  1187. for key in public_keys:
  1188. vm_resource.os_profile.linux_configuration.ssh.public_keys.append(
  1189. self.compute_models.SshPublicKey(path=key['path'], key_data=key['keyData'])
  1190. )
  1191.  
  1192. # data disk
  1193. if vm_dict['properties']['storageProfile'].get('dataDisks'):
  1194. data_disks = []
  1195.  
  1196. for data_disk in vm_dict['properties']['storageProfile']['dataDisks']:
  1197. if data_disk.get('managedDisk'):
  1198. managed_disk_type = data_disk['managedDisk']['storageAccountType']
  1199. data_disk_managed_disk = self.compute_models.ManagedDiskParameters(storage_account_type=managed_disk_type)
  1200. data_disk_vhd = None
  1201. else:
  1202. data_disk_vhd = data_disk['vhd']['uri']
  1203. data_disk_managed_disk = None
  1204.  
  1205. data_disks.append(self.compute_models.DataDisk(
  1206. lun=int(data_disk['lun']),
  1207. name=data_disk.get('name'),
  1208. vhd=data_disk_vhd,
  1209. caching=data_disk.get('caching'),
  1210. create_option=data_disk.get('createOption'),
  1211. disk_size_gb=int(data_disk['diskSizeGB']),
  1212. managed_disk=data_disk_managed_disk,
  1213. ))
  1214. vm_resource.storage_profile.data_disks = data_disks
  1215.  
  1216. self.log("Update virtual machine with parameters:")
  1217. self.create_or_update_vm(vm_resource)
  1218.  
  1219. # Make sure we leave the machine in requested power state
  1220. if (powerstate_change == 'poweron' and
  1221. self.results['ansible_facts']['azure_vm']['powerstate'] != 'running'):
  1222. # Attempt to power on the machine
  1223. self.power_on_vm()
  1224.  
  1225. elif (powerstate_change == 'poweroff' and
  1226. self.results['ansible_facts']['azure_vm']['powerstate'] == 'running'):
  1227. # Attempt to power off the machine
  1228. self.power_off_vm()
  1229.  
  1230. elif powerstate_change == 'restarted':
  1231. self.restart_vm()
  1232.  
  1233. elif powerstate_change == 'deallocated':
  1234. self.deallocate_vm()
  1235.  
  1236. self.results['ansible_facts']['azure_vm'] = self.serialize_vm(self.get_vm())
  1237.  
  1238. elif self.state == 'absent':
  1239. # delete the VM
  1240. self.log("Delete virtual machine {0}".format(self.name))
  1241. self.results['ansible_facts']['azure_vm'] = None
  1242. self.delete_vm(vm)
  1243.  
  1244. # until we sort out how we want to do this globally
  1245. del self.results['actions']
  1246.  
  1247. return self.results
  1248.  
  1249. def get_vm(self):
  1250. '''
  1251. Get the VM with expanded instanceView
  1252.  
  1253. :return: VirtualMachine object
  1254. '''
  1255. try:
  1256. vm = self.compute_client.virtual_machines.get(self.resource_group, self.name, expand='instanceview')
  1257. return vm
  1258. except Exception as exc:
  1259. self.fail("Error getting virtual machine {0} - {1}".format(self.name, str(exc)))
  1260.  
  1261. def serialize_vm(self, vm):
  1262. '''
  1263. Convert a VirtualMachine object to dict.
  1264.  
  1265. :param vm: VirtualMachine object
  1266. :return: dict
  1267. '''
  1268.  
  1269. result = self.serialize_obj(vm, AZURE_OBJECT_CLASS, enum_modules=AZURE_ENUM_MODULES)
  1270. result['id'] = vm.id
  1271. result['name'] = vm.name
  1272. result['type'] = vm.type
  1273. result['location'] = vm.location
  1274. result['tags'] = vm.tags
  1275.  
  1276. result['powerstate'] = dict()
  1277. if vm.instance_view:
  1278. result['powerstate'] = next((s.code.replace('PowerState/', '')
  1279. for s in vm.instance_view.statuses if s.code.startswith('PowerState')), None)
  1280.  
  1281. # Expand network interfaces to include config properties
  1282. for interface in vm.network_profile.network_interfaces:
  1283. int_dict = azure_id_to_dict(interface.id)
  1284. nic = self.get_network_interface(int_dict['networkInterfaces'])
  1285. for interface_dict in result['properties']['networkProfile']['networkInterfaces']:
  1286. if interface_dict['id'] == interface.id:
  1287. nic_dict = self.serialize_obj(nic, 'NetworkInterface')
  1288. interface_dict['name'] = int_dict['networkInterfaces']
  1289. interface_dict['properties'] = nic_dict['properties']
  1290.  
  1291. # Expand public IPs to include config properties
  1292. for interface in result['properties']['networkProfile']['networkInterfaces']:
  1293. for config in interface['properties']['ipConfigurations']:
  1294. if config['properties'].get('publicIPAddress'):
  1295. pipid_dict = azure_id_to_dict(config['properties']['publicIPAddress']['id'])
  1296. try:
  1297. pip = self.network_client.public_ip_addresses.get(self.resource_group,
  1298. pipid_dict['publicIPAddresses'])
  1299. except Exception as exc:
  1300. self.fail("Error fetching public ip {0} - {1}".format(pipid_dict['publicIPAddresses'],
  1301. str(exc)))
  1302. pip_dict = self.serialize_obj(pip, 'PublicIPAddress')
  1303. config['properties']['publicIPAddress']['name'] = pipid_dict['publicIPAddresses']
  1304. config['properties']['publicIPAddress']['properties'] = pip_dict['properties']
  1305.  
  1306. self.log(result, pretty_print=True)
  1307. if self.state != 'absent' and not result['powerstate']:
  1308. self.fail("Failed to determine PowerState of virtual machine {0}".format(self.name))
  1309. return result
  1310.  
  1311. def power_off_vm(self):
  1312. self.log("Powered off virtual machine {0}".format(self.name))
  1313. self.results['actions'].append("Powered off virtual machine {0}".format(self.name))
  1314. try:
  1315. poller = self.compute_client.virtual_machines.power_off(self.resource_group, self.name)
  1316. self.get_poller_result(poller)
  1317. except Exception as exc:
  1318. self.fail("Error powering off virtual machine {0} - {1}".format(self.name, str(exc)))
  1319. return True
  1320.  
  1321. def power_on_vm(self):
  1322. self.results['actions'].append("Powered on virtual machine {0}".format(self.name))
  1323. self.log("Power on virtual machine {0}".format(self.name))
  1324. try:
  1325. poller = self.compute_client.virtual_machines.start(self.resource_group, self.name)
  1326. self.get_poller_result(poller)
  1327. except Exception as exc:
  1328. self.fail("Error powering on virtual machine {0} - {1}".format(self.name, str(exc)))
  1329. return True
  1330.  
  1331. def restart_vm(self):
  1332. self.results['actions'].append("Restarted virtual machine {0}".format(self.name))
  1333. self.log("Restart virtual machine {0}".format(self.name))
  1334. try:
  1335. poller = self.compute_client.virtual_machines.restart(self.resource_group, self.name)
  1336. self.get_poller_result(poller)
  1337. except Exception as exc:
  1338. self.fail("Error restarting virtual machine {0} - {1}".format(self.name, str(exc)))
  1339. return True
  1340.  
  1341. def deallocate_vm(self):
  1342. self.results['actions'].append("Deallocated virtual machine {0}".format(self.name))
  1343. self.log("Deallocate virtual machine {0}".format(self.name))
  1344. try:
  1345. poller = self.compute_client.virtual_machines.deallocate(self.resource_group, self.name)
  1346. self.get_poller_result(poller)
  1347. except Exception as exc:
  1348. self.fail("Error deallocating virtual machine {0} - {1}".format(self.name, str(exc)))
  1349. return True
  1350.  
  1351. def delete_vm(self, vm):
  1352. vhd_uris = []
  1353. managed_disk_ids = []
  1354. nic_names = []
  1355. pip_names = []
  1356.  
  1357. if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
  1358. # store the attached vhd info so we can nuke it after the VM is gone
  1359. if(vm.storage_profile.os_disk.managed_disk):
  1360. self.log('Storing managed disk ID for deletion')
  1361. managed_disk_ids.append(vm.storage_profile.os_disk.managed_disk.id)
  1362. elif(vm.storage_profile.os_disk.vhd):
  1363. self.log('Storing VHD URI for deletion')
  1364. vhd_uris.append(vm.storage_profile.os_disk.vhd.uri)
  1365.  
  1366. data_disks = vm.storage_profile.data_disks
  1367. for data_disk in data_disks:
  1368. if(data_disk.vhd):
  1369. vhd_uris.append(data_disk.vhd.uri)
  1370. elif(data_disk.managed_disk):
  1371. managed_disk_ids.append(data_disk.managed_disk.id)
  1372.  
  1373. # FUTURE enable diff mode, move these there...
  1374. self.log("VHD URIs to delete: {0}".format(', '.join(vhd_uris)))
  1375. self.results['deleted_vhd_uris'] = vhd_uris
  1376. self.log("Managed disk IDs to delete: {0}".format(', '.join(managed_disk_ids)))
  1377. self.results['deleted_managed_disk_ids'] = managed_disk_ids
  1378.  
  1379. if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
  1380. # store the attached nic info so we can nuke them after the VM is gone
  1381. self.log('Storing NIC names for deletion.')
  1382. for interface in vm.network_profile.network_interfaces:
  1383. id_dict = azure_id_to_dict(interface.id)
  1384. nic_names.append(id_dict['networkInterfaces'])
  1385. self.log('NIC names to delete {0}'.format(', '.join(nic_names)))
  1386. self.results['deleted_network_interfaces'] = nic_names
  1387. if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
  1388. # also store each nic's attached public IPs and delete after the NIC is gone
  1389. for name in nic_names:
  1390. nic = self.get_network_interface(name)
  1391. for ipc in nic.ip_configurations:
  1392. if ipc.public_ip_address:
  1393. pip_dict = azure_id_to_dict(ipc.public_ip_address.id)
  1394. pip_names.append(pip_dict['publicIPAddresses'])
  1395. self.log('Public IPs to delete are {0}'.format(', '.join(pip_names)))
  1396. self.results['deleted_public_ips'] = pip_names
  1397.  
  1398. self.log("Deleting virtual machine {0}".format(self.name))
  1399. self.results['actions'].append("Deleted virtual machine {0}".format(self.name))
  1400. try:
  1401. poller = self.compute_client.virtual_machines.delete(self.resource_group, self.name)
  1402. # wait for the poller to finish
  1403. self.get_poller_result(poller)
  1404. except Exception as exc:
  1405. self.fail("Error deleting virtual machine {0} - {1}".format(self.name, str(exc)))
  1406.  
  1407. # TODO: parallelize nic, vhd, and public ip deletions with begin_deleting
  1408. # TODO: best-effort to keep deleting other linked resources if we encounter an error
  1409. if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
  1410. self.log('Deleting VHDs')
  1411. self.delete_vm_storage(vhd_uris)
  1412. self.log('Deleting managed disks')
  1413. self.delete_managed_disks(managed_disk_ids)
  1414.  
  1415. if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
  1416. self.log('Deleting network interfaces')
  1417. for name in nic_names:
  1418. self.delete_nic(name)
  1419.  
  1420. if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
  1421. self.log('Deleting public IPs')
  1422. for name in pip_names:
  1423. self.delete_pip(name)
  1424. return True
  1425.  
  1426. def get_network_interface(self, name):
  1427. try:
  1428. nic = self.network_client.network_interfaces.get(self.resource_group, name)
  1429. return nic
  1430. except Exception as exc:
  1431. self.fail("Error fetching network interface {0} - {1}".format(name, str(exc)))
  1432.  
  1433. def delete_nic(self, name):
  1434. self.log("Deleting network interface {0}".format(name))
  1435. self.results['actions'].append("Deleted network interface {0}".format(name))
  1436. try:
  1437. poller = self.network_client.network_interfaces.delete(self.resource_group, name)
  1438. except Exception as exc:
  1439. self.fail("Error deleting network interface {0} - {1}".format(name, str(exc)))
  1440. self.get_poller_result(poller)
  1441. # Delete doesn't return anything. If we get this far, assume success
  1442. return True
  1443.  
  1444. def delete_pip(self, name):
  1445. self.results['actions'].append("Deleted public IP {0}".format(name))
  1446. try:
  1447. poller = self.network_client.public_ip_addresses.delete(self.resource_group, name)
  1448. self.get_poller_result(poller)
  1449. except Exception as exc:
  1450. self.fail("Error deleting {0} - {1}".format(name, str(exc)))
  1451. # Delete returns nada. If we get here, assume that all is well.
  1452. return True
  1453.  
  1454. def delete_managed_disks(self, managed_disk_ids):
  1455. for mdi in managed_disk_ids:
  1456. try:
  1457. poller = self.rm_client.resources.delete_by_id(mdi, '2017-03-30')
  1458. self.get_poller_result(poller)
  1459. except Exception as exc:
  1460. self.fail("Error deleting managed disk {0} - {1}".format(mdi, str(exc)))
  1461.  
  1462. def delete_vm_storage(self, vhd_uris):
  1463. # FUTURE: figure out a cloud_env indepdendent way to delete these
  1464. for uri in vhd_uris:
  1465. self.log("Extracting info from blob uri '{0}'".format(uri))
  1466. try:
  1467. blob_parts = extract_names_from_blob_uri(uri, self._cloud_environment.suffixes.storage_endpoint)
  1468. except Exception as exc:
  1469. self.fail("Error parsing blob URI {0}".format(str(exc)))
  1470. storage_account_name = blob_parts['accountname']
  1471. container_name = blob_parts['containername']
  1472. blob_name = blob_parts['blobname']
  1473.  
  1474. blob_client = self.get_blob_client(self.resource_group, storage_account_name)
  1475.  
  1476. self.log("Delete blob {0}:{1}".format(container_name, blob_name))
  1477. self.results['actions'].append("Deleted blob {0}:{1}".format(container_name, blob_name))
  1478. try:
  1479. blob_client.delete_blob(container_name, blob_name)
  1480. except Exception as exc:
  1481. self.fail("Error deleting blob {0}:{1} - {2}".format(container_name, blob_name, str(exc)))
  1482.  
  1483. def get_marketplace_image_version(self):
  1484. try:
  1485. versions = self.compute_client.virtual_machine_images.list(self.location,
  1486. self.image['publisher'],
  1487. self.image['offer'],
  1488. self.image['sku'])
  1489. except Exception as exc:
  1490. self.fail("Error fetching image {0} {1} {2} - {3}".format(self.image['publisher'],
  1491. self.image['offer'],
  1492. self.image['sku'],
  1493. str(exc)))
  1494. if versions and len(versions) > 0:
  1495. if self.image['version'] == 'latest':
  1496. return versions[len(versions) - 1]
  1497. for version in versions:
  1498. if version.name == self.image['version']:
  1499. return version
  1500.  
  1501. self.fail("Error could not find image {0} {1} {2} {3}".format(self.image['publisher'],
  1502. self.image['offer'],
  1503. self.image['sku'],
  1504. self.image['version']))
  1505.  
  1506. def get_custom_image_reference(self, name, resource_group=None):
  1507. try:
  1508. if resource_group:
  1509. vm_images = self.compute_client.images.list_by_resource_group(resource_group)
  1510. else:
  1511. vm_images = self.compute_client.images.list()
  1512. except Exception as exc:
  1513. self.fail("Error fetching custom images from subscription - {0}".format(str(exc)))
  1514.  
  1515. for vm_image in vm_images:
  1516. if vm_image.name == name:
  1517. self.log("Using custom image id {0}".format(vm_image.id))
  1518. return self.compute_models.ImageReference(id=vm_image.id)
  1519.  
  1520. self.fail("Error could not find image with name {0}".format(name))
  1521.  
  1522. def get_availability_set(self, resource_group, name):
  1523. try:
  1524. return self.compute_client.availability_sets.get(resource_group, name)
  1525. except Exception as exc:
  1526. self.fail("Error fetching availability set {0} - {1}".format(name, str(exc)))
  1527.  
  1528. def get_storage_account(self, name):
  1529. try:
  1530. account = self.storage_client.storage_accounts.get_properties(self.resource_group,
  1531. name)
  1532. return account
  1533. except Exception as exc:
  1534. self.fail("Error fetching storage account {0} - {1}".format(name, str(exc)))
  1535.  
  1536. def create_or_update_vm(self, params):
  1537. try:
  1538. poller = self.compute_client.virtual_machines.create_or_update(self.resource_group, self.name, params)
  1539. self.get_poller_result(poller)
  1540. except Exception as exc:
  1541. self.fail("Error creating or updating virtual machine {0} - {1}".format(self.name, str(exc)))
  1542.  
  1543. def vm_size_is_valid(self):
  1544. '''
  1545. Validate self.vm_size against the list of virtual machine sizes available for the account and location.
  1546.  
  1547. :return: boolean
  1548. '''
  1549. try:
  1550. sizes = self.compute_client.virtual_machine_sizes.list(self.location)
  1551. except Exception as exc:
  1552. self.fail("Error retrieving available machine sizes - {0}".format(str(exc)))
  1553. for size in sizes:
  1554. if size.name == self.vm_size:
  1555. return True
  1556. return False
  1557.  
  1558. def create_default_storage_account(self):
  1559. '''
  1560. Create a default storage account <vm name>XXXX, where XXXX is a random number. If <vm name>XXXX exists, use it.
  1561. Otherwise, create one.
  1562.  
  1563. :return: storage account object
  1564. '''
  1565. account = None
  1566. valid_name = False
  1567.  
  1568. # Attempt to find a valid storage account name
  1569. storage_account_name_base = re.sub('[^a-zA-Z0-9]', '', self.name[:20].lower())
  1570. for i in range(0, 5):
  1571. rand = random.randrange(1000, 9999)
  1572. storage_account_name = storage_account_name_base + str(rand)
  1573. if self.check_storage_account_name(storage_account_name):
  1574. valid_name = True
  1575. break
  1576.  
  1577. if not valid_name:
  1578. self.fail("Failed to create a unique storage account name for {0}. Try using a different VM name."
  1579. .format(self.name))
  1580.  
  1581. try:
  1582. account = self.storage_client.storage_accounts.get_properties(self.resource_group, storage_account_name)
  1583. except CloudError:
  1584. pass
  1585.  
  1586. if account:
  1587. self.log("Storage account {0} found.".format(storage_account_name))
  1588. self.check_provisioning_state(account)
  1589. return account
  1590. sku = self.storage_models.Sku(self.storage_models.SkuName.standard_lrs)
  1591. sku.tier = self.storage_models.SkuTier.standard
  1592. kind = self.storage_models.Kind.storage
  1593. parameters = self.storage_models.StorageAccountCreateParameters(sku, kind, self.location)
  1594. self.log("Creating storage account {0} in location {1}".format(storage_account_name, self.location))
  1595. self.results['actions'].append("Created storage account {0}".format(storage_account_name))
  1596. try:
  1597. poller = self.storage_client.storage_accounts.create(self.resource_group, storage_account_name, parameters)
  1598. self.get_poller_result(poller)
  1599. except Exception as exc:
  1600. self.fail("Failed to create storage account: {0} - {1}".format(storage_account_name, str(exc)))
  1601. return self.get_storage_account(storage_account_name)
  1602.  
  1603. def check_storage_account_name(self, name):
  1604. self.log("Checking storage account name availability for {0}".format(name))
  1605. try:
  1606. response = self.storage_client.storage_accounts.check_name_availability(name)
  1607. if response.reason == 'AccountNameInvalid':
  1608. raise Exception("Invalid default storage account name: {0}".format(name))
  1609. except Exception as exc:
  1610. self.fail("Error checking storage account name availability for {0} - {1}".format(name, str(exc)))
  1611.  
  1612. return response.name_available
  1613.  
  1614. def create_default_nic(self):
  1615. '''
  1616. Create a default Network Interface <vm name>01. Requires an existing virtual network
  1617. with one subnet. If NIC <vm name>01 exists, use it. Otherwise, create one.
  1618.  
  1619. :return: NIC object
  1620. '''
  1621.  
  1622. network_interface_name = self.name + '01'
  1623. nic = None
  1624.  
  1625. self.log("Create default NIC {0}".format(network_interface_name))
  1626. self.log("Check to see if NIC {0} exists".format(network_interface_name))
  1627. try:
  1628. nic = self.network_client.network_interfaces.get(self.resource_group, network_interface_name)
  1629. except CloudError:
  1630. pass
  1631.  
  1632. if nic:
  1633. self.log("NIC {0} found.".format(network_interface_name))
  1634. self.check_provisioning_state(nic)
  1635. return nic
  1636.  
  1637. self.log("NIC {0} does not exist.".format(network_interface_name))
  1638.  
  1639. virtual_network_resource_group = None
  1640. if self.virtual_network_resource_group:
  1641. virtual_network_resource_group = self.virtual_network_resource_group
  1642. else:
  1643. virtual_network_resource_group = self.resource_group
  1644.  
  1645. if self.virtual_network_name:
  1646. try:
  1647. self.network_client.virtual_networks.list(virtual_network_resource_group, self.virtual_network_name)
  1648. virtual_network_name = self.virtual_network_name
  1649. except CloudError as exc:
  1650. self.fail("Error: fetching virtual network {0} - {1}".format(self.virtual_network_name, str(exc)))
  1651.  
  1652. else:
  1653. # Find a virtual network
  1654. no_vnets_msg = "Error: unable to find virtual network in resource group {0}. A virtual network " \
  1655. "with at least one subnet must exist in order to create a NIC for the virtual " \
  1656. "machine.".format(virtual_network_resource_group)
  1657.  
  1658. virtual_network_name = None
  1659. try:
  1660. vnets = self.network_client.virtual_networks.list(virtual_network_resource_group)
  1661. except CloudError:
  1662. self.log('cloud error!')
  1663. self.fail(no_vnets_msg)
  1664.  
  1665. for vnet in vnets:
  1666. virtual_network_name = vnet.name
  1667. self.log('vnet name: {0}'.format(vnet.name))
  1668. break
  1669.  
  1670. if not virtual_network_name:
  1671. self.fail(no_vnets_msg)
  1672.  
  1673. if self.subnet_name:
  1674. try:
  1675. subnet = self.network_client.subnets.get(virtual_network_resource_group, virtual_network_name, self.subnet_name)
  1676. subnet_id = subnet.id
  1677. except Exception as exc:
  1678. self.fail("Error: fetching subnet {0} - {1}".format(self.subnet_name, str(exc)))
  1679. else:
  1680. no_subnets_msg = "Error: unable to find a subnet in virtual network {0}. A virtual network " \
  1681. "with at least one subnet must exist in order to create a NIC for the virtual " \
  1682. "machine.".format(virtual_network_name)
  1683.  
  1684. subnet_id = None
  1685. try:
  1686. subnets = self.network_client.subnets.list(virtual_network_resource_group, virtual_network_name)
  1687. except CloudError:
  1688. self.fail(no_subnets_msg)
  1689.  
  1690. for subnet in subnets:
  1691. subnet_id = subnet.id
  1692. self.log('subnet id: {0}'.format(subnet_id))
  1693. break
  1694.  
  1695. if not subnet_id:
  1696. self.fail(no_subnets_msg)
  1697.  
  1698. pip = None
  1699. if self.public_ip_allocation_method != 'Disabled':
  1700. self.results['actions'].append('Created default public IP {0}'.format(self.name + '01'))
  1701. pip_info = self.create_default_pip(self.resource_group, self.location, self.name + '01', self.public_ip_allocation_method)
  1702. pip = self.network_models.PublicIPAddress(id=pip_info.id, location=pip_info.location, resource_guid=pip_info.resource_guid)
  1703.  
  1704. self.results['actions'].append('Created default security group {0}'.format(self.name + '01'))
  1705. group = self.create_default_securitygroup(self.resource_group, self.location, self.name + '01', self.os_type,
  1706. self.open_ports)
  1707.  
  1708. parameters = self.network_models.NetworkInterface(
  1709. location=self.location,
  1710. ip_configurations=[
  1711. self.network_models.NetworkInterfaceIPConfiguration(
  1712. private_ip_allocation_method='Dynamic',
  1713. )
  1714. ]
  1715. )
  1716. parameters.ip_configurations[0].subnet = self.network_models.Subnet(id=subnet_id)
  1717. parameters.ip_configurations[0].name = 'default'
  1718. parameters.network_security_group = self.network_models.NetworkSecurityGroup(id=group.id,
  1719. location=group.location,
  1720. resource_guid=group.resource_guid)
  1721. parameters.ip_configurations[0].public_ip_address = pip
  1722.  
  1723. self.log("Creating NIC {0}".format(network_interface_name))
  1724. self.log(self.serialize_obj(parameters, 'NetworkInterface'), pretty_print=True)
  1725. self.results['actions'].append("Created NIC {0}".format(network_interface_name))
  1726. try:
  1727. poller = self.network_client.network_interfaces.create_or_update(self.resource_group,
  1728. network_interface_name,
  1729. parameters)
  1730. new_nic = self.get_poller_result(poller)
  1731. except Exception as exc:
  1732. self.fail("Error creating network interface {0} - {1}".format(network_interface_name, str(exc)))
  1733. return new_nic
  1734.  
  1735.  
  1736. def main():
  1737. AzureRMVirtualMachine()
  1738.  
  1739.  
  1740. if __name__ == '__main__':
  1741. main()
Add Comment
Please, Sign In to add comment