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.
- checksums.yaml +4 -4
- data/LICENSE +201 -201
- data/README.md +37 -654
- data/lib/azure/resource_management/ARM_deployment_template.rb +87 -50
- data/lib/azure/resource_management/ARM_interface.rb +236 -516
- data/lib/azure/resource_management/vnet_config.rb +254 -0
- data/lib/azure/resource_management/windows_credentials.rb +109 -61
- data/lib/azure/service_management/ASM_interface.rb +17 -1
- data/lib/azure/service_management/certificate.rb +37 -13
- data/lib/azure/service_management/connection.rb +0 -0
- data/lib/azure/service_management/deploy.rb +0 -0
- data/lib/azure/service_management/disk.rb +0 -0
- data/lib/azure/service_management/host.rb +0 -0
- data/lib/azure/service_management/image.rb +0 -0
- data/lib/azure/service_management/rest.rb +0 -0
- data/lib/azure/service_management/role.rb +0 -0
- data/lib/azure/service_management/utility.rb +0 -0
- data/lib/chef/knife/azure_base.rb +100 -0
- data/lib/chef/knife/azure_image_list.rb +0 -0
- data/lib/chef/knife/azure_server_create.rb +0 -98
- data/lib/chef/knife/azure_server_delete.rb +0 -0
- data/lib/chef/knife/azure_server_list.rb +0 -0
- data/lib/chef/knife/azure_server_show.rb +0 -0
- data/lib/chef/knife/azurerm_base.rb +42 -9
- data/lib/chef/knife/azurerm_server_create.rb +31 -24
- data/lib/chef/knife/azurerm_server_delete.rb +1 -10
- data/lib/chef/knife/azurerm_server_list.rb +5 -1
- data/lib/chef/knife/azurerm_server_show.rb +5 -1
- data/lib/chef/knife/bootstrap/bootstrap_options.rb +12 -18
- data/lib/chef/knife/bootstrap/bootstrapper.rb +34 -15
- data/lib/chef/knife/bootstrap/common_bootstrap_options.rb +21 -24
- data/lib/chef/knife/bootstrap_azure.rb +58 -0
- data/lib/chef/knife/bootstrap_azurerm.rb +40 -50
- data/lib/knife-azure/version.rb +1 -1
- 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
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
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(
|
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
|
-
|
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 =
|
104
|
-
token_details = {:tokentype => file[-1][
|
105
|
-
|
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
|
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
|
-
|
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
|