knife-azure 1.8.7 → 1.9.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/azure/azure_interface.rb +79 -81
  3. data/lib/azure/custom_errors.rb +34 -35
  4. data/lib/azure/helpers.rb +43 -44
  5. data/lib/azure/resource_management/ARM_deployment_template.rb +679 -678
  6. data/lib/azure/resource_management/ARM_interface.rb +513 -515
  7. data/lib/azure/resource_management/vnet_config.rb +43 -43
  8. data/lib/azure/resource_management/windows_credentials.rb +181 -184
  9. data/lib/azure/service_management/ASM_interface.rb +309 -317
  10. data/lib/azure/service_management/ag.rb +16 -16
  11. data/lib/azure/service_management/certificate.rb +30 -31
  12. data/lib/azure/service_management/connection.rb +31 -31
  13. data/lib/azure/service_management/deploy.rb +40 -38
  14. data/lib/azure/service_management/disk.rb +14 -10
  15. data/lib/azure/service_management/host.rb +28 -24
  16. data/lib/azure/service_management/image.rb +23 -22
  17. data/lib/azure/service_management/loadbalancer.rb +12 -12
  18. data/lib/azure/service_management/rest.rb +20 -19
  19. data/lib/azure/service_management/role.rb +274 -273
  20. data/lib/azure/service_management/storageaccount.rb +29 -25
  21. data/lib/azure/service_management/utility.rb +6 -7
  22. data/lib/azure/service_management/vnet.rb +44 -44
  23. data/lib/chef/knife/azure_ag_create.rb +18 -18
  24. data/lib/chef/knife/azure_ag_list.rb +3 -3
  25. data/lib/chef/knife/azure_base.rb +56 -56
  26. data/lib/chef/knife/azure_image_list.rb +8 -10
  27. data/lib/chef/knife/azure_internal-lb_create.rb +15 -15
  28. data/lib/chef/knife/azure_internal-lb_list.rb +3 -3
  29. data/lib/chef/knife/azure_server_create.rb +49 -50
  30. data/lib/chef/knife/azure_server_delete.rb +22 -24
  31. data/lib/chef/knife/azure_server_list.rb +4 -4
  32. data/lib/chef/knife/azure_server_show.rb +5 -5
  33. data/lib/chef/knife/azure_vnet_create.rb +17 -17
  34. data/lib/chef/knife/azure_vnet_list.rb +3 -3
  35. data/lib/chef/knife/azurerm_base.rb +58 -60
  36. data/lib/chef/knife/azurerm_server_create.rb +23 -22
  37. data/lib/chef/knife/azurerm_server_delete.rb +30 -34
  38. data/lib/chef/knife/azurerm_server_list.rb +42 -42
  39. data/lib/chef/knife/azurerm_server_show.rb +1 -1
  40. data/lib/chef/knife/bootstrap/bootstrap_options.rb +7 -8
  41. data/lib/chef/knife/bootstrap/bootstrapper.rb +65 -65
  42. data/lib/chef/knife/bootstrap/common_bootstrap_options.rb +3 -4
  43. data/lib/chef/knife/bootstrap_azure.rb +13 -13
  44. data/lib/chef/knife/bootstrap_azurerm.rb +106 -106
  45. data/lib/knife-azure/version.rb +2 -2
  46. metadata +43 -76
  47. data/lib/azure/resource_management/ARM_base.rb +0 -29
