test-kitchen-rsync 3.0.0.pre.1

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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +21 -0
  3. data/LICENSE +15 -0
  4. data/Rakefile +53 -0
  5. data/bin/zl-kitchen +11 -0
  6. data/lib/kitchen/base64_stream.rb +48 -0
  7. data/lib/kitchen/chef_utils_wiring.rb +40 -0
  8. data/lib/kitchen/cli.rb +413 -0
  9. data/lib/kitchen/collection.rb +52 -0
  10. data/lib/kitchen/color.rb +63 -0
  11. data/lib/kitchen/command/action.rb +41 -0
  12. data/lib/kitchen/command/console.rb +54 -0
  13. data/lib/kitchen/command/diagnose.rb +84 -0
  14. data/lib/kitchen/command/doctor.rb +39 -0
  15. data/lib/kitchen/command/exec.rb +37 -0
  16. data/lib/kitchen/command/list.rb +148 -0
  17. data/lib/kitchen/command/login.rb +39 -0
  18. data/lib/kitchen/command/package.rb +32 -0
  19. data/lib/kitchen/command/sink.rb +50 -0
  20. data/lib/kitchen/command/test.rb +47 -0
  21. data/lib/kitchen/command.rb +207 -0
  22. data/lib/kitchen/config.rb +344 -0
  23. data/lib/kitchen/configurable.rb +616 -0
  24. data/lib/kitchen/data_munger.rb +1024 -0
  25. data/lib/kitchen/diagnostic.rb +138 -0
  26. data/lib/kitchen/driver/base.rb +133 -0
  27. data/lib/kitchen/driver/dummy.rb +105 -0
  28. data/lib/kitchen/driver/exec.rb +70 -0
  29. data/lib/kitchen/driver/proxy.rb +70 -0
  30. data/lib/kitchen/driver/ssh_base.rb +351 -0
  31. data/lib/kitchen/driver.rb +40 -0
  32. data/lib/kitchen/errors.rb +243 -0
  33. data/lib/kitchen/generator/init.rb +254 -0
  34. data/lib/kitchen/instance.rb +726 -0
  35. data/lib/kitchen/lazy_hash.rb +148 -0
  36. data/lib/kitchen/lifecycle_hook/base.rb +78 -0
  37. data/lib/kitchen/lifecycle_hook/local.rb +53 -0
  38. data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
  39. data/lib/kitchen/lifecycle_hooks.rb +92 -0
  40. data/lib/kitchen/loader/yaml.rb +377 -0
  41. data/lib/kitchen/logger.rb +422 -0
  42. data/lib/kitchen/logging.rb +52 -0
  43. data/lib/kitchen/login_command.rb +49 -0
  44. data/lib/kitchen/metadata_chopper.rb +49 -0
  45. data/lib/kitchen/platform.rb +64 -0
  46. data/lib/kitchen/plugin.rb +76 -0
  47. data/lib/kitchen/plugin_base.rb +60 -0
  48. data/lib/kitchen/provisioner/base.rb +269 -0
  49. data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
  50. data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
  51. data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
  52. data/lib/kitchen/provisioner/chef_apply.rb +121 -0
  53. data/lib/kitchen/provisioner/chef_base.rb +705 -0
  54. data/lib/kitchen/provisioner/chef_infra.rb +167 -0
  55. data/lib/kitchen/provisioner/chef_solo.rb +82 -0
  56. data/lib/kitchen/provisioner/chef_zero.rb +12 -0
  57. data/lib/kitchen/provisioner/dummy.rb +75 -0
  58. data/lib/kitchen/provisioner/shell.rb +157 -0
  59. data/lib/kitchen/provisioner.rb +42 -0
  60. data/lib/kitchen/rake_tasks.rb +80 -0
  61. data/lib/kitchen/shell_out.rb +90 -0
  62. data/lib/kitchen/ssh.rb +289 -0
  63. data/lib/kitchen/state_file.rb +112 -0
  64. data/lib/kitchen/suite.rb +48 -0
  65. data/lib/kitchen/thor_tasks.rb +63 -0
  66. data/lib/kitchen/transport/base.rb +236 -0
  67. data/lib/kitchen/transport/dummy.rb +78 -0
  68. data/lib/kitchen/transport/exec.rb +145 -0
  69. data/lib/kitchen/transport/ssh.rb +579 -0
  70. data/lib/kitchen/transport/winrm.rb +546 -0
  71. data/lib/kitchen/transport.rb +40 -0
  72. data/lib/kitchen/util.rb +229 -0
  73. data/lib/kitchen/verifier/base.rb +243 -0
  74. data/lib/kitchen/verifier/busser.rb +275 -0
  75. data/lib/kitchen/verifier/dummy.rb +75 -0
  76. data/lib/kitchen/verifier/shell.rb +99 -0
  77. data/lib/kitchen/verifier.rb +39 -0
  78. data/lib/kitchen/version.rb +20 -0
  79. data/lib/kitchen/which.rb +26 -0
  80. data/lib/kitchen.rb +152 -0
  81. data/lib/vendor/hash_recursive_merge.rb +79 -0
  82. data/support/busser_install_command.ps1 +14 -0
  83. data/support/busser_install_command.sh +21 -0
  84. data/support/chef-client-fail-if-update-handler.rb +15 -0
  85. data/support/chef_base_init_command.ps1 +18 -0
  86. data/support/chef_base_init_command.sh +1 -0
  87. data/support/chef_base_install_command.ps1 +85 -0
  88. data/support/chef_base_install_command.sh +229 -0
  89. data/support/download_helpers.sh +109 -0
  90. data/support/dummy-validation.pem +27 -0
  91. data/templates/driver/CHANGELOG.md.erb +3 -0
  92. data/templates/driver/Gemfile.erb +3 -0
  93. data/templates/driver/README.md.erb +64 -0
  94. data/templates/driver/Rakefile.erb +21 -0
  95. data/templates/driver/driver.rb.erb +23 -0
  96. data/templates/driver/gemspec.erb +29 -0
  97. data/templates/driver/gitignore.erb +17 -0
  98. data/templates/driver/license_apachev2.erb +15 -0
  99. data/templates/driver/license_lgplv3.erb +16 -0
  100. data/templates/driver/license_mit.erb +22 -0
  101. data/templates/driver/license_reserved.erb +5 -0
  102. data/templates/driver/tailor.erb +4 -0
  103. data/templates/driver/travis.yml.erb +11 -0
  104. data/templates/driver/version.rb.erb +12 -0
  105. data/templates/init/chefignore.erb +2 -0
  106. data/templates/init/kitchen.yml.erb +18 -0
  107. data/test-kitchen.gemspec +52 -0
  108. metadata +528 -0
