knife-azure 1.6.0 → 1.7.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -201
  3. data/README.md +37 -654
  4. data/lib/azure/resource_management/ARM_deployment_template.rb +87 -50
  5. data/lib/azure/resource_management/ARM_interface.rb +236 -516
  6. data/lib/azure/resource_management/vnet_config.rb +254 -0
  7. data/lib/azure/resource_management/windows_credentials.rb +109 -61
  8. data/lib/azure/service_management/ASM_interface.rb +17 -1
  9. data/lib/azure/service_management/certificate.rb +37 -13
  10. data/lib/azure/service_management/connection.rb +0 -0
  11. data/lib/azure/service_management/deploy.rb +0 -0
  12. data/lib/azure/service_management/disk.rb +0 -0
  13. data/lib/azure/service_management/host.rb +0 -0
  14. data/lib/azure/service_management/image.rb +0 -0
  15. data/lib/azure/service_management/rest.rb +0 -0
  16. data/lib/azure/service_management/role.rb +0 -0
  17. data/lib/azure/service_management/utility.rb +0 -0
  18. data/lib/chef/knife/azure_base.rb +100 -0
  19. data/lib/chef/knife/azure_image_list.rb +0 -0
  20. data/lib/chef/knife/azure_server_create.rb +0 -98
  21. data/lib/chef/knife/azure_server_delete.rb +0 -0
  22. data/lib/chef/knife/azure_server_list.rb +0 -0
  23. data/lib/chef/knife/azure_server_show.rb +0 -0
  24. data/lib/chef/knife/azurerm_base.rb +42 -9
  25. data/lib/chef/knife/azurerm_server_create.rb +31 -24
  26. data/lib/chef/knife/azurerm_server_delete.rb +1 -10
  27. data/lib/chef/knife/azurerm_server_list.rb +5 -1
  28. data/lib/chef/knife/azurerm_server_show.rb +5 -1
  29. data/lib/chef/knife/bootstrap/bootstrap_options.rb +12 -18
  30. data/lib/chef/knife/bootstrap/bootstrapper.rb +34 -15
  31. data/lib/chef/knife/bootstrap/common_bootstrap_options.rb +21 -24
  32. data/lib/chef/knife/bootstrap_azure.rb +58 -0
  33. data/lib/chef/knife/bootstrap_azurerm.rb +40 -50
  34. data/lib/knife-azure/version.rb +1 -1
  35. metadata +27 -12
@@ -39,7 +39,7 @@ module Azure
39
39
 
40
40
  def list_servers
41
41
  servers = connection.roles.all
42
- cols = ['DNS Name', 'VM Name', 'Status', 'IP Address', 'SSH Port', 'WinRM Port' ]
42
+ cols = ['DNS Name', 'VM Name', 'Status', 'IP Address', 'SSH Port', 'WinRM Port', 'RDP Port']
43
43
  rows = []
44
44
  servers.each do |server|
45
45
  rows << server.hostedservicename.to_s+".cloudapp.net" # Info about the DNS name at http://msdn.microsoft.com/en-us/library/ee460806.aspx
@@ -58,10 +58,26 @@ module Azure
58
58
  rows << server.publicipaddress.to_s
59
59
  rows << server.sshport.to_s
60
60
  rows << server.winrmport.to_s
61
+ ports = server.tcpports
62
+ rows << rdp_port(ports)
61
63
  end
62
64
  display_list(ui, cols, rows)
63
65
  end
64
66
 
67
+ def rdp_port(arr_ports)
68
+ if !arr_ports
69
+ return ''
70
+ end
71
+ if arr_ports.length > 0
72
+ arr_ports.each do |port|
73
+ if port['Name'] == "Remote Desktop"
74
+ return port['PublicPort']
75
+ end
76
+ end
77
+ end
78
+ return ''
79
+ end
80
+
65
81
  def find_server(params = {})
66
82
  server = connection.roles.find(params[:name], params = { :azure_dns_name => params[:azure_dns_name] })
67
83
  end
@@ -21,10 +21,12 @@ module Azure
21
21
  def initialize(connection)
22
22
  @connection=connection
23
23
  end
24
+
24
25
  def create(params)
25
26
  certificate = Certificate.new(@connection)
26
27
  certificate.create(params)
27
28
  end
29
+
28
30
  def add(certificate_data, certificate_password, certificate_format, dns_name)
29
31
  certificate = Certificate.new(@connection)
