knife-xapi 0.5.4 → 0.6.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.
@@ -18,7 +18,6 @@
18
18
  # limitations under the License.
19
19
  #
20
20
 
21
-
22
21
  require 'chef/knife/xapi_base'
23
22
 
24
23
  class Chef
@@ -27,136 +26,142 @@ class Chef
27
26
  require 'timeout'
28
27
  include Chef::Knife::XapiBase
29
28
 
30
- Chef::Knife::XapiBase.set_defaults( {
31
- :domain => "",
32
- :ssh_user => "root",
33
- :ssh_port => "22",
34
- :ping_timeout => 600,
35
- :install_repo => "http://isoredirect.centos.org/centos/6/os/x86_64/",
36
- :kernel_params => "graphical utf8",
37
- :xapi_disk_size => "8g",
38
- :xapi_cpus => "1",
39
- :xapi_mem => "1g",
40
- :bootstrap_template => "chef-full",
41
- :template_file => false,
42
- :run_list => [],
43
- :json_attributes => {}
44
- })
29
+ Chef::Knife::XapiBase.set_defaults(
30
+ domain: '',
31
+ ssh_user: 'root',
32
+ ssh_port: '22',
33
+ ping_timeout: 600,
34
+ install_repo: 'http://isoredirect.centos.org/centos/6/os/x86_64/',
35
+ kernel_params: 'graphical utf8',
36
+ xapi_disk_size: '8g',
37
+ xapi_cpus: '1',
38
+ xapi_mem: '1g',
39
+ bootstrap_template: 'chef-full',
40
+ template_file: false,
41
+ run_list: [],
42
+ json_attributes: {}
43
+ )
45
44
 
46
45
  deps do
47
46
  require 'chef/knife/bootstrap'
48
47
  Chef::Knife::Bootstrap.load_deps
49
48
  end
50
49
 
51
- banner "knife xapi guest create NAME [NETWORKS] (options)"
50
+ banner 'knife xapi guest create NAME [NETWORKS] (options)'
52
51
 
53
52
  option :xapi_vm_template,
54
- :short => "-T Template Name Label",
55
- :long => "--xapi-vm-template",
56
- :proc => Proc.new { |key| Chef::Config[:knife][:xapi_vm_template] = key },
57
- :description => "xapi template name to create from. accepts an string or regex"
53
+ short: '-T Template Name Label',
54
+ long: '--xapi-vm-template',
55
+ proc: proc { |key| Chef::Config[:knife][:xapi_vm_template] = key },
56
+ description: 'xapi template name to create from. accepts an string or regex'
58
57
 
59
58
  option :install_repo,
60
- :short => "-R If you're using a builtin template you will need to specify a repo url",
61
- :long => "--install-repo",
62
- :description => "Install repo for this template (if needed)",
63
- :proc => Proc.new { |key| Chef::Config[:knife][:install_repo] = key }
59
+ short: "-R If you're using a builtin template you will need to specify a repo url",
60
+ long: '--install-repo',
61
+ description: 'Install repo for this template (if needed)',
62
+ proc: proc { |key| Chef::Config[:knife][:install_repo] = key }
63
+
64
+ option :macaddress,
65
+ short: '-m MAC Address',
66
+ long: '--mac-address',
67
+ description: 'Use a pre-generated MAC address (optional)',
68
+ proc: proc { |key| Chef::Config[:knife][:macaddress] = key }
64
69
 
65
70
  option :xapi_sr,
66
- :short => "-S Storage repo to provision VM from",
67
- :long => "--xapi-sr",
68
- :proc => Proc.new { |key| Chef::Config[:knife][:xapi_sr] = key },
69
- :description => "The Xen SR to use, If blank will use pool/hypervisor default"
71
+ short: '-S Storage repo to provision VM from',
72
+ long: '--xapi-sr',
73
+ proc: proc { |key| Chef::Config[:knife][:xapi_sr] = key },
74
+ description: 'The Xen SR to use, If blank will use pool/hypervisor default'
70
75
 
71
76
  option :kernel_params,
