test-kitchen 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,12 +6,9 @@ This release process applies to all Test Kitchen projects, but each project may
6
6
  2. Check out the master branch of the project being prepared for release.
7
7
  3. Branch into a release-branch of the form `150_release_prep`.
8
8
  4. Modify the `version.rb` file to specify the version for releasing.
9
- 5. Update the changelog to include what is being released.
10
- 1. For these projects we use [PimpMyChangelog](https://github.com/pcreux/pimpmychangelog). All this does is make the CHANGELOG look pretty - for now we manually import the PRs / Issues and categorize them.
11
- 2. Start a [diff](https://github.com/test-kitchen/test-kitchen/compare/v1.4.2...master) in Github between master and the last release
12
- 3. Look for all the merged PRs and add them to the CHANGELOG.md under the appropriate category. They should have the form `* PR #999: PR Description (@author)`.
13
- 4. Install and run `pimpmychangelog`. This should change all the PR numbers and @ mentions into links.
9
+ 5. Run `rake changelog` to regenerate the changelog.
14
10
  6. `git commit` the `version.rb` and `CHANGELOG.md` changes to the branch and setup a PR for them. Allow the PR to run any automated tests and review the CHANGELOG for accuracy.
15
11
  7. Merge the PR to master after review.
16
- 8. Switch your local copy to the master branch and `git pull` to pull in the release preperation changes.
12
+ 8. Switch your local copy to the master branch and `git pull` to pull in the release preparation changes.
17
13
  9. Run `rake release` on the master branch.
14
+ 10. Modify the `version.rb` file and bump the patch or minor version, and commit/push.
data/Rakefile CHANGED
@@ -65,3 +65,12 @@ task :deploy_over_dk do
65
65
  end
66
66
 
67
67
  task :dk_install => [:deploy_over_dk, :install]
68
+
69
+ require "github_changelog_generator/task"
70
+
71
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
72
+ config.future_release = Kitchen::VERSION
73
+ config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature".split(",")
74
+ config.bug_labels = "bug,Bug,Improvement".split(",")
75
+ config.exclude_labels = %w[Duplicate Question Discussion]
76
+ end
@@ -19,8 +19,12 @@
19
19
  require "kitchen/command"
20
20
 
21
21
  require "rubygems/spec_fetcher"
22
- require "chef-config/config"
23
- require "chef-config/workstation_config_loader"
22
+ begin
23
+ require "chef-config/config"
24
+ require "chef-config/workstation_config_loader"
25
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
26
+ # This space left intentionally blank.
27
+ end
24
28
 
25
29
  module Kitchen
26
30
 
@@ -36,8 +40,10 @@ module Kitchen
36
40
  # We are introducing the idea of using the Chef configuration as a
37
41
  # unified config for all the ChefDK tools. The first practical
38
42
  # implementation of this is 1 location to setup proxy configurations.
39
- ChefConfig::WorkstationConfigLoader.new(options[:chef_config_path]).load
40
- ChefConfig::Config.export_proxies
43
+ if defined?(ChefConfig::WorkstationConfigLoader)
44
+ ChefConfig::WorkstationConfigLoader.new(options[:chef_config_path]).load
45
+ end
46
+ ChefConfig::Config.export_proxies if defined?(ChefConfig::Config.export_proxies)
41
47
 
42
48
  specs = fetch_gem_specs.sort { |x, y| x[0] <=> y[0] }
43
49
  specs = specs[0, 49].push(["...", "..."]) if specs.size > 49
@@ -123,6 +123,10 @@ module Kitchen
123
123
  raise ClientError, "#{self.class}#destroy must be implemented"
124
124
  end
125
125
 
126
+ def legacy_state(state)
127
+ backcompat_merged_state(state)
128
+ end
129
+
126
130
  # (see Base#login_command)
127
131
  def login_command(state)
128
132
  instance.transport.connection(backcompat_merged_state(state)).
@@ -391,6 +391,20 @@ module Kitchen
391
391
  self
392
392
  end
393
393
 
394
+ # returns true, if the verifier is busser
395
+ def verifier_busser?(verifier)
396
+ !defined?(Kitchen::Verifier::Busser).nil? && verifier.is_a?(Kitchen::Verifier::Busser)
397
+ end
398
+
399
+ # returns true, if the verifier is dummy
400
+ def verifier_dummy?(verifier)
401
+ !defined?(Kitchen::Verifier::Dummy).nil? && verifier.is_a?(Kitchen::Verifier::Dummy)
402
+ end
403
+
404
+ def use_legacy_ssh_verifier?(verifier)
405
+ verifier_busser?(verifier) || verifier_dummy?(verifier)
406
+ end
407
+
394
408
  # Perform the verify action.
395
409
  #
396
410
  # @see Driver::Base#verify
@@ -399,8 +413,12 @@ module Kitchen
399
413
  def verify_action
400
414
  banner "Verifying #{to_str}..."
401
415
  elapsed = action(:verify) do |state|
402
- if legacy_ssh_base_driver?
416
+ # use special handling for legacy driver
417
+ if legacy_ssh_base_driver? && use_legacy_ssh_verifier?(verifier)
403
418
  legacy_ssh_base_verify(state)
419
+ elsif legacy_ssh_base_driver?
420
+ # read ssh options from legacy driver
421
+ verifier.call(driver.legacy_state(state))
404
422
  else
405
423
  verifier.call(state)
406
424
  end
@@ -44,13 +44,19 @@ module Kitchen
44
44
  raise ClientError, "Platform#new requires option :name"
45
45
  end
46
46
  @os_type = options.fetch(:os_type) do
47
- @name.downcase =~ /^win/ ? "windows" : "unix"
47
+ windows?(options) ? "windows" : "unix"
48
48
  end
49
49
  @shell_type = options.fetch(:shell_type) do
50
- @name.downcase =~ /^win/ ? "powershell" : "bourne"
50
+ windows?(options) ? "powershell" : "bourne"
51
51
  end
52
52
  end
53
53
 
54
+ def windows?(options)
55
+ @name.downcase =~ /^win/ || (
56
+ !options[:transport].nil? && options[:transport][:name] == "winrm"
57
+ )
58
+ end
59
+
54
60
  # Returns a Hash of configuration and other useful diagnostic information.
55
61
  #
56
62
  # @return [Hash] a diagnostic hash
@@ -205,10 +205,18 @@ module Kitchen
205
205
  # Conditionally prefixes a command with a sudo command.
206
206
  #
207
207
  # @param command [String] command to be prefixed
208
- # @return [String] the command, conditionaly prefixed with sudo
208
+ # @return [String] the command, conditionally prefixed with sudo
209
209
  # @api private
210
210
  def sudo(script)
211
- config[:sudo] ? "#{config[:sudo_command]} #{script}" : script
211
+ "#{sudo_command} #{script}".lstrip
212
+ end
213
+
214
+ # Returns the sudo command to use or empty string if sudo is not configured
215
+ #
216
+ # @return [String] the sudo command if sudo config is true
217
+ # @api private
218
+ def sudo_command
219
+ config[:sudo] ? config[:sudo_command].to_s : ""
212
220
  end
213
221
 
214
222
  # Conditionally prefixes a command with a command prefix.
@@ -0,0 +1,125 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: SAWANOBORI Yukihiko <sawanoboriyu@higanworks.com>)
4
+ #
5
+ # Copyright (C) 2015, HiganWorks LLC
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ # Usage:
20
+ #
21
+ # puts your recipes to` apply/` directory.
22
+ #
23
+ # An example of .kitchen.yml.
24
+ #
25
+ # ---
26
+ # driver:
27
+ # name: vagrant
28
+ #
29
+ # provisioner:
30
+ # name: chef_apply
31
+ #
32
+ # platforms:
33
+ # - name: ubuntu-12.04
34
+ # - name: centos-6.4
35
+ #
36
+ # suites:
37
+ # - name: default
38
+ # run_list:
39
+ # - recipe1
40
+ # - recipe2
41
+ #
42
+ #
43
+ # The chef-apply runs twice below.
44
+ #
45
+ # chef-apply apply/recipe1.rb
46
+ # chef-apply apply/recipe2.rb
47
+
48
+ require "kitchen/provisioner/chef_base"
49
+
50
+ module Kitchen
51
+
52
+ module Provisioner
53
+
54
+ # Chef Apply provisioner.
55
+ #
56
+ # @author SAWANOBORI Yukihiko <sawanoboriyu@higanworks.com>)
57
+ class ChefApply < ChefBase
58
+
59
+ kitchen_provisioner_api_version 2
60
+
61
+ plugin_version Kitchen::VERSION
62
+
63
+ default_config :chef_apply_path do |provisioner|
64
+ provisioner.
65
+ remote_path_join(%W[#{provisioner[:chef_omnibus_root]} bin chef-apply]).
66
+ tap { |path| path.concat(".bat") if provisioner.windows_os? }
67
+ end
68
+
69
+ default_config :apply_path do |provisioner|
70
+ provisioner.calculate_path("apply")
71
+ end
72
+ expand_path_for :apply_path
73
+
74
+ # (see ChefBase#create_sandbox)
75
+ def create_sandbox
76
+ @sandbox_path = Dir.mktmpdir("#{instance.name}-sandbox-")
77
+ File.chmod(0755, sandbox_path)
78
+ info("Preparing files for transfer")
79
+ debug("Creating local sandbox in #{sandbox_path}")
80
+
81
+ prepare_json
82
+ prepare(:apply)
83
+ end
84
+
85
+ # (see ChefBase#init_command)
86
+ def init_command
87
+ dirs = %w[
88
+ apply
89
+ ].sort.map { |dir| remote_path_join(config[:root_path], dir) }
90
+
91
+ vars = if powershell_shell?
92
+ init_command_vars_for_powershell(dirs)
93
+ else
94
+ init_command_vars_for_bourne(dirs)
95
+ end
96
+
97
+ prefix_command(shell_code_from_file(vars, "chef_base_init_command"))
98
+ end
99
+
100
+ # (see ChefSolo#run_command)
101
+ def run_command
102
+ level = config[:log_level] == :info ? :auto : config[:log_level]
103
+
104
+ lines = []
105
+ config[:run_list].map do |recipe|
106
+ cmd = sudo(config[:chef_apply_path]).dup.
107
+ tap { |str| str.insert(0, "& ") if powershell_shell? }
108
+ args = [
109
+ "apply/#{recipe}.rb",
110
+ "--log_level #{level}",
111
+ "--no-color"
112
+ ]
113
+ args << "--logfile #{config[:log_file]}" if config[:log_file]
114
+
115
+ lines << wrap_shell_code(
116
+ [cmd, *args].join(" ").
117
+ tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
118
+ )
119
+ end
120
+
121
+ prefix_command(lines.join("\n"))
122
+ end
123
+ end
124
+ end
125
+ end
@@ -26,8 +26,12 @@ require "kitchen/provisioner/chef/common_sandbox"
26
26
  require "kitchen/provisioner/chef/librarian"
27
27
  require "kitchen/util"
28
28
  require "mixlib/install"
29
- require "chef-config/config"
30
- require "chef-config/workstation_config_loader"
29
+ begin
30
+ require "chef-config/config"
31
+ require "chef-config/workstation_config_loader"
32
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
33
+ # This space left intentionally blank.
34
+ end
31
35
 
32
36
  module Kitchen
33
37
 
@@ -96,10 +100,12 @@ module Kitchen
96
100
  def initialize(config = {})
97
101
  super(config)
98
102
 
99
- ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load
103
+ if defined?(ChefConfig::WorkstationConfigLoader)
104
+ ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load
105
+ end
100
106
  # This exports any proxy config present in the Chef config to
101
107
  # appropriate environment variables, which Test Kitchen respects
102
- ChefConfig::Config.export_proxies
108
+ ChefConfig::Config.export_proxies if defined?(ChefConfig::Config.export_proxies)
103
109
  end
104
110
 
105
111
  # (see Base#create_sandbox)
@@ -149,7 +155,8 @@ module Kitchen
149
155
  {
150
156
  :omnibus_url => config[:chef_omnibus_url],
151
157
  :project => project.nil? ? nil : project[1],
152
- :install_flags => config[:chef_omnibus_install_options]
158
+ :install_flags => config[:chef_omnibus_install_options],
159
+ :sudo_command => sudo_command
153
160
  }.tap do |opts|
154
161
  opts[:root] = config[:chef_omnibus_root] if config.key? :chef_omnibus_root
155
162
  opts[:http_proxy] = config[:http_proxy] if config.key? :http_proxy
@@ -46,7 +46,7 @@ module Kitchen
46
46
  default_config :connection_retries, 5
47
47
  default_config :connection_retry_sleep, 1
48
48
  default_config :max_wait_until_ready, 600
49
- default_config :winrm_transport, "plaintext"
49
+ default_config :winrm_transport, :negotiate
50
50
  default_config :port do |transport|
51
51
  transport[:winrm_transport] == :ssl ? 5986 : 5985
52
52
  end
@@ -58,18 +58,7 @@ module Kitchen
58
58
  def finalize_config!(instance)
59
59
  super
60
60
 
61
- transport = config[:winrm_transport].to_sym
62
- config[:winrm_transport] =
63
- case transport
64
- when :sspinegotiate
65
- if host_os_windows?
66
- transport
67
- else
68
- :plaintext
69
- end
70
- else
71
- transport
72
- end
61
+ config[:winrm_transport] = config[:winrm_transport].to_sym
73
62
 
74
63
  self
75
64
  end
@@ -96,10 +85,7 @@ module Kitchen
96
85
  def close
97
86
  return if @session.nil?
98
87
 
99
- shell_id = session.shell
100
- logger.debug("[WinRM] closing remote shell #{shell_id} on #{self}")
101
88
  session.close
102
- logger.debug("[WinRM] remote shell #{shell_id} closed")
103
89
  ensure
104
90
  @session = nil
105
91
  end
@@ -147,10 +133,8 @@ module Kitchen
147
133
  def wait_until_ready
148
134
  delay = 3
149
135
  session(
150
- :retries => max_wait_until_ready / delay,
151
- :delay => delay,
152
- :message => "Waiting for WinRM service on #{endpoint}, " \
153
- "retrying in #{delay} seconds"
136
+ :retry_limit => max_wait_until_ready / delay,
137
+ :retry_delay => delay
154
138
  )
155
139
  execute(PING_COMMAND.dup)
156
140
  end
@@ -164,16 +148,6 @@ module Kitchen
164
148
  # UTF-16 string which will double the string size.
165
149
  MAX_COMMAND_SIZE = 3000
166
150
 
167
- RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
168
- [
169
- Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
170
- Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
171
- ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
172
- HTTPClient::KeepAliveDisconnected,
173
- HTTPClient::ConnectTimeoutError
174
- ].freeze
175
- end
176
-
177
151
  # @return [Integer] how many times to retry when failing to execute
178
152
  # a command or transfer files
179
153
  # @api private
@@ -235,31 +209,6 @@ module Kitchen
235
209
  end
236
210
  end
237
211
 
238
- # Establish a remote shell session on the remote host.
239
- #
240
- # @param opts [Hash] retry options
241
- # @option opts [Integer] :retries the number of times to retry before
242
- # failing
243
- # @option opts [Float] :delay the number of seconds to wait until
244
- # attempting a retry
245
- # @option opts [String] :message an optional message to be logged on
246
- # debug (overriding the default) when a rescuable exception is raised
247
- # @return [Winrm::CommandExecutor] the command executor session
248
- # @api private
249
- def establish_shell(opts)
250
- service_args = [endpoint, winrm_transport, options]
251
- @service = ::WinRM::WinRMWebService.new(*service_args)
252
- closer = WinRM::Transport::ShellCloser.new("#{self}", logger.debug?, service_args)
253
-
254
- executor = WinRM::Transport::CommandExecutor.new(@service, logger, closer)
255
- retryable(opts) do
256
- logger.debug("[WinRM] opening remote shell on #{self}")
257
- shell_id = executor.open
258
- logger.debug("[WinRM] remote shell #{shell_id} is open on #{self}")
259
- end
260
- executor
261
- end
262
-
263
212
  # Execute a Powershell script over WinRM and return the command's
264
213
  # exit code and standard error.
265
214
  #
@@ -278,7 +227,7 @@ module Kitchen
278
227
  # @return [Winrm::FileTransporter] a file transporter
279
228
  # @api private
280
229
  def file_transporter
281
- @file_transporter ||= WinRM::Transport::FileTransporter.new(session, logger)
230
+ @file_transporter ||= WinRM::FS::Core::FileTransporter.new(session)
282
231
  end
283
232
 
284
233
  # (see Base#init_options)
@@ -353,38 +302,6 @@ module Kitchen
353
302
  File.join(kitchen_root, ".kitchen", "#{instance_name}.rdp")
354
303
  end
355
304
 
356
- # Yields to a block and reties the block if certain rescuable
357
- # exceptions are raised.
358
- #
359
- # @param opts [Hash] retry options
360
- # @option opts [Integer] :retries the number of times to retry before
361
- # failing
362
- # @option opts [Float] :delay the number of seconds to wait until
363
- # attempting a retry
364
- # @option opts [String] :message an optional message to be logged on
365
- # debug (overriding the default) when a rescuable exception is raised
366
- # @return [Winrm::CommandExecutor] the command executor session
367
- # @api private
368
- def retryable(opts)
369
- yield
370
- rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH.call => e
371
- if (opts[:retries] -= 1) > 0
372
- message = if opts[:message]
373
- logger.debug("[WinRM] connection failed (#{e.inspect})")
374
- opts[:message]
375
- else
376
- "[WinRM] connection failed, " \
377
- "retrying in #{opts[:delay]} seconds (#{e.inspect})"
378
- end
379
- logger.info(message)
380
- sleep(opts[:delay])
381
- retry
382
- else
383
- logger.warn("[WinRM] connection failed, terminating (#{e.inspect})")
384
- raise
385
- end
386
- end
387
-
388
305
  # Establishes a remote shell session, or establishes one when invoked
389
306
  # the first time.
390
307
  #
@@ -392,10 +309,17 @@ module Kitchen
392
309
  # @return [Winrm::CommandExecutor] the command executor session
393
310
  # @api private
394
311
  def session(retry_options = {})
395
- @session ||= establish_shell({
396
- :retries => connection_retries.to_i,
397
- :delay => connection_retry_sleep.to_i
398
- }.merge(retry_options))
312
+ @session ||= begin
313
+ opts = {
314
+ :retry_limit => connection_retries.to_i,
315
+ :retry_delay => connection_retry_sleep.to_i
316
+ }.merge(retry_options)
317
+
318
+ service_args = [endpoint, winrm_transport, options.merge(opts)]
319
+ @service = ::WinRM::WinRMWebService.new(*service_args)
320
+ @service.logger = logger
321
+ @service.create_executor
322
+ end
399
323
  end
400
324
 
401
325
  # String representation of object, reporting its connection details and
@@ -425,7 +349,7 @@ module Kitchen
425
349
  target_path = File.join("$env:TEMP", "kitchen")
426
350
  upload(script_path, target_path)
427
351
 
428
- %{& "#{File.join(target_path, script_name)}"}
352
+ %{powershell -ExecutionPolicy Bypass -File "#{File.join(target_path, script_name)}"}
429
353
  ensure
430
354
  FileUtils.rmtree(temp_dir)
431
355
  end
@@ -434,7 +358,8 @@ module Kitchen
434
358
 
435
359
  private
436
360
 
437
- WINRM_TRANSPORT_SPEC_VERSION = ["~> 1.0", ">= 1.0.3"].freeze
361
+ WINRM_SPEC_VERSION = ["~> 1.6"].freeze
362
+ WINRM_FS_SPEC_VERSION = ["~> 0.3"].freeze
438
363
 
439
364
  # Builds the hash of options needed by the Connection object on
440
365
  # construction.
@@ -461,8 +386,8 @@ module Kitchen
461
386
  end
462
387
 
463
388
  def additional_transport_args(transport_type)
464
- case transport_type
465
- when :ssl, :sspinegotiate
389
+ case transport_type.to_sym
390
+ when :ssl, :negotiate
466
391
  {
467
392
  :no_ssl_peer_verification => true,
468
393
  :disable_sspi => false,
@@ -473,6 +398,8 @@ module Kitchen
473
398
  :disable_sspi => true,
474
399
  :basic_auth_only => true
475
400
  }
401
+ else
402
+ {}
476
403
  end
477
404
  end
478
405
 
@@ -495,66 +422,27 @@ module Kitchen
495
422
  # (see Base#load_needed_dependencies!)
496
423
  def load_needed_dependencies!
497
424
  super
498
- load_winrm_transport!
499
- load_winrm_s! if host_os_windows?
500
- end
501
-
502
- # Load WinRM::Transport code.
503
- #
504
- # @api private
505
- def load_winrm_transport!
506
- spec_version = WINRM_TRANSPORT_SPEC_VERSION.dup
507
- options = {
508
- :load_msg => "Winrm Transport requested," \
509
- " loading WinRM::Transport gem (#{spec_version})",
510
- :success_msg => "WinRM::Transport library loaded",
511
- :already_msg => "WinRM::Transport previously loaded",
512
- :name => "winrm-transport",
513
- :version => spec_version
514
- }
515
-
516
- load_with_rescue!(options) do
517
- gem "winrm-transport", WINRM_TRANSPORT_SPEC_VERSION.dup
518
- require "winrm/transport/version"
519
- end
520
-
521
- silence_warnings { require "winrm" }
522
- require "winrm/transport/shell_closer"
523
- require "winrm/transport/command_executor"
524
- require "winrm/transport/file_transporter"
525
- end
526
-
527
- def load_winrm_s!
528
- options = {
529
- :load_msg => "The winrm-s gem is being loaded" \
530
- " to enable sspiauthentication.",
531
- :success_msg => "winrm-s is loaded." \
532
- " sspinegotiate auth is now available.",
533
- :already_msg => "winrm-s was already loaded.",
534
- :name => "winrm-s"
535
- }
536
-
537
- load_with_rescue!(options) { require "winrm-s" }
425
+ load_with_rescue!("winrm", WINRM_SPEC_VERSION.dup)
426
+ load_with_rescue!("winrm-fs", WINRM_FS_SPEC_VERSION.dup)
538
427
  end
539
428
 
540
- def load_with_rescue!(options = {}, &block)
541
- logger.debug(options[:load_msg])
542
- attempt_load = execute_block(&block)
429
+ def load_with_rescue!(gem_name, spec_version)
430
+ logger.debug("#{gem_name} requested," \
431
+ " loading #{gem_name} gem (#{spec_version})")
432
+ attempt_load = false
433
+ gem gem_name, spec_version
434
+ silence_warnings { attempt_load = require gem_name }
543
435
  if attempt_load
544
- logger.debug(options[:success_msg])
436
+ logger.debug("#{gem_name} is loaded.")
545
437
  else
546
- logger.debug(options[:already_msg])
438
+ logger.debug("#{gem_name} was already loaded.")
547
439
  end
548
440
  rescue LoadError => e
549
- message = fail_to_load_gem_message(options[:name],
550
- options[:version])
441
+ message = fail_to_load_gem_message(gem_name,
442
+ spec_version)
551
443
  logger.fatal(message)
552
444
  raise UserError,
553
- "Could not load or activate #{options[:name]}. (#{e.message})"
554
- end
555
-
556
- def execute_block
557
- yield if block_given?
445
+ "Could not load or activate #{gem_name}. (#{e.message})"
558
446
  end
559
447
 
560
448
  def fail_to_load_gem_message(name, version = nil)