30
32
  certificate.add_certificate certificate_data, certificate_password, certificate_format, dns_name
@@ -35,7 +37,12 @@ module Azure
35
37
  azure_dns_name: azure_dns_name }
36
38
  certificate = Certificate.new(@connection)
37
39
  thumbprint = certificate.create_ssl_certificate(cert_params)
38
- end
40
+ end
41
+
42
+ def get_certificate(dns_name, fingerprint)
43
+ certificate = Certificate.new(@connection)
44
+ certificate.get_certificate(dns_name, fingerprint)
45
+ end
39
46
  end
40
47
  end
41
48
 
@@ -47,12 +54,14 @@ module Azure
47
54
  @connection = connection
48
55
  @certificate_version = 2 # cf. RFC 5280 - to make it a "v3" certificate
49
56
  end
57
+
50
58
  def create(params)
51
59
  # If RSA private key has been specified, then generate an x 509 certificate from the
52
60
  # public part of the key
53
61
  @cert_data = generate_public_key_certificate_data({:ssh_key => params[:identity_file],
54
62
  :ssh_key_passphrase => params[:identity_file_passphrase]})
55
63
  add_certificate @cert_data, 'knifeazure', 'pfx', params[:azure_dns_name]
64
+
56
65
  # Return the fingerprint to be used while adding role
57
66
  @fingerprint
58
67
  end
@@ -86,21 +95,36 @@ module Azure
86
95
  end
87
96
 
88
97
  def add_certificate certificate_data, certificate_password, certificate_format, dns_name
89
- # Generate XML to call the API
90
- # Add certificate to the hosted service
91
- builder = Nokogiri::XML::Builder.new do |xml|
92
- xml.CertificateFile('xmlns'=>'http://schemas.microsoft.com/windowsazure') {
93
- xml.Data certificate_data
94
- xml.CertificateFormat certificate_format
95
- xml.Password certificate_password
96
- }
97
- end
98
- # Windows Azure API call
99
- @connection.query_azure("hostedservices/#{dns_name}/certificates", "post", builder.to_xml)
98
+ # Generate XML to call the API
99
+ # Add certificate to the hosted service
100
+ builder = Nokogiri::XML::Builder.new do |xml|
101
+ xml.CertificateFile('xmlns'=>'http://schemas.microsoft.com/windowsazure') {
102
+ xml.Data certificate_data
103
+ xml.CertificateFormat certificate_format
104
+ xml.Password certificate_password
105
+ }
106
+ end
107
+ # Windows Azure API call
108
+ @connection.query_azure("hostedservices/#{dns_name}/certificates", "post", builder.to_xml)
109
+
110
+ # Check if certificate is available else raise error
111
+ for attempt in 0..4
112
+ Chef::Log.info "Waiting to get certificate ..."
113
+ res = get_certificate(dns_name, @fingerprint)
114
+ break if !res.empty?
115
+ if attempt == 4
116
+ raise "The certificate with thumbprint #{fingerprint} was not found."
117
+ else
118
+ sleep 5
119
+ end
120
+ end
100
121
  end
101
122
 
102
- ######## SSL certificate generation for knife-azure ssl bootstrap ######
123
+ def get_certificate(dns_name, fingerprint)
124
+ @connection.query_azure("hostedservices/#{dns_name}/certificates/sha1-#{fingerprint}", "get").search("Certificate")
125
+ end
103
126
 
127
+ ######## SSL certificate generation for knife-azure ssl bootstrap ######
104
128
  def create_ssl_certificate cert_params
105
129
  file_path = cert_params[:output_file].sub(/\.(\w+)$/,'')
106
130
  path = prompt_for_file_path
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -231,6 +231,7 @@ class Chef
231
231
  end
232
232
 
233
233
  def find_file(name)
234
+ name = ::File.expand_path(name)
234
235
  config_dir = Chef::Knife.chef_config_dir
235
236
  if File.exist? name
236
237
  file = name
@@ -244,6 +245,105 @@ class Chef
244
245
  end
245
246
  file
246
247
  end