72
- :short => "-B Set of kernel boot params to pass to the vm",
73
- :long => "--kernel-params",
74
- :description => "You can add more boot options to the vm e.g.: \"ks='http://foo.local/ks'\"",
75
- :proc => Proc.new { |key| Chef::Config[:knife][:kernel_params] = key }
77
+ short: '-B Set of kernel boot params to pass to the vm',
78
+ long: '--kernel-params',
79
+ description: "You can add more boot options to the vm e.g.: \"ks='http://foo.local/ks'\"",
80
+ proc: proc { |key| Chef::Config[:knife][:kernel_params] = key }
76
81
 
77
82
  option :xapi_skip_disk,
78
- :long => "--xapi-skip-disk",
79
- :proc => Proc.new { |key| Chef::Config[:knife][:xapi_skip_disk] = key },
80
- :description => "Don't try to add disks to the new VM"
83
+ long: '--xapi-skip-disk',
84
+ proc: proc { |key| Chef::Config[:knife][:xapi_skip_disk] = key },
85
+ description: "Don't try to add disks to the new VM"
81
86
 
82
87
  option :xapi_disk_size,
83
- :short => "-D Size of disk. 1g 512m etc",
84
- :long => "--xapi-disk-size",
85
- :description => "The size of the root disk, use 'm' 'g' 't' if no unit specified assumes g",
86
- :proc => Proc.new { |key| Chef::Config[:knife][:xapi_disk_size] = key.to_s }
88
+ short: '-D Size of disk. 1g 512m etc',
89
+ long: '--xapi-disk-size',
90
+ description: "The size of the root disk, use 'm' 'g' 't' if no unit specified assumes g",
91
+ proc: proc { |key| Chef::Config[:knife][:xapi_disk_size] = key.to_s }
87
92
 
88
93
  option :xapi_cpus,
89
- :short => "-C Number of VCPUs to provision",
90
- :long => "--xapi-cpus",
91
- :description => "Number of VCPUS this vm should have 1 4 8 etc",
92
- :proc => Proc.new { |key| Chef::Config[:knife][:xapi_cpus] = key.to_s }
94
+ short: '-C Number of VCPUs to provision',
95
+ long: '--xapi-cpus',
96
+ description: 'Number of VCPUS this vm should have 1 4 8 etc',
97
+ proc: proc { |key| Chef::Config[:knife][:xapi_cpus] = key.to_s }
93
98
 
94
99
  option :xapi_mem,
95
- :short => "-M Ammount of memory to provision",
96
- :long => "--xapi-mem",
97
- :description => "Ammount of memory the VM should have specify with m g etc 512m, 2g if no unit spcified it assumes gigabytes",
98
- :proc => Proc.new { |key| Chef::Config[:knife][:xapi_mem] = key.to_s }
100
+ short: '-M Ammount of memory to provision',
101
+ long: '--xapi-mem',
102
+ description: 'Ammount of memory the VM should have specify with m g etc 512m, 2g if no unit spcified it assumes gigabytes',
103
+ proc: proc { |key| Chef::Config[:knife][:xapi_mem] = key.to_s }
99
104
 
100
105
  option :chef_node_name,
101
- :short => "-N NAME",
102
- :long => "--node-name NAME",
103
- :description => "The Chef node name for your new node"
106
+ short: '-N NAME',
107
+ long: '--node-name NAME',
108
+ description: 'The Chef node name for your new node'
104
109
 
105
110
  option :ssh_key_name,
106
- :short => "-S KEY",
107
- :long => "--ssh-key KEY",
108
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_key_name] = key },
109
- :description => "The SSH key id"
111
+ short: '-S KEY',
112
+ long: '--ssh-key KEY',
113
+ proc: proc { |key| Chef::Config[:knife][:ssh_key_name] = key },
114
+ description: 'The SSH key id'
110
115
 
111
116
  option :ssh_user,
112
- :short => "-x USERNAME",
113
- :long => "--ssh-user USERNAME",
114
- :description => "The ssh username",
115
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_user] = key }
117
+ short: '-x USERNAME',
118
+ long: '--ssh-user USERNAME',
119
+ description: 'The ssh username',
120
+ proc: proc { |key| Chef::Config[:knife][:ssh_user] = key }
116
121
 
117
122
  option :ssh_password,
