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.
- checksums.yaml +7 -0
- data/Gemfile +21 -0
- data/LICENSE +15 -0
- data/Rakefile +53 -0
- data/bin/zl-kitchen +11 -0
- data/lib/kitchen/base64_stream.rb +48 -0
- data/lib/kitchen/chef_utils_wiring.rb +40 -0
- data/lib/kitchen/cli.rb +413 -0
- data/lib/kitchen/collection.rb +52 -0
- data/lib/kitchen/color.rb +63 -0
- data/lib/kitchen/command/action.rb +41 -0
- data/lib/kitchen/command/console.rb +54 -0
- data/lib/kitchen/command/diagnose.rb +84 -0
- data/lib/kitchen/command/doctor.rb +39 -0
- data/lib/kitchen/command/exec.rb +37 -0
- data/lib/kitchen/command/list.rb +148 -0
- data/lib/kitchen/command/login.rb +39 -0
- data/lib/kitchen/command/package.rb +32 -0
- data/lib/kitchen/command/sink.rb +50 -0
- data/lib/kitchen/command/test.rb +47 -0
- data/lib/kitchen/command.rb +207 -0
- data/lib/kitchen/config.rb +344 -0
- data/lib/kitchen/configurable.rb +616 -0
- data/lib/kitchen/data_munger.rb +1024 -0
- data/lib/kitchen/diagnostic.rb +138 -0
- data/lib/kitchen/driver/base.rb +133 -0
- data/lib/kitchen/driver/dummy.rb +105 -0
- data/lib/kitchen/driver/exec.rb +70 -0
- data/lib/kitchen/driver/proxy.rb +70 -0
- data/lib/kitchen/driver/ssh_base.rb +351 -0
- data/lib/kitchen/driver.rb +40 -0
- data/lib/kitchen/errors.rb +243 -0
- data/lib/kitchen/generator/init.rb +254 -0
- data/lib/kitchen/instance.rb +726 -0
- data/lib/kitchen/lazy_hash.rb +148 -0
- data/lib/kitchen/lifecycle_hook/base.rb +78 -0
- data/lib/kitchen/lifecycle_hook/local.rb +53 -0
- data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
- data/lib/kitchen/lifecycle_hooks.rb +92 -0
- data/lib/kitchen/loader/yaml.rb +377 -0
- data/lib/kitchen/logger.rb +422 -0
- data/lib/kitchen/logging.rb +52 -0
- data/lib/kitchen/login_command.rb +49 -0
- data/lib/kitchen/metadata_chopper.rb +49 -0
- data/lib/kitchen/platform.rb +64 -0
- data/lib/kitchen/plugin.rb +76 -0
- data/lib/kitchen/plugin_base.rb +60 -0
- data/lib/kitchen/provisioner/base.rb +269 -0
- data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
- data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
- data/lib/kitchen/provisioner/chef_apply.rb +121 -0
- data/lib/kitchen/provisioner/chef_base.rb +705 -0
- data/lib/kitchen/provisioner/chef_infra.rb +167 -0
- data/lib/kitchen/provisioner/chef_solo.rb +82 -0
- data/lib/kitchen/provisioner/chef_zero.rb +12 -0
- data/lib/kitchen/provisioner/dummy.rb +75 -0
- data/lib/kitchen/provisioner/shell.rb +157 -0
- data/lib/kitchen/provisioner.rb +42 -0
- data/lib/kitchen/rake_tasks.rb +80 -0
- data/lib/kitchen/shell_out.rb +90 -0
- data/lib/kitchen/ssh.rb +289 -0
- data/lib/kitchen/state_file.rb +112 -0
- data/lib/kitchen/suite.rb +48 -0
- data/lib/kitchen/thor_tasks.rb +63 -0
- data/lib/kitchen/transport/base.rb +236 -0
- data/lib/kitchen/transport/dummy.rb +78 -0
- data/lib/kitchen/transport/exec.rb +145 -0
- data/lib/kitchen/transport/ssh.rb +579 -0
- data/lib/kitchen/transport/winrm.rb +546 -0
- data/lib/kitchen/transport.rb +40 -0
- data/lib/kitchen/util.rb +229 -0
- data/lib/kitchen/verifier/base.rb +243 -0
- data/lib/kitchen/verifier/busser.rb +275 -0
- data/lib/kitchen/verifier/dummy.rb +75 -0
- data/lib/kitchen/verifier/shell.rb +99 -0
- data/lib/kitchen/verifier.rb +39 -0
- data/lib/kitchen/version.rb +20 -0
- data/lib/kitchen/which.rb +26 -0
- data/lib/kitchen.rb +152 -0
- data/lib/vendor/hash_recursive_merge.rb +79 -0
- data/support/busser_install_command.ps1 +14 -0
- data/support/busser_install_command.sh +21 -0
- data/support/chef-client-fail-if-update-handler.rb +15 -0
- data/support/chef_base_init_command.ps1 +18 -0
- data/support/chef_base_init_command.sh +1 -0
- data/support/chef_base_install_command.ps1 +85 -0
- data/support/chef_base_install_command.sh +229 -0
- data/support/download_helpers.sh +109 -0
- data/support/dummy-validation.pem +27 -0
- data/templates/driver/CHANGELOG.md.erb +3 -0
- data/templates/driver/Gemfile.erb +3 -0
- data/templates/driver/README.md.erb +64 -0
- data/templates/driver/Rakefile.erb +21 -0
- data/templates/driver/driver.rb.erb +23 -0
- data/templates/driver/gemspec.erb +29 -0
- data/templates/driver/gitignore.erb +17 -0
- data/templates/driver/license_apachev2.erb +15 -0
- data/templates/driver/license_lgplv3.erb +16 -0
- data/templates/driver/license_mit.erb +22 -0
- data/templates/driver/license_reserved.erb +5 -0
- data/templates/driver/tailor.erb +4 -0
- data/templates/driver/travis.yml.erb +11 -0
- data/templates/driver/version.rb.erb +12 -0
- data/templates/init/chefignore.erb +2 -0
- data/templates/init/kitchen.yml.erb +18 -0
- data/test-kitchen.gemspec +52 -0
- 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
|
data/lib/kitchen/ssh.rb
ADDED
|
@@ -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
|