test-kitchen 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +1 -1
  3. data/.rubocop.yml +3 -0
  4. data/.travis.yml +20 -9
  5. data/CHANGELOG.md +219 -108
  6. data/Gemfile +10 -6
  7. data/Guardfile +38 -9
  8. data/README.md +11 -1
  9. data/Rakefile +21 -37
  10. data/bin/kitchen +4 -4
  11. data/features/kitchen_action_commands.feature +161 -0
  12. data/features/kitchen_console_command.feature +34 -0
  13. data/features/kitchen_diagnose_command.feature +64 -0
  14. data/features/kitchen_init_command.feature +29 -17
  15. data/features/kitchen_list_command.feature +2 -2
  16. data/features/kitchen_login_command.feature +56 -0
  17. data/features/{sink_command.feature → kitchen_sink_command.feature} +0 -0
  18. data/features/kitchen_test_command.feature +88 -0
  19. data/features/step_definitions/gem_steps.rb +8 -6
  20. data/features/step_definitions/git_steps.rb +4 -2
  21. data/features/step_definitions/output_steps.rb +5 -0
  22. data/features/support/env.rb +12 -9
  23. data/lib/kitchen.rb +60 -38
  24. data/lib/kitchen/base64_stream.rb +55 -0
  25. data/lib/kitchen/busser.rb +124 -58
  26. data/lib/kitchen/cli.rb +121 -38
  27. data/lib/kitchen/collection.rb +3 -3
  28. data/lib/kitchen/color.rb +4 -4
  29. data/lib/kitchen/command.rb +78 -11
  30. data/lib/kitchen/command/action.rb +3 -2
  31. data/lib/kitchen/command/console.rb +12 -5
  32. data/lib/kitchen/command/diagnose.rb +17 -3
  33. data/lib/kitchen/command/driver_discover.rb +26 -7
  34. data/lib/kitchen/command/exec.rb +41 -0
  35. data/lib/kitchen/command/list.rb +44 -14
  36. data/lib/kitchen/command/login.rb +2 -1
  37. data/lib/kitchen/command/sink.rb +2 -1
  38. data/lib/kitchen/command/test.rb +5 -4
  39. data/lib/kitchen/config.rb +146 -14
  40. data/lib/kitchen/configurable.rb +314 -0
  41. data/lib/kitchen/data_munger.rb +522 -18
  42. data/lib/kitchen/diagnostic.rb +43 -4
  43. data/lib/kitchen/driver.rb +4 -4
  44. data/lib/kitchen/driver/base.rb +80 -115
  45. data/lib/kitchen/driver/dummy.rb +34 -6
  46. data/lib/kitchen/driver/proxy.rb +14 -3
  47. data/lib/kitchen/driver/ssh_base.rb +61 -7
  48. data/lib/kitchen/errors.rb +109 -9
  49. data/lib/kitchen/generator/driver_create.rb +39 -5
  50. data/lib/kitchen/generator/init.rb +130 -45
  51. data/lib/kitchen/instance.rb +162 -28
  52. data/lib/kitchen/lazy_hash.rb +79 -7
  53. data/lib/kitchen/loader/yaml.rb +159 -27
  54. data/lib/kitchen/logger.rb +267 -21
  55. data/lib/kitchen/logging.rb +30 -3
  56. data/lib/kitchen/login_command.rb +11 -2
  57. data/lib/kitchen/metadata_chopper.rb +2 -2
  58. data/lib/kitchen/provisioner.rb +4 -4
  59. data/lib/kitchen/provisioner/base.rb +107 -103
  60. data/lib/kitchen/provisioner/chef/berkshelf.rb +36 -8
  61. data/lib/kitchen/provisioner/chef/librarian.rb +40 -11
  62. data/lib/kitchen/provisioner/chef_base.rb +206 -167
  63. data/lib/kitchen/provisioner/chef_solo.rb +25 -7
  64. data/lib/kitchen/provisioner/chef_zero.rb +105 -29
  65. data/lib/kitchen/provisioner/dummy.rb +1 -1
  66. data/lib/kitchen/provisioner/shell.rb +21 -6
  67. data/lib/kitchen/rake_tasks.rb +8 -3
  68. data/lib/kitchen/shell_out.rb +15 -18
  69. data/lib/kitchen/ssh.rb +122 -27
  70. data/lib/kitchen/state_file.rb +24 -7
  71. data/lib/kitchen/thor_tasks.rb +9 -4
  72. data/lib/kitchen/util.rb +43 -118
  73. data/lib/kitchen/version.rb +1 -1
  74. data/lib/vendor/hash_recursive_merge.rb +10 -2
  75. data/spec/kitchen/base64_stream_spec.rb +77 -0
  76. data/spec/kitchen/busser_spec.rb +490 -0
  77. data/spec/kitchen/collection_spec.rb +10 -10
  78. data/spec/kitchen/color_spec.rb +2 -2
  79. data/spec/kitchen/config_spec.rb +234 -62
  80. data/spec/kitchen/configurable_spec.rb +490 -0
  81. data/spec/kitchen/data_munger_spec.rb +1070 -862
  82. data/spec/kitchen/diagnostic_spec.rb +79 -0
  83. data/spec/kitchen/driver/base_spec.rb +80 -85
  84. data/spec/kitchen/driver/dummy_spec.rb +43 -14
  85. data/spec/kitchen/driver/proxy_spec.rb +134 -0
  86. data/spec/kitchen/driver/ssh_base_spec.rb +644 -0
  87. data/spec/kitchen/driver_spec.rb +15 -15
  88. data/spec/kitchen/errors_spec.rb +309 -0
  89. data/spec/kitchen/instance_spec.rb +143 -46
  90. data/spec/kitchen/lazy_hash_spec.rb +36 -9
  91. data/spec/kitchen/loader/yaml_spec.rb +237 -226
  92. data/spec/kitchen/logger_spec.rb +419 -0
  93. data/spec/kitchen/logging_spec.rb +59 -0
  94. data/spec/kitchen/login_command_spec.rb +49 -0
  95. data/spec/kitchen/metadata_chopper_spec.rb +82 -0
  96. data/spec/kitchen/platform_spec.rb +4 -4
  97. data/spec/kitchen/provisioner/base_spec.rb +65 -125
  98. data/spec/kitchen/provisioner/chef_base_spec.rb +798 -0
  99. data/spec/kitchen/provisioner/chef_solo_spec.rb +316 -0
  100. data/spec/kitchen/provisioner/chef_zero_spec.rb +624 -0
  101. data/spec/kitchen/provisioner/shell_spec.rb +269 -0
  102. data/spec/kitchen/provisioner_spec.rb +6 -6
  103. data/spec/kitchen/shell_out_spec.rb +143 -0
  104. data/spec/kitchen/ssh_spec.rb +683 -0
  105. data/spec/kitchen/state_file_spec.rb +28 -21
  106. data/spec/kitchen/suite_spec.rb +7 -7
  107. data/spec/kitchen/util_spec.rb +68 -10
  108. data/spec/kitchen_spec.rb +107 -0
  109. data/spec/spec_helper.rb +18 -13
  110. data/support/chef-client-zero.rb +10 -9
  111. data/support/chef_helpers.sh +16 -0
  112. data/support/download_helpers.sh +109 -0
  113. data/test-kitchen.gemspec +42 -33
  114. metadata +107 -33