248
+
249
+ def fetch_deployment
250
+ deployment_name = service.deployment_name(locate_config_value(:azure_dns_name))
251
+ deployment = service.deployment("hostedservices/#{locate_config_value(:azure_dns_name)}/deployments/#{deployment_name}")
252
+
253
+ deployment
254
+ end
255
+
256
+ def fetch_role
257
+ deployment = fetch_deployment
258
+
259
+ if deployment.at_css('Deployment Name') != nil
260
+ role_list_xml = deployment.css('RoleInstanceList RoleInstance')
261
+ role_list_xml.each do |role|
262
+ if role.at_css("RoleName").text == (locate_config_value(:azure_vm_name) || @name_args[0])
263
+ return role
264
+ end
265
+ end
266
+ end
267
+ return nil
268
+ end
269
+
270
+ def fetch_extension(role)
271
+ ext_list_xml = role.css("ResourceExtensionStatusList ResourceExtensionStatus")
272
+ return nil if ext_list_xml.nil?
273
+
274
+ ext_list_xml.each do |ext|
275
+ if ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.LinuxChefClient" || ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.ChefClient"
276
+ return ext
277
+ end
278
+ end
279
+ return nil
280
+ end
281
+
282
+ def fetch_substatus(extension)
283
+ return nil if extension.at_css("ExtensionSettingStatus SubStatusList SubStatus").nil?
284
+ substatus_list_xml = extension.css("ExtensionSettingStatus SubStatusList SubStatus")
285
+ substatus_list_xml.each do |substatus|
286
+ if substatus.at_css("Name").text == "Chef Client run logs"
287
+ return substatus
288
+ end
289
+ end
290
+ return nil
291
+ end
292
+
293
+ def fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
294
+ ## fetch server details ##
295
+ role = fetch_role
296
+ if role != nil
297
+ ## fetch Chef Extension details deployed on the server ##
298
+ ext = fetch_extension(role)
299
+ if ext != nil
300
+ ## fetch substatus field which contains the chef-client run logs ##
301
+ substatus = fetch_substatus(ext)
302
+ if substatus != nil
303
+ ## chef-client run logs becomes available ##
304
+ name = substatus.at_css("Name").text
305
+ status = substatus.at_css("Status").text
306
+ message = substatus.at_css("Message").text
307
+
308
+ ## printing the logs ##
309
+ puts "\n\n******** Please find the chef-client run details below ********\n\n"
310
+ print "----> chef-client run status: "
311
+ case status
312
+ when "Success"
313
+ ## chef-client run succeeded ##
314
+ color = :green
315
+ when "Error"
316
+ ## chef-client run failed ##
317
+ color = :red
318
+ when "Transitioning"
319
+ ## chef-client run did not complete within maximum timeout of 30 minutes ##
320
+ ## fetch whatever logs available under the chef-client.log file ##
321
+ color = :yellow
322
+ end
323
+ puts "#{ui.color(status, color, :bold)}"
324
+ puts "----> chef-client run logs: "
325
+ puts "\n#{message}\n" ## message field of substatus contains the chef-client run logs ##
326
+ else
327
+ ## unavailability of the substatus field indicates that chef-client run is not completed yet on the server ##
328
+ fetch_process_wait_time = ((Time.now - fetch_process_start_time) / 60).round
329
+ if fetch_process_wait_time <= fetch_process_wait_timeout ## wait for maximum 30 minutes until chef-client run logs becomes available ##
330
+ print "#{ui.color('.', :bold)}"
331
+ sleep 30
332
+ fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
333
+ else
334
+ ## wait time exceeded maximum threshold set for the wait timeout ##
335
+ ui.error "\nchef-client run logs could not be fetched since fetch process exceeded wait timeout of #{fetch_process_wait_timeout} minutes.\n"
336
+ end
337
+ end
338
+ else
339
+ ## Chef Extension could not be found ##
340
+ ui.error("Unable to find Chef extension under role #{locate_config_value(:azure_vm_name) || @name_args[0]}.")
341
+ end
342
+ else
343
+ ## server could not be found ##
344
+ ui.error("chef-client run logs could not be fetched since role #{locate_config_value(:azure_vm_name) || @name_args[0]} could not be found.")
345
+ end
346
+ end
247
347
  end
248
348
  end
249
349
  end
File without changes
@@ -391,97 +391,6 @@ class Chef
391
391
  return extension_status
392
392
  end
393
393
 