118
- :short => "-P PASSWORD",
119
- :long => "--ssh-password PASSWORD",
120
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_password] = key },
121
- :description => "The ssh password"
123
+ short: '-P PASSWORD',
124
+ long: '--ssh-password PASSWORD',
125
+ proc: proc { |key| Chef::Config[:knife][:ssh_password] = key },
126
+ description: 'The ssh password'
122
127
 
123
128
  option :ssh_port,
124
- :short => "-p PORT",
125
- :long => "--ssh-port PORT",
126
- :description => "The ssh port"
129
+ short: '-p PORT',
130
+ long: '--ssh-port PORT',
131
+ description: 'The ssh port'
127
132
 
128
133
  option :ping_timeout,
129
- :long => "--ping-timeout",
130
- :description => "Seconds to timeout waiting for ip from guest"
134
+ long: '--ping-timeout',
135
+ description: 'Seconds to timeout waiting for ip from guest'
131
136
 
132
137
  option :bootstrap_version,
133
- :long => "--bootstrap-version VERSION",
134
- :description => "The version of Chef to install"
138
+ long: '--bootstrap-version VERSION',
139
+ description: 'The version of Chef to install'
135
140
 
136
141
  option :bootstrap_template,
137
- :short => "-d Template Name",
138
- :long => "--bootstrap-template Template Name",
139
- :description => "Bootstrap using a specific template"
142
+ short: '-d Template Name',
143
+ long: '--bootstrap-template Template Name',
144
+ description: 'Bootstrap using a specific template'
140
145
 
141
146
  option :template_file,
142
- :short => "-F FILEPATH",
143
- :long => "--template-file TEMPLATE",
144
- :description => "Full path to location of template to use"
147
+ short: '-F FILEPATH',
148
+ long: '--template-file TEMPLATE',
149
+ description: 'Full path to location of template to use'
145
150
 
146
151
  option :json_attributes,
147
- :short => "-j JSON_ATTRIBS",
148
- :long => "--json-attributes",
149
- :description => "A JSON string to be added to the first run of chef-client",
150
- :proc => lambda { |o| JSON.parse(o) }
152
+ short: '-j JSON_ATTRIBS',
153
+ long: '--json-attributes',
154
+ description: 'A JSON string to be added to the first run of chef-client',
155
+ proc: lambda { |o| JSON.parse(o) }
151
156
 
152
157
  option :run_list,
153
- :short => "-r RUN_LIST",
154
- :long => "--run-list RUN_LIST",
155
- :description => "Comma separated list of roles/recipes to apply",
156
- :proc => lambda { |o| o.split(/[\s,]+/) }
158
+ short: '-r RUN_LIST',
159
+ long: '--run-list RUN_LIST',
160
+ description: 'Comma separated list of roles/recipes to apply',
161
+ proc: lambda { |o| o.split(/[\s,]+/) }
157
162
 
158
163
  def tcp_test_ssh(hostname)
159
- tcp_socket = TCPSocket.new(hostname, locate_config_value(:ssh_port) )
164
+ tcp_socket = TCPSocket.new(hostname, locate_config_value(:ssh_port))
160
165
  readable = IO.select([tcp_socket], nil, nil, 5)
161
166
  if readable
162
167
  Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
@@ -195,35 +200,53 @@ class Chef
195
200
  end
196
201
  end
197
202
 
198
- def pingable?(guest_ip, timeout=5)
203
+ def pingable?(guest_ip, timeout = 5)
199
204
  sleep(20)
200
205
  system "ping -c 1 -t #{timeout} #{guest_ip} >/dev/null"
201
206
  end
202
207
 
203
208
  def get_guest_ip(vm_ref)
204
- begin
205
- timeout( locate_config_value(:ping_timeout).to_i ) do
206
- ui.msg "Waiting for guest ip address"
207
- guest_ip = ""
208
- while guest_ip.empty?
209
- print(".")
210
- sleep @initial_sleep_delay ||= 10
211
- vgm = xapi.VM.get_guest_metrics(vm_ref)
212
- next if "OpaqueRef:NULL" == vgm
213
- networks = xapi.VM_guest_metrics.get_networks(vgm)
214
- if networks.has_key?("0/ip")
215
- guest_ip = networks["0/ip"]
216
- end
209
+ timeout(locate_config_value(:ping_timeout).to_i) do
210
+ ui.msg 'Waiting for guest ip address'
211
+ guest_ip = ''
212
+ while guest_ip.empty?
213
+ print('.')
214
+ sleep @initial_sleep_delay ||= 10
215
+ vgm = xapi.VM.get_guest_metrics(vm_ref)
216
+ next if 'OpaqueRef:NULL' == vgm
217
+ networks = xapi.VM_guest_metrics.get_networks(vgm)
218
+ if networks.key?('0/ip')
219
+ guest_ip = networks['0/ip']
217
220
  end