@@ -0,0 +1,55 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2014, Fletcher Nichol
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
+ module Kitchen
20
+
21
+ # Base64 encoder/decoder that operates on IO objects so as to minimize
22
+ # memory allocations on large payloads.
23
+ #
24
+ # @author Fletcher Nichol <fnichol@nichol.ca>
25
+ module Base64Stream
26
+
27
+ # Encodes an input stream into a Base64 output stream. The input and ouput
28
+ # objects must be opened IO resources. In other words, opening and closing
29
+ # the resources are not the responsibilty of this method.
30
+ #
31
+ # @param io_in [#read] input stream
32
+ # @param io_out [#write] output stream
33
+ def self.strict_encode(io_in, io_out)
34
+ buffer = "" # rubocop:disable Lint/UselessAssignment
35
+ while io_in.read(3 * 1000, buffer)
36
+ io_out.write([buffer].pack("m0"))
37
+ end
38
+ buffer = nil # rubocop:disable Lint/UselessAssignment
39
+ end
40
+
41
+ # Decodes a Base64 input stream into an output stream. The input and ouput
42
+ # objects must be opened IO resources. In other words, opening and closing
43
+ # the resources are not the responsibilty of this method.
44
+ #
45
+ # @param io_in [#read] input stream
46
+ # @param io_out [#write] output stream
47
+ def self.strict_decode(io_in, io_out)
48
+ buffer = "" # rubocop:disable Lint/UselessAssignment
49
+ while io_in.read(3 * 1000, buffer)
50
+ io_out.write(buffer.unpack("m0").first)
51
+ end
52
+ buffer = nil # rubocop:disable Lint/UselessAssignment
53
+ end
54
+ end
55
+ end
@@ -16,8 +16,8 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require 'base64'
20
- require 'digest'
19
+ require "base64"
20
+ require "digest"
21
21
 