394
- def fetch_role
395
- deployment_name = service.deployment_name(locate_config_value(:azure_dns_name))
396
- deployment = service.deployment("hostedservices/#{locate_config_value(:azure_dns_name)}/deployments/#{deployment_name}")
397
-
398
- if deployment.at_css('Deployment Name') != nil
399
- role_list_xml = deployment.css('RoleInstanceList RoleInstance')
400
- role_list_xml.each do |role|
401
- if role.at_css("RoleName").text == locate_config_value(:azure_vm_name)
402
- return role
403
- end
404
- end
405
- end
406
- return nil
407
- end
408
-
409
- def fetch_extension(role)
410
- ext_list_xml = role.css("ResourceExtensionStatusList ResourceExtensionStatus")
411
- ext_list_xml.each do |ext|
412
- if ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.LinuxChefClient" || ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.ChefClient"
413
- return ext
414
- end
415
- end
416
- return nil
417
- end
418
-
419
- def fetch_substatus(extension)
420
- return nil if extension.at_css("ExtensionSettingStatus SubStatusList SubStatus").nil?
421
- substatus_list_xml = extension.css("ExtensionSettingStatus SubStatusList SubStatus")
422
- substatus_list_xml.each do |substatus|
423
- if substatus.at_css("Name").text == "Chef Client run logs"
424
- return substatus
425
- end
426
- end
427
- return nil
428
- end
429
-
430
- def fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
431
- ## fetch server details ##
432
- role = fetch_role
433
- if role != nil
434
- ## fetch Chef Extension details deployed on the server ##
435
- ext = fetch_extension(role)
436
- if ext != nil
437
- ## fetch substatus field which contains the chef-client run logs ##
438
- substatus = fetch_substatus(ext)
439
- if substatus != nil
440
- ## chef-client run logs becomes available ##
441
- name = substatus.at_css("Name").text
442
- status = substatus.at_css("Status").text
443
- message = substatus.at_css("Message").text
444
-
445
- ## printing the logs ##
446
- puts "\n\n******** Please find the chef-client run details below ********\n\n"
447
- print "----> chef-client run status: "
448
- case status
449
- when "Success"
450
- ## chef-client run succeeded ##
451
- color = :green
452
- when "Error"
453
- ## chef-client run failed ##
454
- color = :red
455
- when "Transitioning"
456
- ## chef-client run did not complete within maximum timeout of 30 minutes ##
457
- ## fetch whatever logs available under the chef-client.log file ##
458
- color = :yellow
459
- end
460
- puts "#{ui.color(status, color, :bold)}"
461
- puts "----> chef-client run logs: "
462
- puts "\n#{message}\n" ## message field of substatus contains the chef-client run logs ##
463
- else
464
- ## unavailability of the substatus field indicates that chef-client run is not completed yet on the server ##
465
- fetch_process_wait_time = ((Time.now - fetch_process_start_time) / 60).round
466
- if fetch_process_wait_time <= fetch_process_wait_timeout ## wait for maximum 30 minutes until chef-client run logs becomes available ##
467
- print "#{ui.color('.', :bold)}"
468
- sleep 30
469
- fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
470
- else
471
- ## wait time exceeded 30 minutes timeout ##
472
- ui.error "\nchef-client run logs could not be fetched since fetch process exceeded wait timeout of #{fetch_process_wait_timeout} minutes.\n"
473
- end
474
- end
475
- else
476
- ## Chef Extension could not be found ##
477
- ui.error("Unable to find Chef extension under role #{locate_config_value(:azure_vm_name)}.")
478
- end
479
- else
480
- ## server could not be found ##
481
- ui.error("chef-client run logs could not be fetched since role #{locate_config_value(:azure_vm_name)} could not be found.")
482
- end
483
- end
484
-
485
394
  def run
486
395
  $stdout.sync = true
487
396
 
@@ -561,13 +470,6 @@ class Chef
561
470
  exit 1
562
471
  end
563
472
 
564
- if (locate_config_value(:auto_update_client) || locate_config_value(:delete_chef_extension_config) || locate_config_value(:uninstall_chef_client)) && (locate_config_value(:bootstrap_protocol) != 'cloud-api')
565
- ui.error("--auto-update-client option works with --bootstrap-protocol cloud-api") if locate_config_value(:auto_update_client)
566
- ui.error("--delete-chef-extension-config option works with --bootstrap-protocol cloud-api") if locate_config_value(:delete_chef_extension_config)
567
- ui.error("--uninstall-chef-client option works with --bootstrap-protocol cloud-api") if locate_config_value(:uninstall_chef_client)
568
- exit 1
569
- end
570
-
571
473
  if locate_config_value(:extended_logs) && locate_config_value(:bootstrap_protocol) != 'cloud-api'
572
474
  ui.error("--extended-logs option works with --bootstrap-protocol cloud-api")
573
475
  exit 1
