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() |