kitchen-azurerm 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a2cb84c7a3cb3e85e7ed88d727a4f99f24cb8a5
4
- data.tar.gz: 2dc1a7c23c02638ebeb9e6d8aa8ddd66675f64c4
3
+ metadata.gz: dd80f1ea96944f1f057aa4408352df6c271b8ee7
4
+ data.tar.gz: d0f3fe3e46e1adc0b99d54616fb4f6810a310744
5
5
  SHA512:
6
- metadata.gz: b8723d655b8af447183e91bc19ba3d87e48fefef3afd17dbd6595145ab364069298b40f224f6985aa66cdd913db80639e5f3b2904a657d985d4a81814b22d5b1
7
- data.tar.gz: eb2088eec66092536c96b5b7b9dcf3101f022ebe9b82ce057e562528caf86c2eb3dba26727ef14da866621ba23dc8fb016ee3fb07c00505f0e2513d3f7b5d313
6
+ metadata.gz: 6dd7aa39f02b6b4349520d5fd3eeb6f14965bacdfb57a77a3ac8e39f45bfb91f0a17bd0c537a031d5a9e62e5a308871e6fc36e62e44fc9639f9a82e257d4a91a
7
+ data.tar.gz: 2abbf7c815ab65a7b9dbf148639db7417daa93fd4b7458810960a4ea7511628459eea91380adba00daacd40d5313fcbb9d6258fcb903eb75239b01c9d4dd108a
@@ -1,5 +1,8 @@
1
1
  # kitchen-azurerm Changelog
2
2
 
3
+ ## [0.3.3] - 2016-03-07
4
+ - Pinning ms_rest_azure dependencies to avoid errors when using latest ms_rest_azure library.
5
+
3
6
  ## [0.3.2] - 2016-03-07
4
7
  - Breaking: Linux machines are now created using a temporary sshkey (~/.ssh/id_kitchen-azurerm) instead of password (@stuartpreston)
5
8
  - Real error message shown if credentials are incorrect (@stuartpreston)