218
- puts "\n"
219
- return guest_ip
220
221
  end
221
- rescue Timeout::Error
222
- ui.msg "Timeout waiting for XAPI to report IP address "
222
+ puts "\n"
223
+ return guest_ip
223
224
  end
225
+ rescue Timeout::Error
226
+ ui.msg 'Timeout waiting for XAPI to report IP address '
224
227
  end
225
228
 
226
229
 
230
+ def resolve_sr_ref
231
+ ui_sr = locate_config_value(:xapi_sr)
232
+ sr_ref = nil
233
+ if locate_config_value(:xapi_sr)
234
+ if is_uuid?(ui_sr)
235
+ sr_ref = get_sr_by_uuid(ui_sr)
236
+ else
237
+ sr_ref = get_sr_by_name(ui_sr)
238
+ end
239
+ else
240
+ sr_ref = find_default_sr
241
+ end
242
+
243
+ if sr_ref.nil?
244
+ ui.error "SR specified not found or can't be used Aborting"
245
+ fail(vm_ref) if sr_ref.nil?
246
+ end
247
+ sr_ref
248
+ end
249
+
227
250
  def run
228
251
  server_name = @name_args[0]
229
252
  domainname = locate_config_value(:domain)
@@ -234,11 +257,18 @@ class Chef
234
257
  end
235
258
 
236
259
  # get the template vm we are going to build from
237
- template_ref = find_template( locate_config_value(:xapi_vm_template) )
238
-
239
- Chef::Log.debug "Cloning Guest from Template: #{h.color(template_ref, :bold, :cyan )}"
240
- task = xapi.Async.VM.clone(template_ref, fqdn)
241
- ui.msg "Waiting on Template Clone"
260
+ template_ref = find_template(locate_config_value(:xapi_vm_template))
261
+ sr_ref = resolve_sr_ref
262
+
263
+ if locate_config_value(:xapi_sr)
264
+ Chef::Log.debug "Copying Guest from Template: #{h.color(template_ref, :bold, :cyan)}"
265
+ task = xapi.Async.VM.copy(template_ref, fqdn, sr_ref)
266
+ else
267
+ Chef::Log.debug "Cloning Guest from Template: #{h.color(template_ref, :bold, :cyan)}"
268
+ task = xapi.Async.VM.clone(template_ref, fqdn)
269
+ end
270
+
271
+ ui.msg 'Waiting on Template Clone'
242
272
  vm_ref = get_task_ref(task)
243
273
 
244
274
  Chef::Log.debug "New VM ref: #{vm_ref}"
@@ -251,23 +281,25 @@ class Chef
251
281
  repo = locate_config_value(:install_repo)
252
282
 
253
283
  # make sure we don't clobber existing params
254
- other_config = Hash.new
284
+ other_config = {}
255
285
  record = xapi.VM.get_record(vm_ref)
256
- if record.has_key? "other_config"
257
- other_config = record["other_config"]
286
+ if record.key? 'other_config'
287
+ other_config = record['other_config']
258
288
  end
259
- other_config["install-repository"] = repo
260
- # for some reason the deb disks template is wonkey and has weird entry here
289
+ other_config['install-repository'] = repo
290
+
291
+ # remove any disk config/xml template might be trying to do (ubuntu)
261
292
  other_config.delete_if {|k,v| k=="disks"}
293
+
262
294
  Chef::Log.debug "Other_config: #{other_config.inspect}"
263
295
  xapi.VM.set_other_config(vm_ref, other_config)
264
296
 
265
- cpus = locate_config_value( :xapi_cpus ).to_s
297
+ cpus = locate_config_value(:xapi_cpus).to_s
266
298
 