@@ -1,515 +1,513 @@
1
- # License:: Apache License, Version 2.0
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
-
16
- require 'azure/azure_interface'
17
- require 'azure/resource_management/ARM_base'
18
- require 'azure/resource_management/ARM_deployment_template'
19
- require 'azure/resource_management/vnet_config'
20
- require 'azure_mgmt_resources'
21
- require 'azure_mgmt_compute'
22
- require 'azure_mgmt_storage'
23
- require 'azure_mgmt_network'
24
-
25
- module Azure
26
- class ResourceManagement
27
- class ARMInterface < AzureInterface
28
- include Azure::ARM::ARMBase
29
- include Azure::ARM::ARMDeploymentTemplate
30
- include Azure::ARM::VnetConfig
31
-
32
- include Azure::ARM::Resources
33
- include Azure::ARM::Resources::Models
34
-
35
- include Azure::ARM::Compute
36
- include Azure::ARM::Compute::Models
37
-
38
- include Azure::ARM::Storage
39
- include Azure::ARM::Storage::Models
40
-
41
- include Azure::ARM::Network
42
- include Azure::ARM::Network::Models
43
-
44
- attr_accessor :connection
45
-
46
- def initialize(params = {})
47
- if params[:azure_client_secret]
48
- token_provider = MsRestAzure::ApplicationTokenProvider.new(params[:azure_tenant_id], params[:azure_client_id], params[:azure_client_secret])
49
- else
50
- token_provider = MsRest::StringTokenProvider.new(params[:token],params[:tokentype])
51
- end
52
- @credentials = MsRest::TokenCredentials.new(token_provider)
53
- @azure_subscription_id = params[:azure_subscription_id]
54
- super
55
- end
56
-
57
- def resource_management_client
58
- @resource_management_client ||= begin
59
- resource_management_client = ResourceManagementClient.new(@credentials)
60
- resource_management_client.subscription_id = @azure_subscription_id
61
- resource_management_client
62
- end
63
- end
64
-
65
- def compute_management_client
66
- @compute_management_client ||= begin
67
- compute_management_client = ComputeManagementClient.new(@credentials)
68
- compute_management_client.subscription_id = @azure_subscription_id
69
- compute_management_client
70
- end
71
- end
72
-
73
- def storage_management_client
74
- @storage_management_client ||= begin
75
- storage_management_client = StorageManagementClient.new(@credentials)
76
- storage_management_client.subscription_id = @azure_subscription_id
77
- storage_management_client
78
- end
79
- end
80
-
81
- def network_resource_client
82
- @network_resource_client ||= begin
83
- network_resource_client = NetworkManagementClient.new(@credentials)
84
- network_resource_client.subscription_id = @azure_subscription_id
85
- network_resource_client
86
- end
87
- end
88
-
89
- def list_images
90
- end
91
-
92
- def list_servers(resource_group_name = nil)
93
- if resource_group_name.nil?
94
- servers = compute_management_client.virtual_machines.list_all
95
- else
96
- servers = compute_management_client.virtual_machines.list(resource_group_name)
97
- end
98
-
99
- cols = ['VM Name', 'Resource Group Name', 'Location', 'Provisioning State', 'OS Type']
100
- rows = []
101
-
102
- servers.each do |server|
103
- rows << server.name.to_s
104
- rows << server.id.split('/')[4].downcase
105
- rows << server.location.to_s
106
- rows << begin
107
- state = server.provisioning_state.to_s.downcase
108
- case state
109
- when 'failed'
110
- ui.color(state, :red)
111
- when 'succeeded'
112
- ui.color(state, :green)
113
- else
114
- ui.color(state, :yellow)
115
- end
116
- end
117
- rows << server.storage_profile.os_disk.os_type.to_s
118
- end
119
- display_list(ui, cols, rows)
120
- end
121
-
122
- def delete_server(resource_group_name, vm_name)
123
- server = compute_management_client.virtual_machines.get(resource_group_name, vm_name)
124
- if server && server.name == vm_name
125
- puts "\n\n"
126
- msg_pair(ui, 'VM Name', server.name)
127
- msg_pair(ui, 'VM Size', server.hardware_profile.vm_size)
128
- msg_pair(ui, 'VM OS', server.storage_profile.os_disk.os_type)
129
- puts "\n"
130
-
131
- begin
132
- ui.confirm('Do you really want to delete this server')
133
- rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception
134
- server = nil # Cleanup is implicitly performed in other cloud plugins
135
- exit!
136
- end
137
-
138
- ui.info 'Deleting ..'
139
-
140
- begin
141
- server_detail = compute_management_client.virtual_machines.delete(resource_group_name, vm_name)
142
- end until server_detail.nil?
143
-
144
- puts "\n"
145
- ui.warn "Deleted server #{vm_name}"
146
- end
147
- end
148
-
149
- def show_server(name, resource_group)
150
- server = find_server(resource_group, name)
151
- if server
152
- network_interface_name = server.network_profile.network_interfaces[0].id.split('/')[-1]
153
- network_interface_data = network_resource_client.network_interfaces.get(resource_group, network_interface_name)
154
- public_ip_id_data = network_interface_data.ip_configurations[0].public_ipaddress
155
- unless public_ip_id_data.nil?
156
- public_ip_name = public_ip_id_data.id.split('/')[-1]
157
- public_ip_data = network_resource_client.public_ipaddresses.get(resource_group, public_ip_name)
158
- else
159
- public_ip_data = nil
160
- end
161
-
162
- details = Array.new
163
- details << ui.color('Server Name', :bold, :cyan)
164
- details << server.name
165
-
166
- details << ui.color('Size', :bold, :cyan)
167
- details << server.hardware_profile.vm_size
168
-
169
- details << ui.color('Provisioning State', :bold, :cyan)
170
- details << server.provisioning_state
171
-
172
- details << ui.color('Location', :bold, :cyan)
173
- details << server.location
174
-
175
- details << ui.color('Publisher', :bold, :cyan)
176
- details << server.storage_profile.image_reference.publisher
177
-
178
- details << ui.color('Offer', :bold, :cyan)
179
- details << server.storage_profile.image_reference.offer
180
-
181
- details << ui.color('Sku', :bold, :cyan)
182
- details << server.storage_profile.image_reference.sku
183
-
184
- details << ui.color('Version', :bold, :cyan)
185
- details << server.storage_profile.image_reference.version
186
-
187
- details << ui.color('OS Type', :bold, :cyan)
188
- details << server.storage_profile.os_disk.os_type
189
-
190
- details << ui.color('Public IP address', :bold, :cyan)
191
- unless public_ip_data.nil?
192
- details << public_ip_data.ip_address
193
- else
194
- details << ' -- '
195
- end
196
-
197
- details << ui.color('FQDN', :bold, :cyan)
198
- unless public_ip_data.nil? or public_ip_data.dns_settings.nil?
199
- details << public_ip_data.dns_settings.fqdn
200
- else
201
- details << ' -- '
202
- end
203
-
204
- puts ui.list(details, :columns_across, 2)
205
- end
206
- end
207
-
208
- def find_server(resource_group, name)
209
- compute_management_client.virtual_machines.get(resource_group, name)
210
- end
211
-
212
- def virtual_machine_exist?(resource_group_name, vm_name)
213
- begin
214
- compute_management_client.virtual_machines.get(resource_group_name, vm_name)
215
- return true
216
- rescue MsRestAzure::AzureOperationError => error
217
- if error.body
218
- err_json = JSON.parse(error.response.body)
219
- if err_json['error']['code'] == "ResourceNotFound"
220
- return false
221
- else
222
- raise error
223
- end
224
- end
225
- end
226
- end
227
-
228
- def security_group_exist?(resource_group_name, security_group_name)
229
- begin
230
- network_resource_client.network_security_groups.get(resource_group_name, security_group_name)
231
- return true
232
- rescue MsRestAzure::AzureOperationError => error
233
- if error.body
234
- err_json = JSON.parse(error.response.body)
235
- if err_json['error']['code'] == "ResourceNotFound"
236
- return false
237
- else
238
- raise error
239
- end
240
- end
241
- end
242
- end
243
-
244
- def resource_group_exist?(resource_group_name)
245
- resource_management_client.resource_groups.check_existence(resource_group_name)
246
- end
247
-
248
- def platform(image_reference)
249
- @platform ||= begin
250
- if image_reference =~ /WindowsServer.*/
251
- platform = 'Windows'
252
- else
253
- platform = 'Linux'
254
- end
255
- platform
256
- end
257
- end
258
-
259
- def parse_substatus_code(code, index)
260
- code.split('/')[index]
261
- end
262
-
263
- def fetch_substatus(resource_group_name, virtual_machine_name, chef_extension_name)
264
- substatuses = compute_management_client.virtual_machine_extensions.get(
265
- resource_group_name,
266
- virtual_machine_name,
267
- chef_extension_name,
268
- 'instanceView'
269
- ).instance_view.substatuses
270
-
271
- return nil if substatuses.nil?
272
-
273
- substatuses.each do |substatus|
274
- if parse_substatus_code(substatus.code, 1) == 'Chef Client run logs'
275
- return substatus
276
- end
277
- end
278
-
279
- return nil
280
- end
281
-
282
- def fetch_chef_client_logs(resource_group_name, virtual_machine_name, chef_extension_name, fetch_process_start_time, fetch_process_wait_timeout = 30)
283
- ## fetch substatus field which contains the chef-client run logs ##
284
- substatus = fetch_substatus(resource_group_name, virtual_machine_name, chef_extension_name)
285
-
286
- unless substatus.nil?
287
- ## chef-client run logs becomes available ##
288
- status = parse_substatus_code(substatus.code, 2)
289
- message = substatus.message
290
-
291
- puts "\n\n******** Please find the chef-client run details below ********\n\n"
292
- print "----> chef-client run status: "
293
- case status
294
- when 'succeeded'
295
- ## chef-client run succeeded ##
296
- color = :green
297
- when 'failed'
298
- ## chef-client run failed ##
299
- color = :red
300
- when 'transitioning'
301
- ## chef-client run did not complete within maximum timeout of 30 minutes ##
302
- ## fetch whatever logs available under the chef-client.log file ##
303
- color = :yellow
304
- end
305
- puts "#{ui.color(status, color, :bold)}"
306
- puts "----> chef-client run logs: "
307
- puts "\n#{message}\n" ## message field of substatus contains the chef-client run logs ##
308
- else
309
- ## unavailability of the substatus field indicates that chef-client run is not completed yet on the server ##
310
- fetch_process_wait_time = ((Time.now - fetch_process_start_time) / 60).round
311
- if fetch_process_wait_time <= fetch_process_wait_timeout
312
- print "#{ui.color('.', :bold)}"
313
- sleep 30
314
- fetch_chef_client_logs(resource_group_name, virtual_machine_name, chef_extension_name, fetch_process_start_time, fetch_process_wait_timeout)
315
- else
316
- ## wait time exceeded 30 minutes timeout ##
317
- ui.error "\nchef-client run logs could not be fetched since fetch process exceeded wait timeout of #{fetch_process_wait_timeout} minutes.\n"
318
- end
319
- end
320
- end
321
-
322
- def create_server(params = {})
323
- platform(params[:azure_image_reference_offer])
324
- # resource group creation
325
- if resource_group_exist?(params[:azure_resource_group_name])
326
- ui.log("INFO:Resource Group #{params[:azure_resource_group_name]} already exist. Skipping its creation.")
327
- ui.log("INFO:Adding new VM #{params[:azure_vm_name]} to this resource group.")
328
- else
329
- ui.log("Creating ResourceGroup....\n\n")
330
- resource_group = create_resource_group(params)
331
- Chef::Log.info("ResourceGroup creation successfull.")
332
- Chef::Log.info("Resource Group name is: #{resource_group.name}")
333
- Chef::Log.info("Resource Group ID is: #{resource_group.id}")
334
- end
335
-
336
- # virtual machine creation
337
- if virtual_machine_exist?(params[:azure_resource_group_name], params[:azure_vm_name])
338
- ui.log("INFO:Virtual Machine #{params[:azure_vm_name]} already exist under the Resource Group #{params[:azure_resource_group_name]}. Exiting for now.")
339
- else
340
- params[:chef_extension_version] = params[:chef_extension_version].nil? ? get_latest_chef_extension_version(params) : params[:chef_extension_version]
341
- params[:vm_size] = get_vm_size(params[:azure_vm_size])
342
- params[:vnet_config] = create_vnet_config(
343
- params[:azure_resource_group_name],
344
- params[:azure_vnet_name],
345
- params[:azure_vnet_subnet_name]
346
- )
347
- if params[:tcp_endpoints]
348
- if @platform == 'Windows'
349
- params[:tcp_endpoints] = params[:tcp_endpoints] + ",3389"
350
- else
351
- params[:tcp_endpoints] = params[:tcp_endpoints] + ",22,16001"
352
- end
353
- random_no = rand(100..1000)
354
- params[:azure_sec_group_name] = params[:azure_vm_name] + '_sec_grp_' + random_no.to_s
355
- if security_group_exist?(params[:azure_resource_group_name], params[:azure_sec_group_name])
356
- random_no = rand(100..1000)
357
- params[:azure_sec_group_name] = params[:azure_vm_name] + '_sec_grp_' + random_no.to_s
358
- end
359
- end
360
-
361
- ui.log("Creating Virtual Machine....")
362
- deployment = create_virtual_machine_using_template(params)
363
- ui.log("Virtual Machine creation successfull.") unless deployment.nil?
364
-
365
- unless deployment.nil?
366
- ui.log("Deployment name is: #{deployment.name}")
367
- ui.log("Deployment ID is: #{deployment.id}")
368
- deployment.properties.dependencies.each do |deploy|
369
- if deploy.resource_type == "Microsoft.Compute/virtualMachines"
370
- if params[:chef_extension_public_param][:extendedLogs] == "true"
371
- print "\n\nWaiting for the first chef-client run on virtual machine #{deploy.resource_name}"
372
- fetch_chef_client_logs(params[:azure_resource_group_name],
373
- deploy.resource_name,
374
- params[:chef_extension],
375
- Time.now
376
- )
377
- end
378
-
379
- ui.log("VM Details ...")
380
- ui.log("-------------------------------")
381
- ui.log("Virtual Machine name is: #{deploy.resource_name}")
382
- ui.log("Virtual Machine ID is: #{deploy.id}")
383
- show_server(deploy.resource_name, params[:azure_resource_group_name])
384
- end
385
- end
386
- end
387
- end
388
- end
389
-
390
- def vm_public_ip(params = {})
391
- network_resource_client.public_ipaddresses.get(
392
- params[:azure_resource_group_name],
393
- params[:azure_vm_name]
394
- ).value!.body.properties.ip_address
395
- end
396
-
397
- def vm_default_port(params = {})
398
- network_resource_client.network_security_groups.get(
399
- params[:azure_resource_group_name],
400
- params[:azure_vm_name]
401
- ).value!.body.properties.security_rules[0].properties.destination_port_range
402
- end
403
-
404
- def create_resource_group(params = {})
405
- resource_group = ResourceGroup.new()
406
- resource_group.name = params[:azure_resource_group_name]
407
- resource_group.location = params[:azure_service_location]
408
-
409
- begin
410
- resource_group = resource_management_client.resource_groups.create_or_update(resource_group.name, resource_group)
411
- rescue Exception => e
412
- Chef::Log.error("Failed to create the Resource Group -- exception being rescued: #{e.to_s}")
413
- common_arm_rescue_block(e)
414
- end
415
-
416
- resource_group
417
- end
418
-
419
- def create_virtual_machine_using_template(params)
420
- template = create_deployment_template(params)
421
- parameters = create_deployment_parameters(params, @platform)
422
-
423
- deploy_prop = DeploymentProperties.new
424
- deploy_prop.template = template
425
- deploy_prop.parameters = parameters
426
- deploy_prop.mode = 'Incremental'
427
-
428
- deploy_params = Deployment.new
429
- deploy_params.properties = deploy_prop
430
-
431
- deployment = resource_management_client.deployments.create_or_update(params[:azure_resource_group_name], "#{params[:azure_vm_name]}_deploy", deploy_params)
432
- deployment
433
- end
434
-
435
- def create_vm_extension(params)
436
- vm_ext = VirtualMachineExtension.new
437
- vm_ext.name = params[:chef_extension]
438
- vm_ext.location = params[:azure_service_location]
439
- vm_ext.publisher = params[:chef_extension_publisher]
440
- vm_ext.virtual_machine_extension_type = params[:chef_extension]
441
- vm_ext.type_handler_version = params[:chef_extension_version].nil? ? get_latest_chef_extension_version(params) : params[:chef_extension_version]
442
- vm_ext.auto_upgrade_minor_version = false
443
- vm_ext.settings = params[:chef_extension_public_param]
444
- vm_ext.protected_settings = params[:chef_extension_private_param]
445
- begin
446
- vm_extension = compute_management_client.virtual_machine_extensions.create_or_update(
447
- params[:azure_resource_group_name],
448
- params[:azure_vm_name],
449
- vm_ext.name,
450
- vm_ext
451
- )
452
- rescue Exception => error
453
- Chef::Log.error("Failed to create the Virtual Machine Extension -- exception being rescued.")
454
- common_arm_rescue_block(error)
455
- end
456
-
457
- vm_extension
458
- end
459
-
460
- def extension_already_installed?(server)
461
- server.resources.each do |extension|
462
- return true if (extension.virtual_machine_extension_type == "ChefClient" || extension.virtual_machine_extension_type == "LinuxChefClient")
463
- end if server.resources
464
- false
465
- end
466
-
467
- def get_latest_chef_extension_version(params)
468
- ext_version = compute_management_client.virtual_machine_extension_images.list_versions(
469
- params[:azure_service_location],
470
- params[:chef_extension_publisher],
471
- params[:chef_extension]).last.name
472
- ext_version_split_values = ext_version.split(".")
473
- ext_version = ext_version_split_values[0] + "." + ext_version_split_values[1]
474
- ext_version
475
- end
476
-
477
- def delete_resource_group(resource_group_name)
478
- ui.info 'Resource group deletion takes some time. Please wait ...'
479
-
480
- begin
481
- server = resource_management_client.resource_groups.delete(resource_group_name)
482
- end until server.nil?
483
- puts "\n"
484
- end
485
-
486
- def common_arm_rescue_block(error)
487
- if error.class == MsRestAzure::AzureOperationError && error.body
488
- err_json = JSON.parse(error.response.body)
489
- err_details = err_json["error"]["details"] if err_json["error"]
490
- if err_details
491
- err_details.each do |err|
492
- begin
493
- ui.error(JSON.parse(err["message"])["error"]["message"])
494
- rescue JSON::ParserError => e
495
- ui.error(err["message"])
496
- end
497
- end
498
- else
499
- ui.error(err_json["error"]["message"])
500
- end
501
- Chef::Log.debug(error.response.body)
502
- else
503
- begin
504
- JSON.parse(error.message)
505
- Chef::Log.debug("#{error.message}")
506
- rescue JSON::ParserError => e
507
- ui.error("#{error.message}")
508
- end
509
- ui.error("Something went wrong. Please use -VV option for more details.")
510
- Chef::Log.debug("#{error.backtrace.join("\n")}")
511
- end
512
- end
513
- end
514
- end
515
- end
1
+ #
2
+ # Copyright:: Copyright 2018 Chef Software, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "azure/azure_interface"
19
+ require "azure/resource_management/ARM_deployment_template"
20
+ require "azure/resource_management/vnet_config"
21
+ require "azure_mgmt_resources"
22
+ require "azure_mgmt_compute"
23
+ require "azure_mgmt_storage"
24
+ require "azure_mgmt_network"
25
+
26
+ module Azure
27
+ class ResourceManagement
28
+ class ARMInterface < AzureInterface
29
+ include Azure::ARM::ARMDeploymentTemplate
30
+ include Azure::ARM::VnetConfig
31
+
32
+ include Azure::Resources::Mgmt::V2018_05_01
33
+ include Azure::Resources::Mgmt::V2018_05_01::Models
34
+
35
+ include Azure::Compute::Mgmt::V2018_06_01
36
+ include Azure::Compute::Mgmt::V2018_06_01::Models
37
+
38
+ include Azure::Storage::Mgmt::V2018_07_01
39
+ include Azure::Storage::Mgmt::V2018_07_01::Models
40
+
41
+ include Azure::Network::Mgmt::V2018_08_01
42
+ include Azure::Network::Mgmt::V2018_08_01::Models
43
+
44
+ attr_accessor :connection
45
+
46
+ def initialize(params = {})
47
+ if params[:azure_client_secret]
48
+ token_provider = MsRestAzure::ApplicationTokenProvider.new(params[:azure_tenant_id], params[:azure_client_id], params[:azure_client_secret])
49
+ else
50
+ token_provider = MsRest::StringTokenProvider.new(params[:token], params[:tokentype])
51
+ end
52
+ @credentials = MsRest::TokenCredentials.new(token_provider)
53
+ @azure_subscription_id = params[:azure_subscription_id]
54
+ super
55
+ end
56
+
57
+ def resource_management_client
58
+ @resource_management_client ||= begin
59
+ resource_management_client = ResourceManagementClient.new(@credentials)
60
+ resource_management_client.subscription_id = @azure_subscription_id
61
+ resource_management_client
62
+ end
63
+ end
64
+
65
+ def compute_management_client
66
+ @compute_management_client ||= begin
67
+ compute_management_client = ComputeManagementClient.new(@credentials)
68
+ compute_management_client.subscription_id = @azure_subscription_id
69
+ compute_management_client
70
+ end
71
+ end
72
+
73
+ def storage_management_client
74
+ @storage_management_client ||= begin
75
+ storage_management_client = StorageManagementClient.new(@credentials)
76
+ storage_management_client.subscription_id = @azure_subscription_id
77
+ storage_management_client
78
+ end
79
+ end
80
+
81
+ def network_resource_client
82
+ @network_resource_client ||= begin
83
+ network_resource_client = NetworkManagementClient.new(@credentials)
84
+ network_resource_client.subscription_id = @azure_subscription_id
85
+ network_resource_client
86
+ end
87
+ end
88
+
89
+ def list_images
90
+ end
91
+
92
+ def list_servers(resource_group_name = nil)
93
+ if resource_group_name.nil?
94
+ servers = compute_management_client.virtual_machines.list_all
95
+ else
96
+ servers = compute_management_client.virtual_machines.list(resource_group_name)
97
+ end
98
+
99
+ cols = ["VM Name", "Resource Group Name", "Location", "Provisioning State", "OS Type"]
100
+ rows = []
101
+
102
+ servers.each do |server|
103
+ rows << server.name.to_s
104
+ rows << server.id.split("/")[4].downcase
105
+ rows << server.location.to_s
106
+ rows << begin
107
+ state = server.provisioning_state.to_s.downcase
108
+ case state
109
+ when "failed"
110
+ ui.color(state, :red)
111
+ when "succeeded"
112
+ ui.color(state, :green)
113
+ else
114
+ ui.color(state, :yellow)
115
+ end
116
+ end
117
+ rows << server.storage_profile.os_disk.os_type.to_s
118
+ end
119
+ display_list(ui, cols, rows)
120
+ end
121
+
122
+ def delete_server(resource_group_name, vm_name)
123
+ server = compute_management_client.virtual_machines.get(resource_group_name, vm_name)
124
+ if server && server.name == vm_name
125
+ puts "\n\n"
126
+ msg_pair(ui, "VM Name", server.name)
127
+ msg_pair(ui, "VM Size", server.hardware_profile.vm_size)
128
+ msg_pair(ui, "VM OS", server.storage_profile.os_disk.os_type)
129
+ puts "\n"
130
+
131
+ begin
132
+ ui.confirm("Do you really want to delete this server")
133
+ rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception
134
+ server = nil # Cleanup is implicitly performed in other cloud plugins
135
+ exit!
136
+ end
137
+
138
+ ui.info "Deleting .."
139
+
140
+ begin
141
+ server_detail = compute_management_client.virtual_machines.delete(resource_group_name, vm_name)
142
+ end until server_detail.nil?
143
+
144
+ puts "\n"
145
+ ui.warn "Deleted server #{vm_name}"
146
+ end
147
+ end
148
+
149
+ def show_server(name, resource_group)
150
+ server = find_server(resource_group, name)
151
+ if server
152
+ network_interface_name = server.network_profile.network_interfaces[0].id.split("/")[-1]
153
+ network_interface_data = network_resource_client.network_interfaces.get(resource_group, network_interface_name)
154
+ public_ip_id_data = network_interface_data.ip_configurations[0].public_ipaddress
155
+ unless public_ip_id_data.nil?
156
+ public_ip_name = public_ip_id_data.id.split("/")[-1]
157
+ public_ip_data = network_resource_client.public_ipaddresses.get(resource_group, public_ip_name)
158
+ else
159
+ public_ip_data = nil
160
+ end
161
+
162
+ details = Array.new
163
+ details << ui.color("Server Name", :bold, :cyan)
164
+ details << server.name
165
+
166
+ details << ui.color("Size", :bold, :cyan)
167
+ details << server.hardware_profile.vm_size
168
+
169
+ details << ui.color("Provisioning State", :bold, :cyan)
170
+ details << server.provisioning_state
171
+
172
+ details << ui.color("Location", :bold, :cyan)
173
+ details << server.location
174
+
175
+ details << ui.color("Publisher", :bold, :cyan)
176
+ details << server.storage_profile.image_reference.publisher
177
+
178
+ details << ui.color("Offer", :bold, :cyan)
179
+ details << server.storage_profile.image_reference.offer
180
+
181
+ details << ui.color("Sku", :bold, :cyan)
182
+ details << server.storage_profile.image_reference.sku
183
+
184
+ details << ui.color("Version", :bold, :cyan)
185
+ details << server.storage_profile.image_reference.version
186
+
187
+ details << ui.color("OS Type", :bold, :cyan)
188
+ details << server.storage_profile.os_disk.os_type
189
+
190
+ details << ui.color("Public IP address", :bold, :cyan)
191
+ unless public_ip_data.nil?
192
+ details << public_ip_data.ip_address
193
+ else
194
+ details << " -- "
195
+ end
196
+
197
+ details << ui.color("FQDN", :bold, :cyan)
198
+ unless public_ip_data.nil? || public_ip_data.dns_settings.nil?
199
+ details << public_ip_data.dns_settings.fqdn
200
+ else
201
+ details << " -- "
202
+ end
203
+
204
+ puts ui.list(details, :columns_across, 2)
205
+ end
206
+ end
207
+
208
+ def find_server(resource_group, name)
209
+ compute_management_client.virtual_machines.get(resource_group, name)
210
+ end
211
+
212
+ def virtual_machine_exist?(resource_group_name, vm_name)
213
+ compute_management_client.virtual_machines.get(resource_group_name, vm_name)
214
+ true
215
+ rescue MsRestAzure::AzureOperationError => error
216
+ if error.body
217
+ err_json = JSON.parse(error.response.body)
218
+ if err_json["error"]["code"] == "ResourceNotFound"
219
+ return false
220
+ else
221
+ raise error
222
+ end
223
+ end
224
+ end
225
+
226
+ def security_group_exist?(resource_group_name, security_group_name)
227
+ network_resource_client.network_security_groups.get(resource_group_name, security_group_name)
228
+ true
229
+ rescue MsRestAzure::AzureOperationError => error
230
+ if error.body
231
+ err_json = JSON.parse(error.response.body)
232
+ if err_json["error"]["code"] == "ResourceNotFound"
233
+ return false
234
+ else
235
+ raise error
236
+ end
237
+ end
238
+ end
239
+
240
+ def resource_group_exist?(resource_group_name)
241
+ resource_management_client.resource_groups.check_existence(resource_group_name)
242
+ end
243
+
244
+ def platform(image_reference)
245
+ @platform ||= begin
246
+ if image_reference =~ /WindowsServer.*/
247
+ platform = "Windows"
248
+ else
249
+ platform = "Linux"
250
+ end
251
+ platform
252
+ end
253
+ end
254
+
255
+ def parse_substatus_code(code, index)
256
+ code.split("/")[index]
257
+ end
258
+
259
+ def fetch_substatus(resource_group_name, virtual_machine_name, chef_extension_name)
260
+ substatuses = compute_management_client.virtual_machine_extensions.get(
261
+ resource_group_name,
262
+ virtual_machine_name,
263
+ chef_extension_name,
264
+ expand: "instanceView"
265
+ ).instance_view.substatuses
266
+
267
+ return nil if substatuses.nil?
268
+
269
+ substatuses.each do |substatus|
270
+ if parse_substatus_code(substatus.code, 1) == "Chef Client run logs"
271
+ return substatus
272
+ end
273
+ end
274
+
275
+ nil
276
+ end
277
+
278
+ def fetch_chef_client_logs(resource_group_name, virtual_machine_name, chef_extension_name, fetch_process_start_time, fetch_process_wait_timeout = 30)
279
+ ## fetch substatus field which contains the chef-client run logs ##
280
+ substatus = fetch_substatus(resource_group_name, virtual_machine_name, chef_extension_name)
281
+
282
+ unless substatus.nil?
283
+ ## chef-client run logs becomes available ##
284
+ status = parse_substatus_code(substatus.code, 2)
285
+ message = substatus.message
286
+
287
+ puts "\n\n******** Please find the chef-client run details below ********\n\n"
288
+ print "----> chef-client run status: "
289
+ case status
290
+ when "succeeded"
291
+ ## chef-client run succeeded ##
292
+ color = :green
293
+ when "failed"
294
+ ## chef-client run failed ##
295
+ color = :red
296
+ when "transitioning"
297
+ ## chef-client run did not complete within maximum timeout of 30 minutes ##
298
+ ## fetch whatever logs available under the chef-client.log file ##
299
+ color = :yellow
300
+ end
301
+ puts "#{ui.color(status, color, :bold)}"
302
+ puts "----> chef-client run logs: "
303
+ puts "\n#{message}\n" ## message field of substatus contains the chef-client run logs ##
304
+ else
305
+ ## unavailability of the substatus field indicates that chef-client run is not completed yet on the server ##
306
+ fetch_process_wait_time = ((Time.now - fetch_process_start_time) / 60).round
307
+ if fetch_process_wait_time <= fetch_process_wait_timeout
308
+ print "#{ui.color('.', :bold)}"
309
+ sleep 30
310
+ fetch_chef_client_logs(resource_group_name, virtual_machine_name, chef_extension_name, fetch_process_start_time, fetch_process_wait_timeout)
311
+ else
312
+ ## wait time exceeded 30 minutes timeout ##
313
+ ui.error "\nchef-client run logs could not be fetched since fetch process exceeded wait timeout of #{fetch_process_wait_timeout} minutes.\n"
314
+ end
315
+ end
316
+ end
317
+
318
+ def create_server(params = {})
319
+ platform(params[:azure_image_reference_offer])
320
+ # resource group creation
321
+ if resource_group_exist?(params[:azure_resource_group_name])
322
+ ui.log("INFO:Resource Group #{params[:azure_resource_group_name]} already exist. Skipping its creation.")
323
+ ui.log("INFO:Adding new VM #{params[:azure_vm_name]} to this resource group.")
324
+ else
325
+ ui.log("Creating ResourceGroup....\n\n")
326
+ resource_group = create_resource_group(params)
327
+ Chef::Log.info("ResourceGroup creation successfull.")
328
+ Chef::Log.info("Resource Group name is: #{resource_group.name}")
329
+ Chef::Log.info("Resource Group ID is: #{resource_group.id}")
330
+ end
331
+
332
+ # virtual machine creation
333
+ if virtual_machine_exist?(params[:azure_resource_group_name], params[:azure_vm_name])
334
+ ui.log("INFO:Virtual Machine #{params[:azure_vm_name]} already exist under the Resource Group #{params[:azure_resource_group_name]}. Exiting for now.")
335
+ else
336
+ params[:chef_extension_version] = params[:chef_extension_version].nil? ? get_latest_chef_extension_version(params) : params[:chef_extension_version]
337
+ params[:vm_size] = params[:azure_vm_size]
338
+ params[:vnet_config] = create_vnet_config(
339
+ params[:azure_resource_group_name],
340
+ params[:azure_vnet_name],
341
+ params[:azure_vnet_subnet_name]
342
+ )
343
+ if params[:tcp_endpoints]
344
+ if @platform == "Windows"
345
+ params[:tcp_endpoints] = params[:tcp_endpoints] + ",3389"
346
+ else
347
+ params[:tcp_endpoints] = params[:tcp_endpoints] + ",22,16001"
348
+ end
349
+ random_no = rand(100..1000)
350
+ params[:azure_sec_group_name] = params[:azure_vm_name] + "_sec_grp_" + random_no.to_s
351
+ if security_group_exist?(params[:azure_resource_group_name], params[:azure_sec_group_name])
352
+ random_no = rand(100..1000)
353
+ params[:azure_sec_group_name] = params[:azure_vm_name] + "_sec_grp_" + random_no.to_s
354
+ end
355
+ end
356
+
357
+ ui.log("Creating Virtual Machine....")
358
+ deployment = create_virtual_machine_using_template(params)
359
+ ui.log("Virtual Machine creation successfull.") unless deployment.nil?
360
+
361
+ unless deployment.nil?
362
+ ui.log("Deployment name is: #{deployment.name}")
363
+ ui.log("Deployment ID is: #{deployment.id}")
364
+ deployment.properties.dependencies.each do |deploy|
365
+ if deploy.resource_type == "Microsoft.Compute/virtualMachines"
366
+ if params[:chef_extension_public_param][:extendedLogs] == "true"
367
+ print "\n\nWaiting for the first chef-client run on virtual machine #{deploy.resource_name}"
368
+ fetch_chef_client_logs(params[:azure_resource_group_name],
369
+ deploy.resource_name,
370
+ params[:chef_extension],
371
+ Time.now
372
+ )
373
+ end
374
+
375
+ ui.log("VM Details ...")
376
+ ui.log("-------------------------------")
377
+ ui.log("Virtual Machine name is: #{deploy.resource_name}")
378
+ ui.log("Virtual Machine ID is: #{deploy.id}")
379
+ show_server(deploy.resource_name, params[:azure_resource_group_name])
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ def vm_public_ip(params = {})
387
+ network_resource_client.public_ipaddresses.get(
388
+ params[:azure_resource_group_name],
389
+ params[:azure_vm_name]
390
+ ).value!.body.properties.ip_address
391
+ end
392
+
393
+ def vm_default_port(params = {})
394
+ network_resource_client.network_security_groups.get(
395
+ params[:azure_resource_group_name],
396
+ params[:azure_vm_name]
397
+ ).value!.body.properties.security_rules[0].properties.destination_port_range
398
+ end
399
+
400
+ def create_resource_group(params = {})
401
+ resource_group = ResourceGroup.new()
402
+ resource_group.name = params[:azure_resource_group_name]
403
+ resource_group.location = params[:azure_service_location]
404
+
405
+ begin
406
+ resource_group = resource_management_client.resource_groups.create_or_update(resource_group.name, resource_group)
407
+ rescue Exception => e
408
+ Chef::Log.error("Failed to create the Resource Group -- exception being rescued: #{e}")
409
+ common_arm_rescue_block(e)
410
+ end
411
+
412
+ resource_group
413
+ end
414
+
415
+ def create_virtual_machine_using_template(params)
416
+ template = create_deployment_template(params)
417
+ parameters = create_deployment_parameters(params, @platform)
418
+
419
+ deploy_prop = DeploymentProperties.new
420
+ deploy_prop.template = template
421
+ deploy_prop.parameters = parameters
422
+ deploy_prop.mode = "Incremental"
423
+
424
+ deploy_params = Deployment.new
425
+ deploy_params.properties = deploy_prop
426
+
427
+ deployment = resource_management_client.deployments.create_or_update(params[:azure_resource_group_name], "#{params[:azure_vm_name]}_deploy", deploy_params)
428
+ deployment
429
+ end
430
+
431
+ def create_vm_extension(params)
432
+ vm_ext = VirtualMachineExtension.new
433
+ vm_ext.name = params[:chef_extension]
434
+ vm_ext.location = params[:azure_service_location]
435
+ vm_ext.publisher = params[:chef_extension_publisher]
436
+ vm_ext.virtual_machine_extension_type = params[:chef_extension]
437
+ vm_ext.type_handler_version = params[:chef_extension_version].nil? ? get_latest_chef_extension_version(params) : params[:chef_extension_version]
438
+ vm_ext.auto_upgrade_minor_version = false
439
+ vm_ext.settings = params[:chef_extension_public_param]
440
+ vm_ext.protected_settings = params[:chef_extension_private_param]
441
+ begin
442
+ vm_extension = compute_management_client.virtual_machine_extensions.create_or_update(
443
+ params[:azure_resource_group_name],
444
+ params[:azure_vm_name],
445
+ vm_ext.name,
446
+ vm_ext
447
+ )
448
+ rescue Exception => error
449
+ Chef::Log.error("Failed to create the Virtual Machine Extension -- exception being rescued.")
450
+ common_arm_rescue_block(error)
451
+ end
452
+
453
+ vm_extension
454
+ end
455
+
456
+ def extension_already_installed?(server)
457
+ if server.resources
458
+ server.resources.each do |extension|
459
+ return true if extension.virtual_machine_extension_type == "ChefClient" || extension.virtual_machine_extension_type == "LinuxChefClient"
460
+ end
461
+ end
462
+ false
463
+ end
464
+
465
+ def get_latest_chef_extension_version(params)
466
+ ext_version = compute_management_client.virtual_machine_extension_images.list_versions(
467
+ params[:azure_service_location],
468
+ params[:chef_extension_publisher],
469
+ params[:chef_extension]).last.name
470
+ ext_version_split_values = ext_version.split(".")
471
+ ext_version = ext_version_split_values[0] + "." + ext_version_split_values[1]
472
+ ext_version
473
+ end
474
+
475
+ def delete_resource_group(resource_group_name)
476
+ ui.info "Resource group deletion takes some time. Please wait ..."
477
+
478
+ begin
479
+ server = resource_management_client.resource_groups.delete(resource_group_name)
480
+ end until server.nil?
481
+ puts "\n"
482
+ end
483
+
484
+ def common_arm_rescue_block(error)
485
+ if error.class == MsRestAzure::AzureOperationError && error.body
486
+ err_json = JSON.parse(error.response.body)
487
+ err_details = err_json["error"]["details"] if err_json["error"]
488
+ if err_details
489
+ err_details.each do |err|
490
+ begin
491
+ ui.error(JSON.parse(err["message"])["error"]["message"])
492
+ rescue JSON::ParserError => e
493
+ ui.error(err["message"])
494
+ end
495
+ end
496
+ else
497
+ ui.error(err_json["error"]["message"])
498
+ end
499
+ Chef::Log.debug(error.response.body)
500
+ else
501
+ begin
502
+ JSON.parse(error.message)
503
+ Chef::Log.debug("#{error.message}")
504
+ rescue JSON::ParserError => e
505
+ ui.error("#{error.message}")
506
+ end
507
+ ui.error("Something went wrong. Please use -VV option for more details.")
508
+ Chef::Log.debug("#{error.backtrace.join("\n")}")
509
+ end
510
+ end
511
+ end
512
+ end
513
+ end