@@ -1,545 +1,550 @@
1
- require 'kitchen'
2
- require 'kitchen/driver/credentials'
3
- require 'securerandom'
4
- require 'azure_mgmt_resources'
5
- require 'azure_mgmt_network'
6
- require 'base64'
7
- require 'sshkey'
8
- require 'fileutils'
9
-
10
- module Kitchen
11
- module Driver
12
- #
13
- # Azurerm
14
- #
15
- class Azurerm < Kitchen::Driver::Base
16
- attr_accessor :resource_management_client
17
-
18
- default_config(:azure_resource_group_name) do |config|
19
- "kitchen-#{config.instance.name}"
20
- end
21
-
22
- default_config(:image_urn) do |_config|
23
- 'Canonical:UbuntuServer:14.04.3-LTS:latest'
24
- end
25
-
26
- default_config(:username) do |_config|
27
- 'azure'
28
- end
29
-
30
- default_config(:password) do |_config|
31
- 'P2ssw0rd'
32
- end
33
-
34
- default_config(:vm_name) do |_config|
35
- 'vm'
36
- end
37
-
38
- default_config(:storage_account_type) do |_config|
39
- 'Standard_LRS'
40
- end
41
-
42
- default_config(:boot_diagnostics_enabled) do |_config|
43
- 'true'
44
- end
45
-
46
- default_config(:winrm_powershell_script) do |_config|
47
- false
48
- end
49
-
50
- default_config(:azure_management_url) do |_config|
51
- 'https://management.azure.com'
52
- end
53
-
54
- def create(state)
55
- state = validate_state(state)
56
-
57
- image_publisher, image_offer, image_sku, image_version = config[:image_urn].split(':', 4)
58
- deployment_parameters = {
59
- location: config[:location],
60
- vmSize: config[:machine_size],
61
- storageAccountType: config[:storage_account_type],
62
- bootDiagnosticsEnabled: config[:boot_diagnostics_enabled],
63
- newStorageAccountName: "storage#{state[:uuid]}",
64
- adminUsername: state[:username],
65
- adminPassword: state[:password],
66
- dnsNameForPublicIP: "kitchen-#{state[:uuid]}",
67
- imagePublisher: image_publisher,
68
- imageOffer: image_offer,
69
- imageSku: image_sku,
70
- imageVersion: image_version,
71
- vmName: state[:vm_name]
72
- }
73
-
74
- credentials = Kitchen::Driver::Credentials.new.azure_credentials_for_subscription(config[:subscription_id])
75
- @resource_management_client = ::Azure::ARM::Resources::ResourceManagementClient.new(credentials)
76
- @resource_management_client.subscription_id = config[:subscription_id]
77
-
78
- # Create Resource Group
79
- resource_group = ::Azure::ARM::Resources::Models::ResourceGroup.new
80
- resource_group.location = config[:location]
81
- begin
82
- info "Creating Resource Group: #{state[:azure_resource_group_name]}"
83
- resource_management_client.resource_groups.create_or_update(state[:azure_resource_group_name], resource_group).value!
84
- rescue ::MsRestAzure::AzureOperationError => operation_error
85
- info operation_error
86
- raise operation_error
87
- end
88
-
89
- # Execute deployment steps
90
- begin
91
- deployment_name = "deploy-#{state[:uuid]}"
92
- info "Creating Deployment: #{deployment_name}"
93
- resource_management_client.deployments.create_or_update(state[:azure_resource_group_name], deployment_name, deployment(deployment_parameters)).value!
94
- rescue ::MsRestAzure::AzureOperationError => operation_error
95
- rest_error = operation_error.body['error']
96
- deployment_active = rest_error['code'] == 'DeploymentActive'
97
- if deployment_active
98
- info "Deployment for resource group #{state[:azure_resource_group_name]} is ongoing."
99
- info "If you need to change the deployment template you'll need to rerun `kitchen create` for this instance."
100
- else
101
- info rest_error
102
- raise operation_error
103
- end
104
- end
105
-
106
- # Monitor all operations until completion
107
- follow_deployment_until_end_state(state[:azure_resource_group_name], deployment_name)
108
-
109
- # Now retrieve the public IP from the resource group:
110
- network_management_client = ::Azure::ARM::Network::NetworkResourceProviderClient.new(credentials)
111
- network_management_client.subscription_id = config[:subscription_id]
112
- result = network_management_client.public_ip_addresses.get(state[:azure_resource_group_name], 'publicip').value!
113
- info "IP Address is: #{result.body.properties.ip_address} [#{result.body.properties.dns_settings.fqdn}]"
114
- state[:hostname] = result.body.properties.ip_address
115
- end
116
-
117
- def existing_state_value?(state, property)
118
- state.key?(property) && !state[property].nil?
119
- end
120
-
121
- def validate_state(state = {})
122
- state[:uuid] = SecureRandom.hex(8) unless existing_state_value?(state, :uuid)
123
- state[:server_id] = "vm#{state[:uuid]}" unless existing_state_value?(state, :server_id)
124
- state[:azure_resource_group_name] = azure_resource_group_name unless existing_state_value?(state, :azure_resource_group_name)
125
- [:subscription_id, :username, :password, :vm_name, :azure_management_url].each do |config_element|
126
- state[config_element] = config[config_element] unless existing_state_value?(state, config_element)
127
- end
128
-
129
- state
130
- end
131
-
132
- def azure_resource_group_name
133
- formatted_time = Time.now.utc.strftime '%Y%m%dT%H%M%S'
134
- "#{config[:azure_resource_group_name]}-#{formatted_time}"
135
- end
136
-
137
- def template_for_transport_name
138
- template = JSON.parse(virtual_machine_deployment_template)
139
- if instance.transport.name.casecmp('winrm') == 0
140
- encoded_command = Base64.strict_encode64(enable_winrm_powershell_script)
141
- command = command_to_execute
142
- template['resources'].select { |h| h['type'] == 'Microsoft.Compute/virtualMachines' }.each do |resource|
143
- resource['properties']['osProfile']['customData'] = encoded_command
144
- end
145
- template['resources'] << JSON.parse(custom_script_extension_template(command))
146
- end
147
-
148
- if instance.transport.name.casecmp('ssh') == 0 && !instance.transport[:ssh_key].nil?
149
- info "Adding public key from #{File.expand_path(instance.transport[:ssh_key])}.pub to the deployment."
150
- public_key = public_key_for_deployment(File.expand_path(instance.transport[:ssh_key]))
151
- template['resources'].select { |h| h['type'] == 'Microsoft.Compute/virtualMachines' }.each do |resource|
152
- resource['properties']['osProfile']['linuxConfiguration'] = JSON.parse(custom_linux_configuration(public_key))
153
- end
154
- end
155
- template.to_json
156
- end
157
-
158
- def public_key_for_deployment(private_key_filename)
159
- if File.file?(private_key_filename) == false
160
- k = SSHKey.generate
161
-
162
- ::FileUtils.mkdir_p(File.dirname(private_key_filename))
163
-
164
- private_key_file = File.new(private_key_filename, 'w')
165
- private_key_file.syswrite(k.private_key)
166
- private_key_file.chmod(0600)
167
- private_key_file.close
168
-
169
- public_key_file = File.new("#{private_key_filename}.pub", 'w')
170
- public_key_file.syswrite(k.ssh_public_key)
171
- public_key_file.chmod(0600)
172
- public_key_file.close
173
-
174
- output = k.ssh_public_key
175
- else
176
- output = File.read("#{private_key_filename}.pub")
177
- end
178
- output
179
- end
180
-
181
- def deployment(parameters)
182
- template = template_for_transport_name
183
- deployment = ::Azure::ARM::Resources::Models::Deployment.new
184
- deployment.properties = ::Azure::ARM::Resources::Models::DeploymentProperties.new
185
- deployment.properties.mode = Azure::ARM::Resources::Models::DeploymentMode::Incremental
186
- deployment.properties.template = JSON.parse(template)
187
- deployment.properties.parameters = parameters_in_values_format(parameters)
188
- debug(deployment.properties.template)
189
- deployment
190
- end
191
-
192
- def parameters_in_values_format(parameters_in)
193
- parameters = parameters_in.map do |key, value|
194
- { key.to_sym => { 'value' => value } }
195
- end
196
- parameters.reduce(:merge!)
197
- end
198
-
199
- def follow_deployment_until_end_state(resource_group, deployment_name)
200
- end_provisioning_states = 'Canceled,Failed,Deleted,Succeeded'
201
- end_provisioning_state_reached = false
202
- until end_provisioning_state_reached
203
- list_outstanding_deployment_operations(resource_group, deployment_name)
204
- sleep 10
205
- deployment_provisioning_state = deployment_state(resource_group, deployment_name)
206
- end_provisioning_state_reached = end_provisioning_states.split(',').include?(deployment_provisioning_state)
207
- end
208
- info "Resource Template deployment reached end state of '#{deployment_provisioning_state}'."
209
- show_failed_operations(resource_group, deployment_name) if deployment_provisioning_state == 'Failed'
210
- end
211
-
212
- def show_failed_operations(resource_group, deployment_name)
213
- failed_operations = resource_management_client.deployment_operations.list(resource_group, deployment_name).value!
214
- failed_operations.body.value.each do |val|
215
- resource_code = val.properties.status_code
216
- raise val.properties.status_message.inspect.to_s if resource_code != 'OK'
217
- end
218
- end
219
-
220
- def list_outstanding_deployment_operations(resource_group, deployment_name)
221
- end_operation_states = 'Failed,Succeeded'
222
- deployment_operations = resource_management_client.deployment_operations.list(resource_group, deployment_name).value!
223
- deployment_operations.body.value.each do |val|
224
- resource_provisioning_state = val.properties.provisioning_state
225
- resource_name = val.properties.target_resource.resource_name
226
- resource_type = val.properties.target_resource.resource_type
227
- end_operation_state_reached = end_operation_states.split(',').include?(resource_provisioning_state)
228
- unless end_operation_state_reached
229
- info "Resource #{resource_type} '#{resource_name}' provisioning status is #{resource_provisioning_state}"
230
- end
231
- end
232
- end
233
-
234
- def deployment_state(resource_group, deployment_name)
235
- deployments = resource_management_client.deployments.get(resource_group, deployment_name).value!
236
- deployments.body.properties.provisioning_state
237
- end
238
-
239
- def destroy(state)
240
- return if state[:server_id].nil?
241
- credentials = Kitchen::Driver::Credentials.new.azure_credentials_for_subscription(state[:subscription_id])
242
- resource_management_client = ::Azure::ARM::Resources::ResourceManagementClient.new(credentials, state[:azure_management_url])
243
- resource_management_client.subscription_id = state[:subscription_id]
244
- begin
245
- info "Destroying Resource Group: #{state[:azure_resource_group_name]}"
246
- resource_management_client.resource_groups.begin_delete(state[:azure_resource_group_name]).value!
247
- info 'Destroy operation accepted and will continue in the background.'
248
- rescue ::MsRestAzure::AzureOperationError => operation_error
249
- info operation_error.body['error']
250
- raise operation_error
251
- end
252
- state.delete(:server_id)
253
- state.delete(:hostname)
254
- state.delete(:username)
255
- state.delete(:password)
256
- end
257
-
258
- def enable_winrm_powershell_script
259
- config[:winrm_powershell_script] || <<-PS1
260
- $cert = New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\\LocalMachine\\My
261
- $config = '@{CertificateThumbprint="' + $cert.Thumbprint + '"}'
262
- winrm create winrm/config/listener?Address=*+Transport=HTTPS $config
263
- winrm set winrm/config/service/auth '@{Basic="true";Kerberos="false";Negotiate="true";Certificate="false";CredSSP="true"}'
264
- New-NetFirewallRule -DisplayName "Windows Remote Management (HTTPS-In)" -Name "Windows Remote Management (HTTPS-In)" -Profile Any -LocalPort 5986 -Protocol TCP
265
- winrm set winrm/config/service '@{AllowUnencrypted="true"}'
266
- New-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)" -Name "Windows Remote Management (HTTP-In)" -Profile Any -LocalPort 5985 -Protocol TCP
267
- PS1
268
- end
269
-
270
- def command_to_execute
271
- 'copy /y c:\\\\azuredata\\\\customdata.bin c:\\\\azuredata\\\\customdata.ps1 && powershell.exe -ExecutionPolicy Unrestricted -Command \\"start-process powershell.exe -verb runas -argumentlist c:\\\\azuredata\\\\customdata.ps1\\"'
272
- end
273
-
274
- def custom_linux_configuration(public_key)
275
- <<-EOH
276
- {
277
- "disablePasswordAuthentication": "true",
278
- "ssh": {
279
- "publicKeys": [
280
- {
281
- "path": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
282
- "keyData": "#{public_key}"
283
- }
284
- ]
285
- }
286
- }
287
- EOH
288
- end
289
-
290
- def custom_script_extension_template(command)
291
- <<-EOH
292
- {
293
- "type": "Microsoft.Compute/virtualMachines/extensions",
294
- "name": "[concat(variables('vmName'),'/','enableWinRM')]",
295
- "apiVersion": "2015-05-01-preview",
296
- "location": "[variables('location')]",
297
- "dependsOn": [
298
- "[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]"
299
- ],
300
- "properties": {
301
- "publisher": "Microsoft.Compute",
302
- "type": "CustomScriptExtension",
303
- "typeHandlerVersion": "1.4",
304
- "settings": {
305
- "commandToExecute": "#{command}"
306
- }
307
- }
308
- }
309
- EOH
310
- end
311
-
312
- def virtual_machine_deployment_template
313
- <<-EOH
314
- {
315
- "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
316
- "contentVersion": "1.0.0.0",
317
- "parameters": {
318
- "location": {
319
- "type": "string",
320
- "metadata": {
321
- "description": "The location where the resources will be created."
322
- }
323
- },
324
- "vmSize": {
325
- "type": "string",
326
- "metadata": {
327
- "description": "The size of the VM to be created"
328
- }
329
- },
330
- "newStorageAccountName": {
331
- "type": "string",
332
- "metadata": {
333
- "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
334
- }
335
- },
336
- "adminUsername": {
337
- "type": "string",
338
- "metadata": {
339
- "description": "User name for the Virtual Machine."
340
- }
341
- },
342
- "adminPassword": {
343
- "type": "securestring",
344
- "metadata": {
345
- "description": "Password for the Virtual Machine."
346
- }
347
- },
348
- "dnsNameForPublicIP": {
349
- "type": "string",
350
- "metadata": {
351
- "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
352
- }
353
- },
354
- "imagePublisher": {
355
- "type": "string",
356
- "defaultValue": "Canonical",
357
- "metadata": {
358
- "description": "Publisher for the VM, e.g. Canonical, MicrosoftWindowsServer"
359
- }
360
- },
361
- "imageOffer": {
362
- "type": "string",
363
- "defaultValue": "UbuntuServer",
364
- "metadata": {
365
- "description": "Offer for the VM, e.g. UbuntuServer, WindowsServer."
366
- }
367
- },
368
- "imageSku": {
369
- "type": "string",
370
- "defaultValue": "14.04.3-LTS",
371
- "metadata": {
372
- "description": "Sku for the VM, e.g. 14.04.3-LTS"
373
- }
374
- },
375
- "imageVersion": {
376
- "type": "string",
377
- "defaultValue": "latest",
378
- "metadata": {
379
- "description": "Either a date or latest."
380
- }
381
- },
382
- "vmName": {
383
- "type": "string",
384
- "defaultValue": "vm",
385
- "metadata": {
386
- "description": "The vm name created inside of the resource group."
387
- }
388
- },
389
- "storageAccountType": {
390
- "type": "string",
391
- "defaultValue": "Standard_LRS",
392
- "metadata": {
393
- "description": "The type of storage to use (e.g. Standard_LRS or Premium_LRS)."
394
- }
395
- },
396
- "bootDiagnosticsEnabled": {
397
- "type": "string",
398
- "defaultValue": "true",
399
- "metadata": {
400
- "description": "Whether to enable (true) or disable (false) boot diagnostics. Default: true (requires Standard storage)."
401
- }
402
- }
403
- },
404
- "variables": {
405
- "location": "[parameters('location')]",
406
- "OSDiskName": "osdisk",
407
- "nicName": "nic",
408
- "addressPrefix": "10.0.0.0/16",
409
- "subnetName": "Subnet",
410
- "subnetPrefix": "10.0.0.0/24",
411
- "storageAccountType": "[parameters('storageAccountType')]",
412
- "publicIPAddressName": "publicip",
413
- "publicIPAddressType": "Dynamic",
414
- "vmStorageAccountContainerName": "vhds",
415
- "vmName": "[parameters('vmName')]",
416
- "vmSize": "[parameters('vmSize')]",
417
- "virtualNetworkName": "vnet",
418
- "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
419
- "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
420
- },
421
- "resources": [
422
- {
423
- "type": "Microsoft.Storage/storageAccounts",
424
- "name": "[parameters('newStorageAccountName')]",
425
- "apiVersion": "2015-05-01-preview",
426
- "location": "[variables('location')]",
427
- "properties": {
428
- "accountType": "[variables('storageAccountType')]"
429
- }
430
- },
431
- {
432
- "apiVersion": "2015-05-01-preview",
433
- "type": "Microsoft.Network/publicIPAddresses",
434
- "name": "[variables('publicIPAddressName')]",
435
- "location": "[variables('location')]",
436
- "properties": {
437
- "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
438
- "dnsSettings": {
439
- "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
440
- }
441
- }
442
- },
443
- {
444
- "apiVersion": "2015-05-01-preview",
445
- "type": "Microsoft.Network/virtualNetworks",
446
- "name": "[variables('virtualNetworkName')]",
447
- "location": "[variables('location')]",
448
- "properties": {
449
- "addressSpace": {
450
- "addressPrefixes": [
451
- "[variables('addressPrefix')]"
452
- ]
453
- },
454
- "subnets": [
455
- {
456
- "name": "[variables('subnetName')]",
457
- "properties": {
458
- "addressPrefix": "[variables('subnetPrefix')]"
459
- }
460
- }
461
- ]
462
- }
463
- },
464
- {
465
- "apiVersion": "2015-05-01-preview",
466
- "type": "Microsoft.Network/networkInterfaces",
467
- "name": "[variables('nicName')]",
468
- "location": "[variables('location')]",
469
- "dependsOn": [
470
- "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
471
- "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
472
- ],
473
- "properties": {
474
- "ipConfigurations": [
475
- {
476
- "name": "ipconfig1",
477
- "properties": {
478
- "privateIPAllocationMethod": "Dynamic",
479
- "publicIPAddress": {
480
- "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
481
- },
482
- "subnet": {
483
- "id": "[variables('subnetRef')]"
484
- }
485
- }
486
- }
487
- ]
488
- }
489
- },
490
- {
491
- "apiVersion": "2015-06-15",
492
- "type": "Microsoft.Compute/virtualMachines",
493
- "name": "[variables('vmName')]",
494
- "location": "[variables('location')]",
495
- "dependsOn": [
496
- "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
497
- "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
498
- ],
499
- "properties": {
500
- "hardwareProfile": {
501
- "vmSize": "[variables('vmSize')]"
502
- },
503
- "osProfile": {
504
- "computername": "[variables('vmName')]",
505
- "adminUsername": "[parameters('adminUsername')]",
506
- "adminPassword": "[parameters('adminPassword')]"
507
- },
508
- "storageProfile": {
509
- "imageReference": {
510
- "publisher": "[parameters('imagePublisher')]",
511
- "offer": "[parameters('imageOffer')]",
512
- "sku": "[parameters('imageSku')]",
513
- "version": "[parameters('imageVersion')]"
514
- },
515
- "osDisk": {
516
- "name": "osdisk",
517
- "vhd": {
518
- "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
519
- },
520
- "caching": "ReadWrite",
521
- "createOption": "FromImage"
522
- }
523
- },
524
- "networkProfile": {
525
- "networkInterfaces": [
526
- {
527
- "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
528
- }
529
- ]
530
- },
531
- "diagnosticsProfile": {
532
- "bootDiagnostics": {
533
- "enabled": "[parameters('bootDiagnosticsEnabled')]",
534
- "storageUri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
535
- }
536
- }
537
- }
538
- }
539
- ]
540
- }
541
- EOH
542
- end
543
- end
544
- end
545
- end
1
+ require 'kitchen'
2
+ require 'kitchen/driver/credentials'
3
+ require 'securerandom'
4
+ require 'azure_mgmt_resources'
5
+ require 'azure_mgmt_network'
6
+ require 'base64'
7
+ require 'sshkey'
8
+ require 'fileutils'
9
+
10
+ module Kitchen
11
+ module Driver
12
+ #
13
+ # Azurerm
14
+ #
15
+ class Azurerm < Kitchen::Driver::Base
16
+ attr_accessor :resource_management_client
17
+
18
+ default_config(:azure_resource_group_name) do |config|
19
+ "kitchen-#{config.instance.name}"
20
+ end
21
+
22
+ default_config(:image_urn) do |_config|
23
+ 'Canonical:UbuntuServer:14.04.3-LTS:latest'
24
+ end
25
+
26
+ default_config(:username) do |_config|
27
+ 'azure'
28
+ end
29
+
30
+ default_config(:password) do |_config|
31
+ 'P2ssw0rd'
32
+ end
33
+
34
+ default_config(:vm_name) do |_config|
35
+ 'vm'
36
+ end
37
+
38
+ default_config(:storage_account_type) do |_config|
39
+ 'Standard_LRS'
40
+ end
41
+
42
+ default_config(:boot_diagnostics_enabled) do |_config|
43
+ 'true'
44
+ end
45
+
46
+ default_config(:winrm_powershell_script) do |_config|
47
+ false
48
+ end
49
+
50
+ default_config(:azure_management_url) do |_config|
51
+ 'https://management.azure.com'
52
+ end
53
+
54
+ def create(state)
55
+ state = validate_state(state)
56
+
57
+ image_publisher, image_offer, image_sku, image_version = config[:image_urn].split(':', 4)
58
+ deployment_parameters = {
59
+ location: config[:location],
60
+ vmSize: config[:machine_size],
61
+ storageAccountType: config[:storage_account_type],
62
+ bootDiagnosticsEnabled: config[:boot_diagnostics_enabled],
63
+ newStorageAccountName: "storage#{state[:uuid]}",
64
+ adminUsername: state[:username],
65
+ adminPassword: state[:password],
66
+ dnsNameForPublicIP: "kitchen-#{state[:uuid]}",
67
+ imagePublisher: image_publisher,
68
+ imageOffer: image_offer,
69
+ imageSku: image_sku,
70
+ imageVersion: image_version,
71
+ vmName: state[:vm_name]
72
+ }
73
+
74
+ credentials = Kitchen::Driver::Credentials.new.azure_credentials_for_subscription(config[:subscription_id])
75
+ @resource_management_client = ::Azure::ARM::Resources::ResourceManagementClient.new(credentials)
76
+ @resource_management_client.subscription_id = config[:subscription_id]
77
+
78
+ # Create Resource Group
79
+ resource_group = ::Azure::ARM::Resources::Models::ResourceGroup.new
80
+ resource_group.location = config[:location]
81
+ begin
82
+ info "Creating Resource Group: #{state[:azure_resource_group_name]}"
83
+ resource_management_client.resource_groups.create_or_update(state[:azure_resource_group_name], resource_group).value!
84
+ rescue ::MsRestAzure::AzureOperationError => operation_error
85
+ error_message = if operation_error.body.nil? == true
86
+ operation_error.inspect
87
+ else
88
+ operation_error.body['error']
89
+ end
90
+ info error_message
91
+ raise error_message
92
+ end
93
+
94
+ # Execute deployment steps
95
+ begin
96
+ deployment_name = "deploy-#{state[:uuid]}"
97
+ info "Creating Deployment: #{deployment_name}"
98
+ resource_management_client.deployments.create_or_update(state[:azure_resource_group_name], deployment_name, deployment(deployment_parameters)).value!
99
+ rescue ::MsRestAzure::AzureOperationError => operation_error
100
+ rest_error = operation_error.body['error']
101
+ deployment_active = rest_error['code'] == 'DeploymentActive'
102
+ if deployment_active
103
+ info "Deployment for resource group #{state[:azure_resource_group_name]} is ongoing."
104
+ info "If you need to change the deployment template you'll need to rerun `kitchen create` for this instance."
105
+ else
106
+ info rest_error
107
+ raise operation_error
108
+ end
109
+ end
110
+
111
+ # Monitor all operations until completion
112
+ follow_deployment_until_end_state(state[:azure_resource_group_name], deployment_name)
113
+
114
+ # Now retrieve the public IP from the resource group:
115
+ network_management_client = ::Azure::ARM::Network::NetworkResourceProviderClient.new(credentials)
116
+ network_management_client.subscription_id = config[:subscription_id]
117
+ result = network_management_client.public_ip_addresses.get(state[:azure_resource_group_name], 'publicip').value!
118
+ info "IP Address is: #{result.body.properties.ip_address} [#{result.body.properties.dns_settings.fqdn}]"
119
+ state[:hostname] = result.body.properties.ip_address
120
+ end
121
+
122
+ def existing_state_value?(state, property)
123
+ state.key?(property) && !state[property].nil?
124
+ end
125
+
126
+ def validate_state(state = {})
127
+ state[:uuid] = SecureRandom.hex(8) unless existing_state_value?(state, :uuid)
128
+ state[:server_id] = "vm#{state[:uuid]}" unless existing_state_value?(state, :server_id)
129
+ state[:azure_resource_group_name] = azure_resource_group_name unless existing_state_value?(state, :azure_resource_group_name)
130
+ [:subscription_id, :username, :password, :vm_name, :azure_management_url].each do |config_element|
131
+ state[config_element] = config[config_element] unless existing_state_value?(state, config_element)
132
+ end
133
+
134
+ state
135
+ end
136
+
137
+ def azure_resource_group_name
138
+ formatted_time = Time.now.utc.strftime '%Y%m%dT%H%M%S'
139
+ "#{config[:azure_resource_group_name]}-#{formatted_time}"
140
+ end
141
+
142
+ def template_for_transport_name
143
+ template = JSON.parse(virtual_machine_deployment_template)
144
+ if instance.transport.name.casecmp('winrm') == 0
145
+ encoded_command = Base64.strict_encode64(enable_winrm_powershell_script)
146
+ command = command_to_execute
147
+ template['resources'].select { |h| h['type'] == 'Microsoft.Compute/virtualMachines' }.each do |resource|
148
+ resource['properties']['osProfile']['customData'] = encoded_command
149
+ end
150
+ template['resources'] << JSON.parse(custom_script_extension_template(command))
151
+ end
152
+
153
+ if instance.transport.name.casecmp('ssh') == 0 && !instance.transport[:ssh_key].nil?
154
+ info "Adding public key from #{File.expand_path(instance.transport[:ssh_key])}.pub to the deployment."
155
+ public_key = public_key_for_deployment(File.expand_path(instance.transport[:ssh_key]))
156
+ template['resources'].select { |h| h['type'] == 'Microsoft.Compute/virtualMachines' }.each do |resource|
157
+ resource['properties']['osProfile']['linuxConfiguration'] = JSON.parse(custom_linux_configuration(public_key))
158
+ end
159
+ end
160
+ template.to_json
161
+ end
162
+
163
+ def public_key_for_deployment(private_key_filename)
164
+ if File.file?(private_key_filename) == false
165
+ k = SSHKey.generate
166
+
167
+ ::FileUtils.mkdir_p(File.dirname(private_key_filename))
168
+
169
+ private_key_file = File.new(private_key_filename, 'w')
170
+ private_key_file.syswrite(k.private_key)
171
+ private_key_file.chmod(0600)
172
+ private_key_file.close
173
+
174
+ public_key_file = File.new("#{private_key_filename}.pub", 'w')
175
+ public_key_file.syswrite(k.ssh_public_key)
176
+ public_key_file.chmod(0600)
177
+ public_key_file.close
178
+
179
+ output = k.ssh_public_key
180
+ else
181
+ output = File.read("#{private_key_filename}.pub")
182
+ end
183
+ output
184
+ end
185
+
186
+ def deployment(parameters)
187
+ template = template_for_transport_name
188
+ deployment = ::Azure::ARM::Resources::Models::Deployment.new
189
+ deployment.properties = ::Azure::ARM::Resources::Models::DeploymentProperties.new
190
+ deployment.properties.mode = Azure::ARM::Resources::Models::DeploymentMode::Incremental
191
+ deployment.properties.template = JSON.parse(template)
192
+ deployment.properties.parameters = parameters_in_values_format(parameters)
193
+ debug(deployment.properties.template)
194
+ deployment
195
+ end
196
+
197
+ def parameters_in_values_format(parameters_in)
198
+ parameters = parameters_in.map do |key, value|
199
+ { key.to_sym => { 'value' => value } }
200
+ end
201
+ parameters.reduce(:merge!)
202
+ end
203
+
204
+ def follow_deployment_until_end_state(resource_group, deployment_name)
205
+ end_provisioning_states = 'Canceled,Failed,Deleted,Succeeded'
206
+ end_provisioning_state_reached = false
207
+ until end_provisioning_state_reached
208
+ list_outstanding_deployment_operations(resource_group, deployment_name)
209
+ sleep 10
210
+ deployment_provisioning_state = deployment_state(resource_group, deployment_name)
211
+ end_provisioning_state_reached = end_provisioning_states.split(',').include?(deployment_provisioning_state)
212
+ end
213
+ info "Resource Template deployment reached end state of '#{deployment_provisioning_state}'."
214
+ show_failed_operations(resource_group, deployment_name) if deployment_provisioning_state == 'Failed'
215
+ end
216
+
217
+ def show_failed_operations(resource_group, deployment_name)
218
+ failed_operations = resource_management_client.deployment_operations.list(resource_group, deployment_name).value!
219
+ failed_operations.body.value.each do |val|
220
+ resource_code = val.properties.status_code
221
+ raise val.properties.status_message.inspect.to_s if resource_code != 'OK'
222
+ end
223
+ end
224
+
225
+ def list_outstanding_deployment_operations(resource_group, deployment_name)
226
+ end_operation_states = 'Failed,Succeeded'
227
+ deployment_operations = resource_management_client.deployment_operations.list(resource_group, deployment_name).value!
228
+ deployment_operations.body.value.each do |val|
229
+ resource_provisioning_state = val.properties.provisioning_state
230
+ resource_name = val.properties.target_resource.resource_name
231
+ resource_type = val.properties.target_resource.resource_type
232
+ end_operation_state_reached = end_operation_states.split(',').include?(resource_provisioning_state)
233
+ unless end_operation_state_reached
234
+ info "Resource #{resource_type} '#{resource_name}' provisioning status is #{resource_provisioning_state}"
235
+ end
236
+ end
237
+ end
238
+
239
+ def deployment_state(resource_group, deployment_name)
240
+ deployments = resource_management_client.deployments.get(resource_group, deployment_name).value!
241
+ deployments.body.properties.provisioning_state
242
+ end
243
+
244
+ def destroy(state)
245
+ return if state[:server_id].nil?
246
+ credentials = Kitchen::Driver::Credentials.new.azure_credentials_for_subscription(state[:subscription_id])
247
+ resource_management_client = ::Azure::ARM::Resources::ResourceManagementClient.new(credentials, state[:azure_management_url])
248
+ resource_management_client.subscription_id = state[:subscription_id]
249
+ begin
250
+ info "Destroying Resource Group: #{state[:azure_resource_group_name]}"
251
+ resource_management_client.resource_groups.begin_delete(state[:azure_resource_group_name]).value!
252
+ info 'Destroy operation accepted and will continue in the background.'
253
+ rescue ::MsRestAzure::AzureOperationError => operation_error
254
+ info operation_error.body['error']
255
+ raise operation_error
256
+ end
257
+ state.delete(:server_id)
258
+ state.delete(:hostname)
259
+ state.delete(:username)
260
+ state.delete(:password)
261
+ end
262
+
263
+ def enable_winrm_powershell_script
264
+ config[:winrm_powershell_script] || <<-PS1
265
+ $cert = New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\\LocalMachine\\My
266
+ $config = '@{CertificateThumbprint="' + $cert.Thumbprint + '"}'
267
+ winrm create winrm/config/listener?Address=*+Transport=HTTPS $config
268
+ winrm set winrm/config/service/auth '@{Basic="true";Kerberos="false";Negotiate="true";Certificate="false";CredSSP="true"}'
269
+ New-NetFirewallRule -DisplayName "Windows Remote Management (HTTPS-In)" -Name "Windows Remote Management (HTTPS-In)" -Profile Any -LocalPort 5986 -Protocol TCP
270
+ winrm set winrm/config/service '@{AllowUnencrypted="true"}'
271
+ New-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)" -Name "Windows Remote Management (HTTP-In)" -Profile Any -LocalPort 5985 -Protocol TCP
272
+ PS1
273
+ end
274
+
275
+ def command_to_execute
276
+ 'copy /y c:\\\\azuredata\\\\customdata.bin c:\\\\azuredata\\\\customdata.ps1 && powershell.exe -ExecutionPolicy Unrestricted -Command \\"start-process powershell.exe -verb runas -argumentlist c:\\\\azuredata\\\\customdata.ps1\\"'
277
+ end
278
+
279
+ def custom_linux_configuration(public_key)
280
+ <<-EOH
281
+ {
282
+ "disablePasswordAuthentication": "true",
283
+ "ssh": {
284
+ "publicKeys": [
285
+ {
286
+ "path": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
287
+ "keyData": "#{public_key}"
288
+ }
289
+ ]
290
+ }
291
+ }
292
+ EOH
293
+ end
294
+
295
+ def custom_script_extension_template(command)
296
+ <<-EOH
297
+ {
298
+ "type": "Microsoft.Compute/virtualMachines/extensions",
299
+ "name": "[concat(variables('vmName'),'/','enableWinRM')]",
300
+ "apiVersion": "2015-05-01-preview",
301
+ "location": "[variables('location')]",
302
+ "dependsOn": [
303
+ "[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]"
304
+ ],
305
+ "properties": {
306
+ "publisher": "Microsoft.Compute",
307
+ "type": "CustomScriptExtension",
308
+ "typeHandlerVersion": "1.4",
309
+ "settings": {
310
+ "commandToExecute": "#{command}"
311
+ }
312
+ }
313
+ }
314
+ EOH
315
+ end
316
+
317
+ def virtual_machine_deployment_template
318
+ <<-EOH
319
+ {
320
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
321
+ "contentVersion": "1.0.0.0",
322
+ "parameters": {
323
+ "location": {
324
+ "type": "string",
325
+ "metadata": {
326
+ "description": "The location where the resources will be created."
327
+ }
328
+ },
329
+ "vmSize": {
330
+ "type": "string",
331
+ "metadata": {
332
+ "description": "The size of the VM to be created"
333
+ }
334
+ },
335
+ "newStorageAccountName": {
336
+ "type": "string",
337
+ "metadata": {
338
+ "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
339
+ }
340
+ },
341
+ "adminUsername": {
342
+ "type": "string",
343
+ "metadata": {
344
+ "description": "User name for the Virtual Machine."
345
+ }
346
+ },
347
+ "adminPassword": {
348
+ "type": "securestring",
349
+ "metadata": {
350
+ "description": "Password for the Virtual Machine."
351
+ }
352
+ },
353
+ "dnsNameForPublicIP": {
354
+ "type": "string",
355
+ "metadata": {
356
+ "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
357
+ }
358
+ },
359
+ "imagePublisher": {
360
+ "type": "string",
361
+ "defaultValue": "Canonical",
362
+ "metadata": {
363
+ "description": "Publisher for the VM, e.g. Canonical, MicrosoftWindowsServer"
364
+ }
365
+ },
366
+ "imageOffer": {
367
+ "type": "string",
368
+ "defaultValue": "UbuntuServer",
369
+ "metadata": {
370
+ "description": "Offer for the VM, e.g. UbuntuServer, WindowsServer."
371
+ }
372
+ },
373
+ "imageSku": {
374
+ "type": "string",
375
+ "defaultValue": "14.04.3-LTS",
376
+ "metadata": {
377
+ "description": "Sku for the VM, e.g. 14.04.3-LTS"
378
+ }
379
+ },
380
+ "imageVersion": {
381
+ "type": "string",
382
+ "defaultValue": "latest",
383
+ "metadata": {
384
+ "description": "Either a date or latest."
385
+ }
386
+ },
387
+ "vmName": {
388
+ "type": "string",
389
+ "defaultValue": "vm",
390
+ "metadata": {
391
+ "description": "The vm name created inside of the resource group."
392
+ }
393
+ },
394
+ "storageAccountType": {
395
+ "type": "string",
396
+ "defaultValue": "Standard_LRS",
397
+ "metadata": {
398
+ "description": "The type of storage to use (e.g. Standard_LRS or Premium_LRS)."
399
+ }
400
+ },
401
+ "bootDiagnosticsEnabled": {
402
+ "type": "string",
403
+ "defaultValue": "true",
404
+ "metadata": {
405
+ "description": "Whether to enable (true) or disable (false) boot diagnostics. Default: true (requires Standard storage)."
406
+ }
407
+ }
408
+ },
409
+ "variables": {
410
+ "location": "[parameters('location')]",
411
+ "OSDiskName": "osdisk",
412
+ "nicName": "nic",
413
+ "addressPrefix": "10.0.0.0/16",
414
+ "subnetName": "Subnet",
415
+ "subnetPrefix": "10.0.0.0/24",
416
+ "storageAccountType": "[parameters('storageAccountType')]",
417
+ "publicIPAddressName": "publicip",
418
+ "publicIPAddressType": "Dynamic",
419
+ "vmStorageAccountContainerName": "vhds",
420
+ "vmName": "[parameters('vmName')]",
421
+ "vmSize": "[parameters('vmSize')]",
422
+ "virtualNetworkName": "vnet",
423
+ "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
424
+ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
425
+ },
426
+ "resources": [
427
+ {
428
+ "type": "Microsoft.Storage/storageAccounts",
429
+ "name": "[parameters('newStorageAccountName')]",
430
+ "apiVersion": "2015-05-01-preview",
431
+ "location": "[variables('location')]",
432
+ "properties": {
433
+ "accountType": "[variables('storageAccountType')]"
434
+ }
435
+ },
436
+ {
437
+ "apiVersion": "2015-05-01-preview",
438
+ "type": "Microsoft.Network/publicIPAddresses",
439
+ "name": "[variables('publicIPAddressName')]",
440
+ "location": "[variables('location')]",
441
+ "properties": {
442
+ "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
443
+ "dnsSettings": {
444
+ "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
445
+ }
446
+ }
447
+ },
448
+ {
449
+ "apiVersion": "2015-05-01-preview",
450
+ "type": "Microsoft.Network/virtualNetworks",
451
+ "name": "[variables('virtualNetworkName')]",
452
+ "location": "[variables('location')]",
453
+ "properties": {
454
+ "addressSpace": {
455
+ "addressPrefixes": [
456
+ "[variables('addressPrefix')]"
457
+ ]
458
+ },
459
+ "subnets": [
460
+ {
461
+ "name": "[variables('subnetName')]",
462
+ "properties": {
463
+ "addressPrefix": "[variables('subnetPrefix')]"
464
+ }
465
+ }
466
+ ]
467
+ }
468
+ },
469
+ {
470
+ "apiVersion": "2015-05-01-preview",
471
+ "type": "Microsoft.Network/networkInterfaces",
472
+ "name": "[variables('nicName')]",
473
+ "location": "[variables('location')]",
474
+ "dependsOn": [
475
+ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
476
+ "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
477
+ ],
478
+ "properties": {
479
+ "ipConfigurations": [
480
+ {
481
+ "name": "ipconfig1",
482
+ "properties": {
483
+ "privateIPAllocationMethod": "Dynamic",
484
+ "publicIPAddress": {
485
+ "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
486
+ },
487
+ "subnet": {
488
+ "id": "[variables('subnetRef')]"
489
+ }
490
+ }
491
+ }
492
+ ]
493
+ }
494
+ },
495
+ {
496
+ "apiVersion": "2015-06-15",
497
+ "type": "Microsoft.Compute/virtualMachines",
498
+ "name": "[variables('vmName')]",
499
+ "location": "[variables('location')]",
500
+ "dependsOn": [
501
+ "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
502
+ "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
503
+ ],
504
+ "properties": {
505
+ "hardwareProfile": {
506
+ "vmSize": "[variables('vmSize')]"
507
+ },
508
+ "osProfile": {
509
+ "computername": "[variables('vmName')]",
510
+ "adminUsername": "[parameters('adminUsername')]",
511
+ "adminPassword": "[parameters('adminPassword')]"
512
+ },
513
+ "storageProfile": {
514
+ "imageReference": {
515
+ "publisher": "[parameters('imagePublisher')]",
516
+ "offer": "[parameters('imageOffer')]",
517
+ "sku": "[parameters('imageSku')]",
518
+ "version": "[parameters('imageVersion')]"
519
+ },
520
+ "osDisk": {
521
+ "name": "osdisk",
522
+ "vhd": {
523
+ "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
524
+ },
525
+ "caching": "ReadWrite",
526
+ "createOption": "FromImage"
527
+ }
528
+ },
529
+ "networkProfile": {
530
+ "networkInterfaces": [
531
+ {
532
+ "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
533
+ }
534
+ ]
535
+ },
536
+ "diagnosticsProfile": {
537
+ "bootDiagnostics": {
538
+ "enabled": "[parameters('bootDiagnosticsEnabled')]",
539
+ "storageUri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
540
+ }
541
+ }
542
+ }
543
+ }
544
+ ]
545
+ }
546
+ EOH
547
+ end
548
+ end
549
+ end
550
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-azurerm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stuart Preston
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-07 00:00:00.000000000 Z
11
+ date: 2016-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inifile