22
22
  module Kitchen
23
23
 
@@ -95,21 +95,23 @@ module Kitchen
95
95
  # @return [String] a command string to setup the test suite, or nil if no
96
96
  # work needs to be performed
97
97
  def setup_cmd
98
- @setup_cmd ||= if local_suite_files.empty?
99
- nil
100
- else
101
- setup_cmd = []
102
- setup_cmd << busser_setup_env
103
- setup_cmd << "if ! #{sudo}#{config[:ruby_bindir]}/gem list busser -i >/dev/null"
104
- setup_cmd << "then #{sudo}#{config[:ruby_bindir]}/gem install #{gem_install_args}"
105
- setup_cmd << "fi"
106
- setup_cmd << "gem_bindir=`#{config[:ruby_bindir]}/ruby -rrubygems -e \"puts Gem.bindir\"`"
107
- setup_cmd << "#{sudo}${gem_bindir}/busser setup"
108
- setup_cmd << "#{sudo}#{config[:busser_bin]} plugin install #{plugins.join(' ')}"
109
-
110
- # use Bourne (/bin/sh) as Bash does not exist on all Unix flavors
111
- "sh -c '#{setup_cmd.join('; ')}'"
112
- end
98
+ return if local_suite_files.empty?
99
+
100
+ ruby = "#{config[:ruby_bindir]}/ruby"
101
+ gem = sudo("#{config[:ruby_bindir]}/gem")
102
+ busser = sudo(config[:busser_bin])
103
+
104
+ cmd = <<-CMD.gsub(/^ {8}/, "")
105
+ #{busser_setup_env}
106
+ gem_bindir=`#{ruby} -rrubygems -e "puts Gem.bindir"`
107
+
108
+ if ! #{gem} list busser -i >/dev/null; then
109
+ #{gem} install #{gem_install_args}
110
+ fi
111
+ #{sudo("${gem_bindir}")}/busser setup
112
+ #{busser} plugin install #{plugins.join(" ")}
113
+ CMD
114
+ Util.wrap_command(cmd)
113
115
  end
114
116
 
115
117
  # Returns a command string which transfers all suite test files to the
@@ -121,18 +123,22 @@ module Kitchen
121
123
  # @return [String] a command string to transfer all suite test files, or
122
124
  # nil if no work needs to be performed.
123
125
  def sync_cmd
124
- @sync_cmd ||= if local_suite_files.empty?
125
- nil
126
- else
127
- sync_cmd = []
128
- sync_cmd << busser_setup_env
129
- sync_cmd << "#{sudo}#{config[:busser_bin]} suite cleanup"
130
- sync_cmd << "#{local_suite_files.map { |f| stream_file(f, remote_file(f, config[:suite_name])) }.join("; ")}"
131
- sync_cmd << "#{helper_files.map { |f| stream_file(f, remote_file(f, "helpers")) }.join("; ")}"
132
-
133
- # use Bourne (/bin/sh) as Bash does not exist on all Unix flavors
134
- "sh -c '#{sync_cmd.join('; ')}'"
126
+ return if local_suite_files.empty?
127
+
128
+ cmd = <<-CMD.gsub(/^ {8}/, "")
129
+ #{busser_setup_env}
130
+
131
+ #{sudo(config[:busser_bin])} suite cleanup
132
+
133
+ CMD
134
+ local_suite_files.each do |f|
135
+ cmd << stream_file(f, remote_file(f, config[:suite_name])).concat("\n")
136
+ end
137
+ helper_files.each do |f|
138
+ cmd << stream_file(f, remote_file(f, "helpers")).concat("\n")
135
139
  end
140
+
141
+ Util.wrap_command(cmd)
136
142
  end
137
143
 
138
144
  # Returns a command string which runs all Busser suite tests for the suite.
