kitchen-azurerm 0.3.5 → 0.3.6

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: 8651b3f882557721799353ed9098d5a3f12b9e57
4
- data.tar.gz: c1bb1f88699542c1760eed48bb95065aa64e976b
3
+ metadata.gz: c5bf6aca89882e1148668e693edae5dc6e37d1f7
4
+ data.tar.gz: d4c53242bf99c9aa1d2a0bfb43e3bf694360b5b6
5
5
  SHA512:
6
- metadata.gz: c1e2ef35e30913b144048b6415bf6acca33ad163f88b855158d76cfb36cde02695b6d76dfdeda304bf6f94bf3c11ca5f003450e855bd76a5566f59c1d52f3fd3
7
- data.tar.gz: d9aa055ba5e111a9e8c66bac9f4c0972558bde77be98335c1ef974300598be58e1ae28b79c986a471af47c985ddcd7f1be3f3239618a4432f2cf421ca8cbba54
6
+ metadata.gz: 60be9d3b4755c863e35cc42446604d09c8707886cf8bd65c0fcfa43a6f58181cb0901b654301ef1a95ed425879167cbdc9a88da4e0589b4935bc4e88197778e5
7
+ data.tar.gz: f94c74f78b15c39b75544e152f04b1ce915ebf72272f972ce8f32af9c0596c36a36ef0bd66951f2ce5456db4a3a58920be0461c7527949a743558185896302b7
@@ -1,5 +1,8 @@
1
1
  # kitchen-azurerm Changelog
2
2
 
3
+ ## [0.3.6] - 2016-05-10
4
+ - Remove version pin on inifile gem dependency, compatible with newer ChefDK (@stuartpreston)
5
+
3
6
  ## [0.3.5] - 2016-03-21
4
7
  - Remove transport name restriction on SSH key upload (allow rsync support) (@stuartpreston)
5
8
  - Support SSH public keys with newlines as generated by ssh-keygen (@stuartpreston)
data/README.md CHANGED
@@ -25,7 +25,7 @@ You are now ready to configure kitchen-azurerm to use the credentials from the s
25
25
  1. **Subscription ID**: available from the Azure portal
26
26
  2. **Client ID**: this will be the Application Id from the application in step 2.
27
27
  3. **Client Secret/Password**: this will be the password you supplied in the command in step 2.
28
- 4. **Tenant ID**: listed after the command in step 5.
28
+ 4. **Tenant ID**: use the command detailed in "Manually provide credentials through Azure CLI" step 1 to get the TenantId.
29
29
 
30
30
  Using a text editor, open or create the file ```~/.azure/credentials``` and add the following section, noting there is one section per Subscription ID. **Make sure you save the file with UTF-8 encoding**
31
31
 
@@ -1,550 +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
- 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
- unless 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.strip
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
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
+ unless 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.strip
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,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-azurerm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.3.6
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-21 00:00:00.000000000 Z
11
+ date: 2016-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inifile
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: 3.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: 3.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: azure_mgmt_resources
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -182,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
182
  version: '0'
183
183
  requirements: []
184
184
  rubyforge_project:
185
- rubygems_version: 2.5.2
185
+ rubygems_version: 2.6.3
186
186
  signing_key:
187
187
  specification_version: 4
188
188
  summary: Test Kitchen driver for Azure Resource Manager.