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.
- 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
|