@@ -143,16 +149,15 @@ module Kitchen
143
149
  # @return [String] a command string to run the test suites, or nil if no
144
150
  # work needs to be performed
145
151
  def run_cmd
146
- @run_cmd ||= if local_suite_files.empty?
147
- nil
148
- else
149
- run_cmd = []
150
- run_cmd << busser_setup_env
151
- run_cmd << "#{sudo}#{config[:busser_bin]} test"
152
-
153
- # use Bourne (/bin/sh) as Bash does not exist on all Unix flavors
154
- "sh -c '#{run_cmd.join('; ')}'"
155
- end
152
+ return if local_suite_files.empty?
153
+
154
+ cmd = <<-CMD.gsub(/^ {8}/, "")
155
+ #{busser_setup_env}
156
+
157
+ #{sudo(config[:busser_bin])} test
158
+ CMD
159
+
160
+ Util.wrap_command(cmd)
156
161
  end
157
162
 
158
163
  private
@@ -160,83 +165,144 @@ module Kitchen
160
165
  DEFAULT_RUBY_BINDIR = "/opt/chef/embedded/bin".freeze
161
166
  DEFAULT_ROOT_PATH = "/tmp/busser".freeze
162
167
 
168
+ # @return [Hash] a configuration hash
169
+ # @api private
163
170
  attr_reader :config
164
171
 
172
+ # Ensures that the object is internally consistent and otherwise raising
173
+ # an exception.
174
+ #
175
+ # @param suite_name [String] the suite name
176
+ # @raise [ClientError] if a suite name is missing
177
+ # @raise [UserError] if the suite name is invalid
178
+ # @api private
165
179
  def validate_options(suite_name)
166
180
  if suite_name.nil?
167
181
  raise ClientError, "Busser#new requires a suite_name"
168
182
  end
169
183
 
170
- if suite_name == 'helper'
184
+ if suite_name == "helper"
171
185
  raise UserError,
172
186
  "Suite name invalid: 'helper' is a reserved directory name."
173
187
  end
174
188
  end
175
189
 
190
+ # Returns a uniquely sorted Array of Busser plugin gems that need to
191
+ # be installed for the related suite.
192
+ #
193
+ # @return [Array<String>] a lexically sorted, unique item array of Busser
194
+ # plugin gem names
195
+ # @api private
176
196
  def plugins
197
+ non_suite_dirs = %w[data data_bags environments nodes roles]
177
198
  glob = File.join(config[:test_base_path], config[:suite_name], "*")
178
199
  Dir.glob(glob).reject { |d|
179
- ! File.directory?(d) || non_suite_dirs.include?(File.basename(d))
200
+ !File.directory?(d) || non_suite_dirs.include?(File.basename(d))
180
201
  }.map { |d| "busser-#{File.basename(d)}" }.sort.uniq
181
202
  end
182
203
 
204
+ # Returns an Array of test suite filenames for the related suite currently
205
+ # residing on the local workstation. Any special provisioner-specific
206
+ # directories (such as a Chef roles/ directory) are excluded.
207
+ #
208
+ # @return [Array<String>] array of suite files
209
+ # @api private
183
210
  def local_suite_files
184
211
  base = File.join(config[:test_base_path], config[:suite_name])
185
212
  glob = File.join(base, "*/**/*")
186
213
  Dir.glob(glob).reject do |f|
187
- is_chef_data_dir?(base, f) || File.directory?(f)
214
+ chef_data_dir?(base, f) || File.directory?(f)
188
215
  end
189
216
  end
190
217
 
