knife-azure 1.6.0 → 1.7.0

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