kitchen-azurerm 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
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