@@ -0,0 +1,90 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2012, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require "mixlib/shellout" unless defined?(Mixlib::ShellOut)
19
+
20
+ module Kitchen
21
+ # Mixin that wraps a command shell out invocation, providing a #run_command
22
+ # method.
23
+ #
24
+ # @author Fletcher Nichol <fnichol@nichol.ca>
25
+ module ShellOut
26
+ # Wrapped exception for any interally raised shell out commands.
27
+ class ShellCommandFailed < TransientFailure; end
28
+
29
+ # Executes a command in a subshell on the local running system.
30
+ #
31
+ # @param cmd [String] command to be executed locally
32
+ # @param options [Hash] additional configuration of command
33
+ # @option options [TrueClass, FalseClass] :use_sudo whether or not to use
34
+ # sudo
35
+ # @option options [String] :sudo_command custom sudo command to use.
36
+ # Default is "sudo -E".
37
+ # @option options [String] :log_subject used in the output or log header
38
+ # for clarity and context. Default is "local".
39
+ # @option options [String] :cwd the directory to chdir to before running
40
+ # the command
41
+ # @option options [Hash] :environment a Hash of environment variables to
42
+ # set before the command is run. By default, the environment will
43
+ # *always* be set to `'LC_ALL' => 'C'` to prevent issues with multibyte
44
+ # characters in Ruby 1.8. To avoid this, use :environment => nil for
45
+ # *no* extra environment settings, or
46
+ # `:environment => {'LC_ALL'=>nil, ...}` to set other environment
47
+ # settings without changing the locale.
48
+ # @option options [Integer] :timeout Numeric value for the number of
49
+ # seconds to wait on the child process before raising an Exception.
50
+ # This is calculated as the total amount of time that ShellOut waited on
51
+ # the child process without receiving any output (i.e., IO.select
52
+ # returned nil). Default is 60000 seconds. Note: the stdlib Timeout
53
+ # library is not used.
54
+ # @return [String] the standard output of the command as a String
55
+ # @raise [ShellCommandFailed] if the command fails
56
+ # @raise [Error] for all other unexpected exceptions
57
+ def run_command(cmd, options = {})
58
+ if options.fetch(:use_sudo, false)
59
+ cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}"
60
+ end
61
+ subject = "[#{options.fetch(:log_subject, "local")} command]"
62
+
63
+ debug("#{subject} BEGIN (#{cmd})")
64
+ sh = Mixlib::ShellOut.new(cmd, shell_opts(options))
65
+ sh.run_command
66
+ debug("#{subject} END #{Util.duration(sh.execution_time)}")
67
+ sh.error!
68
+ sh.stdout
69
+ rescue Mixlib::ShellOut::ShellCommandFailed => ex
70
+ raise ShellCommandFailed, ex.message
71
+ rescue Exception => error # rubocop:disable Lint/RescueException
72
+ error.extend(Kitchen::Error)
73
+ raise
74
+ end
75
+
76
+ private
77
+
78
+ # Returns a hash of MixLib::ShellOut options for the command.
79
+ #
80
+ # @param options [Hash] a Hash of options
81
+ # @return [Hash] a new Hash of options, filterd and merged with defaults
82
+ # @api private
83
+ def shell_opts(options)
84
+ filtered_opts = options.reject do |key, _value|
85
+ %i{use_sudo sudo_command log_subject quiet}.include?(key)
86
+ end
87
+ { live_stream: logger, timeout: 60_000 }.merge(filtered_opts)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,289 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2013, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require "logger"
19
+ module Net
20
+ autoload :SSH, "net/ssh"
21
+ end
22
+ require "socket" unless defined?(Socket)
23
+
24
+ require_relative "errors"
25
+ require_relative "login_command"
26
+
27
+ module Kitchen
28
+ # Wrapped exception for any internally raised SSH-related errors.
29
+ #
30
+ # @author Fletcher Nichol <fnichol@nichol.ca>
31
+ class SSHFailed < TransientFailure; end
32
+
33
+ # Class to help establish SSH connections, issue remote commands, and
34
+ # transfer files between a local system and remote node.
35
+ #
36
+ # @author Fletcher Nichol <fnichol@nichol.ca>
37
+ class SSH
38
+ # Constructs a new SSH object.
39
+ #
40
+ # @example basic usage
41
+ #
42
+ # ssh = Kitchen::SSH.new("remote.example.com", "root")
43
+ # ssh.exec("sudo apt-get update")
44
+ # ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
45
+ # ssh.shutdown
46
+ #
47
+ # @example block usage
48
+ #
49
+ # Kitchen::SSH.new("remote.example.com", "root") do |ssh|
50
+ # ssh.exec("sudo apt-get update")
51
+ # ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
52
+ # end
53
+ #
54
+ # @param hostname [String] the remote hostname (IP address, FQDN, etc.)
55
+ # @param username [String] the username for the remote host
56
+ # @param options [Hash] configuration options
57
+ # @option options [Logger] :logger the logger to use
58
+ # (default: `::Logger.new(STDOUT)`)
59
+ # @yield [self] if a block is given then the constructed object yields
60
+ # itself and calls `#shutdown` at the end, closing the remote connection
61
+ def initialize(hostname, username, options = {})
62
+ @hostname = hostname
63
+ @username = username
64
+ @options = options.dup
65
+ @logger = @options.delete(:logger) || ::Logger.new(STDOUT)
66
+
67
+ if block_given?
68
+ yield self
69
+ shutdown
70
+ end
71
+ end
72
+
73
+ # Execute a command on the remote host.
74
+ #
75
+ # @param cmd [String] command string to execute
76
+ # @raise [SSHFailed] if the command does not exit with a 0 code
77
+ def exec(cmd)
78
+ logger.debug("[SSH] #{self} (#{cmd})")
79
+ exit_code = exec_with_exit(cmd)
80
+
81
+ if exit_code != 0
82
+ raise SSHFailed, "SSH exited (#{exit_code}) for command: [#{cmd}]"
83
+ end
84
+ end
85
+
86
+ # Uploads a local file to remote host.
87
+ #
88
+ # @param local [String] path to local file
89
+ # @param remote [String] path to remote file destination
90
+ # @param options [Hash] configuration options that are passed to
91
+ # `Net::SCP.upload`
92
+ # @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
93
+ def upload!(local, remote, options = {}, &progress)
94
+ require "net/scp" unless defined?(Net::SCP)
95
+ if progress.nil?
96
+ progress = lambda do |_ch, name, sent, total|
97
+ logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total
98
+ end
99
+ end
100
+
101
+ session.scp.upload!(local, remote, options, &progress)
102
+ end
103
+
104
+ def upload(local, remote, options = {}, &progress)
105
+ require "net/scp" unless defined?(Net::SCP)
106
+ if progress.nil?
107
+ progress = lambda do |_ch, name, sent, total|
108
+ if sent == total
109
+ logger.debug("Async Uploaded #{name} (#{total} bytes)")
110
+ end
111
+ end
112
+ end
113
+
114
+ session.scp.upload(local, remote, options, &progress)
115
+ end
116
+
117
+ # Uploads a recursive directory to remote host.
118
+ #
119
+ # @param local [String] path to local file or directory
120
+ # @param remote [String] path to remote file destination
121
+ # @param options [Hash] configuration options that are passed to
122
+ # `Net::SCP.upload`
123
+ # @option options [true,false] :recursive recursive copy (default: `true`)
124
+ # @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
125
+ def upload_path!(local, remote, options = {}, &progress)
126
+ options = { recursive: true }.merge(options)
127
+
128
+ upload!(local, remote, options, &progress)
129
+ end
130
+
131
+ def upload_path(local, remote, options = {}, &progress)
132
+ options = { recursive: true }.merge(options)
133
+ upload(local, remote, options, &progress)
134
+ end
135
+
136
+ # Shuts down the session connection, if it is still active.
137
+ def shutdown
138
+ return if @session.nil?
139
+
140
+ logger.debug("[SSH] closing connection to #{self}")
141
+ session.shutdown!
142
+ ensure
143
+ @session = nil
144
+ end
145
+
146
+ # Blocks until the remote host's SSH TCP port is listening.
147
+ def wait
148
+ logger.info("Waiting for #{hostname}:#{port}...") until test_ssh
149
+ end
150
+
151
+ # Builds a LoginCommand which can be used to open an interactive session
152
+ # on the remote host.
153
+ #
154
+ # @return [LoginCommand] the login command
155
+ def login_command
156
+ args = %w{ -o UserKnownHostsFile=/dev/null }
157
+ args += %w{ -o StrictHostKeyChecking=no }
158
+ args += %w{ -o IdentitiesOnly=yes } if options[:keys]
159
+ args += %W{ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} }
160
+ if options.key?(:forward_agent)
161
+ args += %W{ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} }
162
+ end
163
+ Array(options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key} } }
164
+ args += %W{ -p #{port} }
165
+ args += %W{ #{username}@#{hostname} }
166
+
167
+ LoginCommand.new("ssh", args)
168
+ end
169
+
170
+ private
171
+
172
+ # TCP socket exceptions
173
+ SOCKET_EXCEPTIONS = [
174
+ SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
175
+ Errno::ENETUNREACH, IOError
176
+ ].freeze
177
+
178
+ # @return [String] the remote hostname
179
+ # @api private
180
+ attr_reader :hostname
181
+
182
+ # @return [String] the username for the remote host
183
+ # @api private
184
+ attr_reader :username
185
+
186
+ # @return [Hash] SSH options, passed to `Net::SSH.start`
187
+ attr_reader :options
188
+
189
+ # @return [Logger] the logger to use
190
+ # @api private
191
+ attr_reader :logger
192
+
193
+ # Builds the Net::SSH session connection or returns the existing one if
194
+ # built.
195
+ #
196
+ # @return [Net::SSH::Connection::Session] the SSH connection session
197
+ # @api private
198
+ def session
199
+ @session ||= establish_connection
200
+ end
201
+
202
+ # Establish a connection session to the remote host.
203
+ #
204
+ # @return [Net::SSH::Connection::Session] the SSH connection session
205
+ # @api private
206
+ def establish_connection
207
+ rescue_exceptions = [
208
+ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
209
+ Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
210
+ Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout
211
+ ]
212
+ retries = options[:ssh_retries] || 3
213
+
214
+ begin
215
+ logger.debug("[SSH] opening connection to #{self}")
216
+ Net::SSH.start(hostname, username, options)
217
+ rescue *rescue_exceptions => e
218
+ retries -= 1
219
+ if retries > 0
220
+ logger.info("[SSH] connection failed, retrying (#{e.inspect})")
221
+ sleep options[:ssh_timeout] || 1
222
+ retry
223
+ else
224
+ logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
225
+ raise
226
+ end
227
+ end
228
+ end
229
+
230
+ # String representation of object, reporting its connection details and
231
+ # configuration.
232
+ #
233
+ # @api private
234
+ def to_s
235
+ "#{username}@#{hostname}:#{port}<#{options.inspect}>"
236
+ end
237
+
238
+ # @return [Integer] SSH port (default: 22)
239
+ # @api private
240
+ def port
241
+ options.fetch(:port, 22)
242
+ end
243
+
244
+ # Execute a remote command and return the command's exit code.
245
+ #
246
+ # @param cmd [String] command string to execute
247
+ # @return [Integer] the exit code of the command
248
+ # @api private
249
+ def exec_with_exit(cmd)
250
+ exit_code = nil
251
+ session.open_channel do |channel|
252
+ channel.request_pty
253
+
254
+ channel.exec(cmd) do |_ch, _success|
255
+ channel.on_data do |_ch, data|
256
+ logger << data
257
+ end
258
+
259
+ channel.on_extended_data do |_ch, _type, data|
260
+ logger << data
261
+ end
262
+
263
+ channel.on_request("exit-status") do |_ch, data|
264
+ exit_code = data.read_long
265
+ end
266
+ end
267
+ end
268
+ session.loop
269
+ exit_code
270
+ end
271
+
272
+ # Test a remote TCP socket (presumably SSH) for connectivity.
273
+ #
274
+ # @return [true,false] a truthy value if the socket is ready and false
275
+ # otherwise
276
+ # @api private
277
+ def test_ssh
278
+ socket = TCPSocket.new(hostname, port)
279
+ IO.select([socket], nil, nil, 5)
280
+ rescue *SOCKET_EXCEPTIONS
281
+ sleep options[:ssh_timeout] || 2
282
+ false
283
+ rescue Errno::EPERM, Errno::ETIMEDOUT
284
+ false
285
+ ensure
286
+ socket && socket.close
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,112 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2013, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ autoload :YAML, "yaml"
19
+
20
+ module Kitchen
21
+ # Exception class for any exceptions raised when reading and parsing a state
22
+ # file from disk
23
+ class StateFileLoadError < StandardError; end
24
+
25
+ # State persistence manager for instances between actions and invocations.
26
+ #
27
+ # @author Fletcher Nichol <fnichol@nichol.ca>
28
+ class StateFile
29
+ # Constructs an new instance taking the kitchen root and instance name.
30
+ #
31
+ # @param kitchen_root [String] path to the Kitchen project's root directory
32
+ # @param name [String] name of the instance representing this state
33
+ def initialize(kitchen_root, name)
34
+ @file_name = File.expand_path(
35
+ File.join(kitchen_root, ".kitchen", "#{name}.yml")
36
+ )
37
+ end
38
+
39
+ # Reads and loads an instance's state into a Hash data structure which is
40
+ # returned.
41
+ #
42
+ # @return [Hash] a hash representation of an instance's state
43
+ # @raise [StateFileLoadError] if there is a problem loading the state file
44
+ # from disk and loading it into a Hash
45
+ def read
46
+ if File.exist?(file_name) && !File.zero?(file_name)
47
+ Util.symbolized_hash(deserialize_string(read_file))
48
+ else
49
+ {}
50
+ end
51
+ end
52
+
53
+ # Serializes the state hash and writes a state file to disk.
54
+ #
55
+ # @param state [Hash] the current state of the instance
56
+ def write(state)
57
+ dir = File.dirname(file_name)
58
+ serialized_string = serialize_hash(Util.stringified_hash(state))
59
+
60
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
61
+ File.open(file_name, "wb") { |f| f.write(serialized_string) }
62
+ end
63
+
64
+ # Destroys a state file on disk if it exists.
65
+ def destroy
66
+ FileUtils.rm_f(file_name) if File.exist?(file_name)
67
+ end
68
+
69
+ # Returns a Hash of configuration and other useful diagnostic information.
70
+ #
71
+ # @return [Hash] a diagnostic hash
72
+ def diagnose
73
+ raw = read
74
+ result = {}
75
+ raw.keys.sort.each { |k| result[k] = raw[k] }
76
+ result
77
+ end
78
+
79
+ private
80
+
81
+ # @return [String] absolute path to the yaml state file on disk
82
+ # @api private
83
+ attr_reader :file_name
84
+
85
+ # @return [String] a string representation of the yaml state file
86
+ # @api private
87
+ def read_file
88
+ IO.read(file_name)
89
+ end
90
+
91
+ # Parses a YAML string and returns a Hash.
92
+ #
93
+ # @param string [String] a yaml document as a string
94
+ # @return [Hash] a hash
95
+ # @raise [StateFileLoadError] if the string document cannot be parsed
96
+ # @api private
97
+ def deserialize_string(string)
98
+ YAML.safe_load(string)
99
+ rescue SyntaxError, Psych::SyntaxError, Psych::DisallowedClass => ex
100
+ raise StateFileLoadError, "Error parsing #{file_name} (#{ex.message})"
101
+ end
102
+
103
+ # Serializes a Hash into a YAML string.
104
+ #
105
+ # @param hash [Hash] a hash
106
+ # @return [String] a yaml document as a string
107
+ # @api private
108
+ def serialize_hash(hash)
109
+ ::YAML.dump(hash)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2012, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Kitchen
19
+ # A logical configuration representing a test case or fixture that will be
20
+ # executed on a platform.
21
+ #
22
+ # @author Fletcher Nichol <fnichol@nichol.ca>
23
+ class Suite
24
+ # @return [String] logical name of this suite
25
+ attr_reader :name
26
+
27
+ # @return [Array] Array of names of excluded platforms
28
+ attr_reader :excludes
29
+
30
+ # @return [Array] Array of names of only included platforms
31
+ attr_reader :includes
32
+
33
+ # Constructs a new suite.
34
+ #
35
+ # @param [Hash] options configuration for a new suite
36
+ # @option options [String] :name logical name of this suit (**Required**)
37
+ # @option options [String] :excludes Array of names of excluded platforms
38
+ # @option options [String] :includes Array of names of only included
39
+ # platforms
40
+ def initialize(options = {})
41
+ @name = options.fetch(:name) do
42
+ raise ClientError, "Suite#new requires option :name"
43
+ end
44
+ @excludes = options.fetch(:excludes, [])
45
+ @includes = options.fetch(:includes, [])
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,63 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2012, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require "thor" unless defined?(Thor)
19
+
20
+ require_relative "../kitchen"
21
+
22
+ module Kitchen
23
+ # Kitchen Thor task generator.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ class ThorTasks < Thor
27
+ namespace :kitchen
28
+
29
+ # Creates Kitchen Thor tasks and allows the callee to configure it.
30
+ #
31
+ # @yield [self] gives itself to the block
32
+ def initialize(*args)
33
+ super
34
+ @config = Kitchen::Config.new
35
+ Kitchen.logger = Kitchen.default_file_logger(nil, false)
36
+ yield self if block_given?
37
+ define
38
+ end
39
+
40
+ private
41
+
42
+ # @return [Config] a Kitchen::Config
43
+ attr_reader :config
44
+
45
+ # Generates a test Thor task for each instance and one to test all
46
+ # instances in serial.
47
+ #
48
+ # @api private
49
+ def define
50
+ config.instances.each do |instance|
51
+ self.class.desc instance.name, "Run #{instance.name} test instance"
52
+ self.class.send(:define_method, instance.name.tr("-", "_")) do
53
+ instance.test(:always)
54
+ end
55
+ end
56
+
57
+ self.class.desc "all", "Run all test instances"
58
+ self.class.send(:define_method, :all) do
59
+ config.instances.each { |i| invoke i.name.tr("-", "_") }
60
+ end
61
+ end
62
+ end
63
+ end