knife-azure 1.8.7 → 1.9.0

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