267
- xapi.VM.set_VCPUs_max( vm_ref, cpus )
268
- xapi.VM.set_VCPUs_at_startup( vm_ref, cpus )
299
+ xapi.VM.set_VCPUs_max(vm_ref, cpus)
300
+ xapi.VM.set_VCPUs_at_startup(vm_ref, cpus)
269
301
 
270
- memory_size = input_to_bytes( locate_config_value(:xapi_mem) ).to_s
302
+ memory_size = input_to_bytes(locate_config_value(:xapi_mem)).to_s
271
303
  # static-min <= dynamic-min = dynamic-max = static-max
272
304
  xapi.VM.set_memory_limits(vm_ref, memory_size, memory_size, memory_size, memory_size)
273
305
 
@@ -283,100 +315,89 @@ class Chef
283
315
  boot_args << " domain=#{domainname}" unless boot_args.match(/domain=.+\s?/)
284
316
  boot_args << " dnsdomain=#{domainname}" unless boot_args.match(/dnsdomain=.+\s?/)
285
317
 
286
- xapi.VM.set_PV_args( vm_ref, boot_args )
318
+ xapi.VM.set_PV_args(vm_ref, boot_args)
287
319
 
288
320
  # TODO: validate that the vm gets a network here
289
321
  networks = @name_args[1..-1]
290
322
  # if the user has provided networks
291
323
  if networks.length >= 1
292
- clear_vm_vifs( xapi.VM.get_record( vm_ref ) )
324
+ clear_vm_vifs(xapi.VM.get_record(vm_ref))
293
325
  networks.each_with_index do |net, index|
294
326
  add_vif_by_name(vm_ref, index, net)
295
327
  end
296
328
  end
297
329
 
298
- unless locate_config_value(:xapi_skip_disk)
299
- sr_ref = nil
300
- if locate_config_value(:xapi_sr)
301
- sr_ref = get_sr_by_name( locate_config_value(:xapi_sr) )
302
- else
303
- sr_ref = find_default_sr
304
- end
305
-
306
- if sr_ref.nil?
307
- ui.error "SR specified not found or can't be used Aborting"
308
- fail(vm_ref) if sr_ref.nil?
309
- end
310
- Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
311
-
330
+ Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
312
331
 
332
+ unless locate_config_value(:xapi_skip_disk)
313
333
  disk_size = locate_config_value(:xapi_disk_size)
314
- # setup disks
315
- if disk_size != nil and disk_size.to_i > 0
316
- # when a template already has disks, we decide the position number based on it.
317
- position = xapi.VM.get_VBDs(vm_ref).length
334
+ # setup disks
335
+ if !disk_size.nil? && disk_size.to_i > 0
336
+ # when a template already has disks, we decide the position number based on it.
337
+ position = xapi.VM.get_VBDs(vm_ref).length
318
338
 
319
339
  # Create the VDI
320
- vdi_ref = create_vdi("#{server_name}-root", sr_ref, locate_config_value(:xapi_disk_size) )
340
+ vdi_ref = create_vdi("#{server_name}-root", sr_ref, locate_config_value(:xapi_disk_size))
321
341
  fail(vm_ref) unless vdi_ref
322
342
 
323
343
  # Attach the VDI to the VM
324
344
  # if its position is 0 set it bootable
325
- position == 0 ? bootable=true : bootable=false
345
+ position == 0 ? bootable = true : bootable = false
326
346
 
327
347
  vbd_ref = create_vbd(vm_ref, vdi_ref, position, bootable)
328
348
  fail(vm_ref) unless vbd_ref
329
349
  end
330
350
  end
331
351
 