191
- def is_chef_data_dir?(base, file)
192
- file =~ %r[^#{base}/(data|data_bags|environments|nodes|roles)/]
218
+ # Determines whether or not a local workstation file exists under a
219
+ # Chef-related directory.
220
+ #
221
+ # @return [truthy,falsey] whether or not a given file is some kind of
222
+ # Chef-related file
223
+ # @api private
224
+ def chef_data_dir?(base, file)
225
+ file =~ %r{^#{base}/(data|data_bags|environments|nodes|roles)/}
193
226
  end
194
227
 
228
+ # Returns an Array of common helper filenames currently residing on the
229
+ # local workstation.
230
+ #
231
+ # @return [Array<String>] array of helper files
232
+ # @api private
195
233
  def helper_files
196
- Dir.glob(File.join(config[:test_base_path], "helpers", "*/**/*")).reject { |f| File.directory?(f) }
234
+ glob = File.join(config[:test_base_path], "helpers", "*/**/*")
235
+ Dir.glob(glob).reject { |f| File.directory?(f) }
197
236
  end
198
237
 
238
+ # Returns a command string that will, once evaluated, result in the
239
+ # fully qualified destination path of a file on an instance.
240
+ #
241
+ # @param file [String] absolute path to the local file
242
+ # @param dir [String] suite directory or helper directory name
243
+ # @return [String] command string
244
+ # @api private
199
245
  def remote_file(file, dir)
200
246
  local_prefix = File.join(config[:test_base_path], dir)
201
- "$(#{sudo}#{config[:busser_bin]} suite path)/".concat(file.sub(%r{^#{local_prefix}/}, ''))
247
+ "`#{sudo(config[:busser_bin])} suite path`/".
248
+ concat(file.sub(%r{^#{local_prefix}/}, ""))
202
249
  end
203
250
 
251
+ # Returns a command string that will, once evaluated, result in the copying
252
+ # of a local file to a remote instance.
253
+ #
254
+ # @param local_path [String] the path to a local source file for copying
255
+ # @param remote_path [String] the destrination path on the remote instance
256
+ # @return [String] command string
257
+ # @api private
204
258
  def stream_file(local_path, remote_path)
205
259
  local_file = IO.read(local_path)
260
+ encoded_file = Base64.encode64(local_file).gsub("\n", "")
206
261
  md5 = Digest::MD5.hexdigest(local_file)
207
- perms = sprintf("%o", File.stat(local_path).mode)[2, 4]
262
+ perms = format("%o", File.stat(local_path).mode)[2, 4]
208
263
  stream_cmd = [
209
- "#{sudo}#{config[:busser_bin]}",
264
+ sudo(config[:busser_bin]),
210
265
  "deserialize",
211
266
  "--destination=#{remote_path}",
212
267
  "--md5sum=#{md5}",
213
268
  "--perms=#{perms}"
214
269
  ].join(" ")
215
270
 
216
- stream_file_cmd = []
217
- stream_file_cmd << %{echo "Uploading #{remote_path} (mode=#{perms})"}
218
- stream_file_cmd << %{echo "#{Base64.encode64(local_file).gsub("\n", '')}" | #{sudo}#{stream_cmd}}
219
- stream_file_cmd.join('; ')
220
- end
221
-
222
- def sudo
223
- config[:sudo] ? "sudo -E " : ""
271
+ [
272
+ %{echo "Uploading #{remote_path} (mode=#{perms})"},
273
+ %{echo "#{encoded_file}" | #{stream_cmd}}
274
+ ].join("\n").concat("\n")
224
275
  end
225
276
 
226
- def non_suite_dirs
227
- %w{data data_bags environments nodes roles}
277
+ # Conditionally prefixes a command with a sudo command.
278
+ #
279
+ # @param command [String] command to be prefixed
280
+ # @return [String] the command, conditionaly prefixed with sudo
281
+ # @api private
282
+ def sudo(command)
283
+ config[:sudo] ? "sudo -E #{command}" : command
228
284
  end
229
285
 
286
+ # Returns a command string that sets appropriate environment variables for
287
+ # busser commands.
288
+ #
289
+ # @return [String] command string
290
+ # @api private
230
291
  def busser_setup_env
231
292
  [
232
293
  %{BUSSER_ROOT="#{config[:root_path]}"},
233
294
  %{GEM_HOME="#{config[:root_path]}/gems"},
234
295
  %{GEM_PATH="#{config[:root_path]}/gems"},
235
296
  %{GEM_CACHE="#{config[:root_path]}/gems/cache"},
236
- %{; export BUSSER_ROOT GEM_HOME GEM_PATH GEM_CACHE}
297
+ %{\nexport BUSSER_ROOT GEM_HOME GEM_PATH GEM_CACHE}
237
298
  ].join(" ")
238
299
  end
239
300
 
301
+ # Returns arguments to a `gem install` command, suitable to install the
302
+ # Busser gem.
303
+ #
304
+ # @return [String] arguments string
305
+ # @api private
240
306
  def gem_install_args
241
307
  gem, version = config[:version].split("@")
242
308
  gem, version = "busser", gem if gem =~ /^\d+\.\d+\.\d+/
@@ -16,11 +16,11 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require 'thor'
19
+ require "thor"
20
20
 
21
- require 'kitchen'
22
- require 'kitchen/generator/driver_create'
23
- require 'kitchen/generator/init'
21
+ require "kitchen"
22
+ require "kitchen/generator/driver_create"
23
+ require "kitchen/generator/init"
24
24
 
25
25
  module Kitchen
26
26
 
@@ -32,12 +32,21 @@ module Kitchen
32
32
  # Common module to load and invoke a CLI-implementation agnostic command.
33
33
  module PerformCommand
34
34
 
35
+ # Perform a CLI subcommand.
36
+ #
37
+ # @param task [String] action to take, usually corresponding to the
38
+ # subcommand name
39
+ # @param command [String] command class to create and invoke]
40
+ # @param args [Array] remainder arguments from processed ARGV
41
+ # (default: `nil`)
42
+ # @param additional_options [Hash] additional configuration needed to
43
+ # set up the command class (default: `{}`)
35
44
  def perform(task, command, args = nil, additional_options = {})
36
45
  require "kitchen/command/#{command}"
37
46
 
38
47
  command_options = {
39
48
  :action => task,
40
- :help => lambda { help(task) },
49
+ :help => -> { help(task) },
41
50
  :config => @config,
42
51
  :shell => shell
43
52
  }.merge(additional_options)
@@ -51,6 +60,8 @@ module Kitchen
51
60
  include Logging
52
61
  include PerformCommand
53
62
 
63
+ # The maximum number of concurrent instances that can run--which is a bit
64
+ # high
54
65
  MAX_CONCURRENCY = 9999
55
66
 
56
67
  # Constructs a new instance.
@@ -59,22 +70,27 @@ module Kitchen
59
70
  $stdout.sync = true
60
71
  Kitchen.logger = Kitchen.default_file_logger
61
72
  @loader = Kitchen::Loader::YAML.new(
62
- :project_config => ENV['KITCHEN_YAML'],
63
- :local_config => ENV['KITCHEN_LOCAL_YAML'],
64
- :global_config => ENV['KITCHEN_GLOBAL_YAML']
73
+ :project_config => ENV["KITCHEN_YAML"],
74
+ :local_config => ENV["KITCHEN_LOCAL_YAML"],
75
+ :global_config => ENV["KITCHEN_GLOBAL_YAML"]
65
76
  )
66
77
  @config = Kitchen::Config.new(
67
78
  :loader => @loader,
68
- :log_level => ENV.fetch('KITCHEN_LOG', "info").downcase.to_sym
79
+ :log_level => ENV.fetch("KITCHEN_LOG", "info").downcase.to_sym
69
80
  )
70
81
  end
71
82
 
72
83
  desc "list [INSTANCE|REGEXP|all]", "Lists one or more instances"
73
- method_option :bare, :aliases => "-b", :type => :boolean,
84
+ method_option :bare,
85
+ :aliases => "-b",
86
+ :type => :boolean,
74
87
  :desc => "List the name of each instance only, one per line"
75
- method_option :debug, :aliases => "-d", :type => :boolean,
88
+ method_option :debug,
89
+ :aliases => "-d",
90
+ :type => :boolean,
76
91
  :desc => "[Deprecated] Please use `kitchen diagnose'"
77
- method_option :log_level, :aliases => "-l",
92
+ method_option :log_level,
93
+ :aliases => "-l",
78
94
  :desc => "Set the log level (debug, info, warn, error, fatal)"
79
95
  def list(*args)
80
96
  update_config!
@@ -82,36 +98,63 @@ module Kitchen
82
98
  end
83
99
 
84
100
  desc "diagnose [INSTANCE|REGEXP|all]", "Show computed diagnostic configuration"
85
- method_option :log_level, :aliases => "-l",
101
+ method_option :log_level,
102
+ :aliases => "-l",
86
103
  :desc => "Set the log level (debug, info, warn, error, fatal)"
87
- method_option :loader, :type => :boolean,
104
+ method_option :loader,
105
+ :type => :boolean,
88
106
  :desc => "Include data loader diagnostics"
89
- method_option :instances, :type => :boolean, :default => true,
107
+ method_option :instances,
108
+ :type => :boolean,
109
+ :default => true,
90
110
  :desc => "Include instances diagnostics"
91
- method_option :all, :type => :boolean,
111
+ method_option :all,
112
+ :type => :boolean,
92
113
  :desc => "Include all diagnostics"
93
114
  def diagnose(*args)
94
115
  update_config!
95
116
  perform("diagnose", "diagnose", args, :loader => @loader)
96
117
  end
97
118
 
98
- [:create, :converge, :setup, :verify, :destroy].each do |action|
119
+ {
120
+ :create => "Change instance state to create. " \
121
+ "Start one or more instances",
122
+ :converge => "Change instance state to converge. " \
123
+ "Use a provisioner to configure one or more instances",
124
+ :setup => "Change instance state to setup. " \
125
+ "Prepare to run automated tests. " \
126
+ "Install busser and related gems on one or more instances",
127
+ :verify => "Change instance state to verify. " \
128
+ "Run automated tests on one or more instances",
129
+ :destroy => "Change instance state to destroy. " \
130
+ "Delete all information for one or more instances"
131
+ }.each do |action, short_desc|
99
132
  desc(
100
133
  "#{action} [INSTANCE|REGEXP|all]",
101
- "#{action.capitalize} one or more instances"
134
+ short_desc
102
135
  )
103
- method_option :concurrency, :aliases => "-c",
104
- :type => :numeric, :lazy_default => MAX_CONCURRENCY,
105
- :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
136
+ long_desc <<-DESC
137
+ The instance states are in order: destroy, create, converge, setup, verify, destroy.
138
+ Change one or more instances from the current state to the #{action} state. Actions for all
139
+ intermediate states will be executed. See http://kitchen.ci for further explanation.
140
+ DESC
141
+ method_option :concurrency,
142
+ :aliases => "-c",
143
+ :type => :numeric,
144
+ :lazy_default => MAX_CONCURRENCY,
145
+ :desc => <<-DESC.gsub(/^\s+/, "").gsub(/\n/, " ")
106
146
  Run a #{action} against all matching instances concurrently. Only N
107
147
  instances will run at the same time if a number is given.
108
148
  DESC
109
- method_option :parallel, :aliases => "-p", :type => :boolean,
110
- :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
149
+ method_option :parallel,
150
+ :aliases => "-p",
151
+ :type => :boolean,
152
+ :desc => <<-DESC.gsub(/^\s+/, "").gsub(/\n/, " ")
111
153
  [Future DEPRECATION, use --concurrency]
112
154
  Run a #{action} against all matching instances concurrently.
113
155
  DESC
114
- method_option :log_level, :aliases => "-l",
156
+ method_option :log_level,
157
+ :aliases => "-l",
115
158
  :desc => "Set the log level (debug, info, warn, error, fatal)"
116
159
  define_method(action) do |*args|
117
160
  update_config!
@@ -119,9 +162,13 @@ module Kitchen
119
162
  end
120
163
  end
121
164
 
122
- desc "test [INSTANCE|REGEXP|all]", "Test one or more instances"
165
+ desc "test [INSTANCE|REGEXP|all]",
166
+ "Test (destroy, create, converge, setup, verify and destroy) one or more instances"
123
167
  long_desc <<-DESC
124
- Test one or more instances
168
+ The instance states are in order: destroy, create, converge, setup, verify, destroy.
169
+ Test changes the state of one or more instances to destroyed, then executes
170
+ the actions for each state up to destroy. At any sign of failure, executing the
171
+ actions stops and the instance is left in the last successful execution state.
125
172
 
126
173
  There are 3 post-verify modes for instance cleanup, triggered with
127
174
  the `--destroy' flag:
@@ -130,22 +177,31 @@ module Kitchen
130
177
  * always: instances will always be destroyed afterwards.\n
131
178
  * never: instances will never be destroyed afterwards.
132
179
  DESC
133
- method_option :concurrency, :aliases => "-c",
134
- :type => :numeric, :lazy_default => MAX_CONCURRENCY,
135
- :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
180
+ method_option :concurrency,
181
+ :aliases => "-c",
182
+ :type => :numeric,
183
+ :lazy_default => MAX_CONCURRENCY,
184
+ :desc => <<-DESC.gsub(/^\s+/, "").gsub(/\n/, " ")
136
185
  Run a test against all matching instances concurrently. Only N
137
186
  instances will run at the same time if a number is given.
138
187
  DESC
139
- method_option :parallel, :aliases => "-p", :type => :boolean,
140
- :desc => <<-DESC.gsub(/^\s+/, '').gsub(/\n/, ' ')
188
+ method_option :parallel,
189
+ :aliases => "-p",
190
+ :type => :boolean,
191
+ :desc => <<-DESC.gsub(/^\s+/, "").gsub(/\n/, " ")
141
192
  [Future DEPRECATION, use --concurrency]
142
193
  Run a test against all matching instances concurrently.
143
194
  DESC
144
- method_option :log_level, :aliases => "-l",
195
+ method_option :log_level,
196
+ :aliases => "-l",
145
197
  :desc => "Set the log level (debug, info, warn, error, fatal)"
146
- method_option :destroy, :aliases => "-d", :default => "passing",
198
+ method_option :destroy,
199
+ :aliases => "-d",
200
+ :default => "passing",
147
201
  :desc => "Destroy strategy to use after testing (passing, always, never)."
148
- method_option :auto_init, :type => :boolean, :default => false,
202
+ method_option :auto_init,
203
+ :type => :boolean,
204
+ :default => false,
149
205
  :desc => "Invoke init command if .kitchen.yml is missing"
150
206
  def test(*args)
151
207
  update_config!
@@ -154,18 +210,32 @@ module Kitchen
154
210
  end
155
211
 
156
212
  desc "login INSTANCE|REGEXP", "Log in to one instance"
157
- method_option :log_level, :aliases => "-l",
213
+ method_option :log_level,
214
+ :aliases => "-l",
158
215
  :desc => "Set the log level (debug, info, warn, error, fatal)"
159
216
  def login(*args)
160
217
  update_config!
161
218
  perform("login", "login", args)
162
219
  end
163
220
 
221
+ desc "exec INSTANCE|REGEXP -c REMOTE_COMMAND",
222
+ "Execute command on one or more instance"
223
+ method_option :log_level,
224
+ :aliases => "-l",
225
+ :desc => "Set the log level (debug, info, warn, error, fatal)"
226
+ method_option :command,
227
+ :aliases => "-c",
228
+ :desc => "execute via ssh"
229
+ def exec(*args)
230
+ update_config!
231
+ perform("exec", "exec", args)
232
+ end
233
+
164
234
  desc "version", "Print Kitchen's version information"
165
235
  def version
166
236
  puts "Test Kitchen version #{Kitchen::VERSION}"
167
237
  end
168
- map %w(-v --version) => :version
238
+ map %w[-v --version] => :version
169
239
 
170
240
  desc "sink", "Show the Kitchen sink!", :hide => true
171
241
  def sink
@@ -217,6 +287,7 @@ module Kitchen
217
287
  perform("discover", "driver_discover", args)
218
288
  end
219
289
 
290
+ # @return [String] basename
220
291
  def self.basename
221
292
  super + " driver"
222
293
  end
@@ -238,14 +309,23 @@ module Kitchen
238
309
 
239
310
  private
240
311
 
312
+ # Ensure the any failing commands exit non-zero.
313
+ #
314
+ # @return [true] you die always on failure
315
+ # @api private
241
316
  def self.exit_on_failure?
242
317
  true
243
318
  end
244
319
 
320
+ # @return [Logger] the common logger
321
+ # @api private
245
322
  def logger
246
323
  Kitchen.logger
247
324
  end
248
325
 
326
+ # Update and finalize options for logging, concurrency, and other concerns.
327
+ #
328
+ # @api private
249
329
  def update_config!
250
330
  if options[:log_level]
251
331
  level = options[:log_level].downcase.to_sym
@@ -264,10 +344,13 @@ module Kitchen
264
344
  end
265
345
  end
266
346
 
347
+ # If auto_init option is active, invoke the init generator.
348
+ #
349
+ # @api private
267
350
  def ensure_initialized
268
- yaml = ENV['KITCHEN_YAML'] || '.kitchen.yml'
351
+ yaml = ENV["KITCHEN_YAML"] || ".kitchen.yml"
269
352
 
270
- if options[:auto_init] && ! File.exists?(yaml)
353
+ if options[:auto_init] && !File.exist?(yaml)
271
354
  banner "Invoking init as '#{yaml}' file is missing"
272
355
  invoke "init"
273
356
  end