File without changes
File without changes
File without changes
@@ -21,6 +21,7 @@ require 'chef/knife'
21
21
  require 'azure/resource_management/ARM_interface'
22
22
  require 'mixlib/shellout'
23
23
  require 'time'
24
+ require 'json'
24
25
 
25
26
  class Chef
26
27
  class Knife
@@ -48,7 +49,7 @@ class Chef
48
49
  end
49
50
 
50
51
  def service
51
- details = authentication_details()
52
+ details = authentication_details
52
53
  details.update(:azure_subscription_id => locate_config_value(:azure_subscription_id))
53
54
  @service ||= begin
54
55
  service = Azure::ResourceManagement::ARMInterface.new(details)
@@ -86,30 +87,61 @@ class Chef
86
87
  end
87
88
 
88
89
  def authentication_details
89
- if(!locate_config_value(:azure_tenant_id).nil? && !locate_config_value(:azure_client_id).nil? && !locate_config_value(:azure_client_secret).nil?)
90
+ if(locate_config_value(:azure_tenant_id) && locate_config_value(:azure_client_id) && locate_config_value(:azure_client_secret))
90
91
  return {:azure_tenant_id => locate_config_value(:azure_tenant_id), :azure_client_id => locate_config_value(:azure_client_id), :azure_client_secret => locate_config_value(:azure_client_secret)}
91
92
  elsif Chef::Platform.windows?
92
93
  token_details = token_details_for_windows()
93
94
  else
94
95
  token_details = token_details_for_linux()
95
96
  end
96
- check_token_validity(token_details)
97
- return token_details
97
+ token_details = check_token_validity(token_details)
98
+ token_details
98
99
  end
99
100
 
100
101
  def token_details_for_linux
101
102
  home_dir = File.expand_path('~')
102
103
  file = File.read(home_dir + '/.azure/accessTokens.json')
103
- file = eval(file)
104
- token_details = {:tokentype => file[-1][:tokenType], :user => file[-1][:userId], :token => file[-1][:accessToken], :clientid => file[-1][:_clientId], :expiry_time => file[-1][:expiresOn], :refreshtoken => file[-1][:refreshToken]}
105
- return token_details
104
+ file = JSON.parse(file)
105
+ token_details = {:tokentype => file[-1]["tokenType"], :user => file[-1]["userId"], :token => file[-1]["accessToken"], :clientid => file[-1]["_clientId"], :expiry_time => file[-1]["expiresOn"], :refreshtoken => file[-1]["refreshToken"]}
106
+ token_details
106
107
  end
107
108
 
108
- def check_token_validity(token_details)
109
+ def is_token_valid?(token_details)
109
110
  time_difference = Time.parse(token_details[:expiry_time]) - Time.now.utc
110
111
  if time_difference <= 0
111
- raise "Token has expired, please run any azure command like azure vm list to get new token from refresh token else run azure login command"
112
+ return false
113
+ elsif time_difference <= 600 # 600sec = 10min
114
+ # This is required otherwise a long running command may fail inbetween if the token gets expired.
115
+ raise "Token will expire within 10 minutes. Please run 'azure login' command"
116
+ else
117
+ return true
118
+ end
119
+ end
120
+
121
+ def refresh_token
122
+ begin
123
+ ui.log("Authenticating...")
124
+ Mixlib::ShellOut.new("azure vm show 'knifetest@resourcegroup' testvm", :timeout => 30).run_command
125
+ rescue Mixlib::ShellOut::CommandTimeout
126
+ rescue Exception
127
+ raise "Token has expired. Please run 'azure login' command"
128
+ end
129
+ if Chef::Platform.windows?
130
+ token_details = token_details_for_windows()
131
+ else
132
+ token_details = token_details_for_linux()
133
+ end
134
+ token_details
135
+ end
136
+
137
+ def check_token_validity(token_details)
138
+ if !is_token_valid?(token_details)
139
+ token_details = refresh_token()
140
+ if !is_token_valid?(token_details)
141
+ raise "Token has expired. Please run 'azure login' command"
142
+ end
112
143
  end
144
+ token_details
113
145
  end
114
146
 
115
147
  def validate_azure_login
@@ -157,6 +189,7 @@ class Chef
157
189
  end
158
190
 
159
191
  def find_file(name)
192
+ name = ::File.expand_path(name)
160
193
  config_dir = Chef::Knife.chef_config_dir
161
194
  if File.exist? name
162
195
  file = name