332
- ui.msg "Provisioning new Guest: #{h.color(fqdn, :bold, :cyan )}"
333
- ui.msg "Boot Args: #{h.color boot_args,:bold, :cyan}"
334
- ui.msg "Install Repo: #{ h.color(repo,:bold, :cyan)}"
335
- ui.msg "Memory: #{ h.color( locate_config_value(:xapi_mem).to_s, :bold, :cyan)}"
336
- ui.msg "CPUs: #{ h.color( locate_config_value(:xapi_cpus).to_s, :bold, :cyan)}"
337
- ui.msg "Disk: #{ h.color( disk_size.to_s, :bold, :cyan)}"
352
+ ui.msg "Provisioning new Guest: #{h.color(fqdn, :bold, :cyan)}"
353
+ ui.msg "Boot Args: #{h.color boot_args, :bold, :cyan}"
354
+ ui.msg "Install Repo: #{ h.color(repo, :bold, :cyan)}"
355
+ ui.msg "Memory: #{ h.color(locate_config_value(:xapi_mem).to_s, :bold, :cyan)}"
356
+ ui.msg "CPUs: #{ h.color(locate_config_value(:xapi_cpus).to_s, :bold, :cyan)}"
357
+ ui.msg "Disk: #{ h.color(disk_size.to_s, :bold, :cyan)}"
338
358
  provisioned = xapi.VM.provision(vm_ref)
339
359
 
340
- ui.msg "Starting new Guest #{h.color( provisioned, :cyan)} "
360
+ ui.msg "Starting new Guest #{h.color(provisioned, :cyan)} "
341
361
  start(vm_ref)
342
362
 
343
363
  exit 0 unless locate_config_value(:run_list)
344
- rescue Exception => e
345
- ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}"
364
+ rescue => e
365
+ ui.msg "#{h.color 'ERROR:'} #{h.color(e.message, :red)}"
346
366
  # have to use join here to pass a string to highline
347
- puts "Nested backtrace:"
348
- ui.msg "#{h.color( e.backtrace.join("\n"), :yellow)}"
349
- fail(vm_ref)
367
+ puts 'Nested backtrace:'
368
+ ui.msg "#{h.color(e.backtrace.join("\n"), :yellow)}"
369
+ raise(vm_ref)
350
370
  end
351
371
 
352
372
  if locate_config_value(:run_list).empty?
353
- unless ( locate_config_value(:template_file) or locate_config_value(:bootstrap_template) )
373
+ unless locate_config_value(:template_file) or
374
+ locate_config_value(:bootstrap_template) != 'chef-full'
354
375
  exit 0
355
376
  end
356
377
  end
357
378
 
358
379
  guest_addr = wait_for_guest_ip(vm_ref)
359
- if guest_addr.nil? or guest_addr.empty?
360
- ui.msg("ip seems wrong using host+domain name instead")
380
+ if guest_addr.nil? || guest_addr.empty?
381
+ ui.msg('ip seems wrong using host+domain name instead')
361
382
  guest_addr = "#{server_name}.#{domainname}"
362
383
  end
363
384
  ui.msg "Trying to connect to guest @ #{guest_addr} "
364
385
 
365
386
  begin
366
387
  timeout(480) do
367
- print(".") until tcp_test_ssh(guest_addr) {
388
+ print('.') until tcp_test_ssh(guest_addr) do
368
389
  sleep @initial_sleep_delay ||= 10
369
- ui.msg( "#{ h.color "OK!", :green}" )
370
- }
390
+ ui.msg("#{ h.color 'OK!', :green}")
391
+ end
371
392
  end
372
393
  rescue Timeout::Error
373
- ui.msg "Timeout trying to login Wont bootstrap"
374
- fail
394
+ ui.msg 'Timeout trying to login Wont bootstrap'
395
+ raise
375
396
  end
376
397
 
377
398
  begin
378
399
  bootstrap = Chef::Knife::Bootstrap.new
379
- bootstrap.name_args = [ guest_addr ]
400
+ bootstrap.name_args = [guest_addr]
380
401
  bootstrap.config[:run_list] = locate_config_value(:run_list)
381
402
  bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
382
403
  bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
@@ -393,15 +414,13 @@ class Chef
393
414
  bootstrap.config[:run_list] = locate_config_value(:run_list)
394
415
 
395
416
  bootstrap.run
396
- rescue Exception => e
397
- ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}"
398
- puts "Nested backtrace:"
399
- ui.msg "#{h.color( e.backtrace.join("\n"), :yellow)}"
400
- fail
417
+ rescue => e
418
+ ui.msg "#{h.color 'ERROR:'} #{h.color(e.message, :red)}"
419
+ puts 'Nested backtrace:'
420
+ ui.msg "#{h.color(e.backtrace.join("\n"), :yellow)}"
421
+ raise
401
422
  end
402
423
  end
403
-
404
424
  end
405
425
  end
406
426
  end
407
-