test-kitchen 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cane +1 -1
- data/.rubocop.yml +3 -0
- data/.travis.yml +20 -9
- data/CHANGELOG.md +219 -108
- data/Gemfile +10 -6
- data/Guardfile +38 -9
- data/README.md +11 -1
- data/Rakefile +21 -37
- data/bin/kitchen +4 -4
- data/features/kitchen_action_commands.feature +161 -0
- data/features/kitchen_console_command.feature +34 -0
- data/features/kitchen_diagnose_command.feature +64 -0
- data/features/kitchen_init_command.feature +29 -17
- data/features/kitchen_list_command.feature +2 -2
- data/features/kitchen_login_command.feature +56 -0
- data/features/{sink_command.feature → kitchen_sink_command.feature} +0 -0
- data/features/kitchen_test_command.feature +88 -0
- data/features/step_definitions/gem_steps.rb +8 -6
- data/features/step_definitions/git_steps.rb +4 -2
- data/features/step_definitions/output_steps.rb +5 -0
- data/features/support/env.rb +12 -9
- data/lib/kitchen.rb +60 -38
- data/lib/kitchen/base64_stream.rb +55 -0
- data/lib/kitchen/busser.rb +124 -58
- data/lib/kitchen/cli.rb +121 -38
- data/lib/kitchen/collection.rb +3 -3
- data/lib/kitchen/color.rb +4 -4
- data/lib/kitchen/command.rb +78 -11
- data/lib/kitchen/command/action.rb +3 -2
- data/lib/kitchen/command/console.rb +12 -5
- data/lib/kitchen/command/diagnose.rb +17 -3
- data/lib/kitchen/command/driver_discover.rb +26 -7
- data/lib/kitchen/command/exec.rb +41 -0
- data/lib/kitchen/command/list.rb +44 -14
- data/lib/kitchen/command/login.rb +2 -1
- data/lib/kitchen/command/sink.rb +2 -1
- data/lib/kitchen/command/test.rb +5 -4
- data/lib/kitchen/config.rb +146 -14
- data/lib/kitchen/configurable.rb +314 -0
- data/lib/kitchen/data_munger.rb +522 -18
- data/lib/kitchen/diagnostic.rb +43 -4
- data/lib/kitchen/driver.rb +4 -4
- data/lib/kitchen/driver/base.rb +80 -115
- data/lib/kitchen/driver/dummy.rb +34 -6
- data/lib/kitchen/driver/proxy.rb +14 -3
- data/lib/kitchen/driver/ssh_base.rb +61 -7
- data/lib/kitchen/errors.rb +109 -9
- data/lib/kitchen/generator/driver_create.rb +39 -5
- data/lib/kitchen/generator/init.rb +130 -45
- data/lib/kitchen/instance.rb +162 -28
- data/lib/kitchen/lazy_hash.rb +79 -7
- data/lib/kitchen/loader/yaml.rb +159 -27
- data/lib/kitchen/logger.rb +267 -21
- data/lib/kitchen/logging.rb +30 -3
- data/lib/kitchen/login_command.rb +11 -2
- data/lib/kitchen/metadata_chopper.rb +2 -2
- data/lib/kitchen/provisioner.rb +4 -4
- data/lib/kitchen/provisioner/base.rb +107 -103
- data/lib/kitchen/provisioner/chef/berkshelf.rb +36 -8
- data/lib/kitchen/provisioner/chef/librarian.rb +40 -11
- data/lib/kitchen/provisioner/chef_base.rb +206 -167
- data/lib/kitchen/provisioner/chef_solo.rb +25 -7
- data/lib/kitchen/provisioner/chef_zero.rb +105 -29
- data/lib/kitchen/provisioner/dummy.rb +1 -1
- data/lib/kitchen/provisioner/shell.rb +21 -6
- data/lib/kitchen/rake_tasks.rb +8 -3
- data/lib/kitchen/shell_out.rb +15 -18
- data/lib/kitchen/ssh.rb +122 -27
- data/lib/kitchen/state_file.rb +24 -7
- data/lib/kitchen/thor_tasks.rb +9 -4
- data/lib/kitchen/util.rb +43 -118
- data/lib/kitchen/version.rb +1 -1
- data/lib/vendor/hash_recursive_merge.rb +10 -2
- data/spec/kitchen/base64_stream_spec.rb +77 -0
- data/spec/kitchen/busser_spec.rb +490 -0
- data/spec/kitchen/collection_spec.rb +10 -10
- data/spec/kitchen/color_spec.rb +2 -2
- data/spec/kitchen/config_spec.rb +234 -62
- data/spec/kitchen/configurable_spec.rb +490 -0
- data/spec/kitchen/data_munger_spec.rb +1070 -862
- data/spec/kitchen/diagnostic_spec.rb +79 -0
- data/spec/kitchen/driver/base_spec.rb +80 -85
- data/spec/kitchen/driver/dummy_spec.rb +43 -14
- data/spec/kitchen/driver/proxy_spec.rb +134 -0
- data/spec/kitchen/driver/ssh_base_spec.rb +644 -0
- data/spec/kitchen/driver_spec.rb +15 -15
- data/spec/kitchen/errors_spec.rb +309 -0
- data/spec/kitchen/instance_spec.rb +143 -46
- data/spec/kitchen/lazy_hash_spec.rb +36 -9
- data/spec/kitchen/loader/yaml_spec.rb +237 -226
- data/spec/kitchen/logger_spec.rb +419 -0
- data/spec/kitchen/logging_spec.rb +59 -0
- data/spec/kitchen/login_command_spec.rb +49 -0
- data/spec/kitchen/metadata_chopper_spec.rb +82 -0
- data/spec/kitchen/platform_spec.rb +4 -4
- data/spec/kitchen/provisioner/base_spec.rb +65 -125
- data/spec/kitchen/provisioner/chef_base_spec.rb +798 -0
- data/spec/kitchen/provisioner/chef_solo_spec.rb +316 -0
- data/spec/kitchen/provisioner/chef_zero_spec.rb +624 -0
- data/spec/kitchen/provisioner/shell_spec.rb +269 -0
- data/spec/kitchen/provisioner_spec.rb +6 -6
- data/spec/kitchen/shell_out_spec.rb +143 -0
- data/spec/kitchen/ssh_spec.rb +683 -0
- data/spec/kitchen/state_file_spec.rb +28 -21
- data/spec/kitchen/suite_spec.rb +7 -7
- data/spec/kitchen/util_spec.rb +68 -10
- data/spec/kitchen_spec.rb +107 -0
- data/spec/spec_helper.rb +18 -13
- data/support/chef-client-zero.rb +10 -9
- data/support/chef_helpers.sh +16 -0
- data/support/download_helpers.sh +109 -0
- data/test-kitchen.gemspec +42 -33
- metadata +107 -33
data/lib/kitchen/ssh.rb
CHANGED
@@ -16,20 +16,20 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
19
|
+
require "logger"
|
20
|
+
require "net/ssh"
|
21
|
+
require "net/scp"
|
22
|
+
require "socket"
|
23
23
|
|
24
|
-
require
|
25
|
-
require
|
24
|
+
require "kitchen/errors"
|
25
|
+
require "kitchen/login_command"
|
26
26
|
|
27
27
|
module Kitchen
|
28
28
|
|
29
29
|
# Wrapped exception for any internally raised SSH-related errors.
|
30
30
|
#
|
31
31
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
32
|
-
class SSHFailed < TransientFailure
|
32
|
+
class SSHFailed < TransientFailure; end
|
33
33
|
|
34
34
|
# Class to help establish SSH connections, issue remote commands, and
|
35
35
|
# transfer files between a local system and remote node.
|
@@ -37,6 +37,29 @@ module Kitchen
|
|
37
37
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
38
38
|
class SSH
|
39
39
|
|
40
|
+
# Constructs a new SSH object.
|
41
|
+
#
|
42
|
+
# @example basic usage
|
43
|
+
#
|
44
|
+
# ssh = Kitchen::SSH.new("remote.example.com", "root")
|
45
|
+
# ssh.exec("sudo apt-get update")
|
46
|
+
# ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
|
47
|
+
# ssh.shutdown
|
48
|
+
#
|
49
|
+
# @example block usage
|
50
|
+
#
|
51
|
+
# Kitchen::SSH.new("remote.example.com", "root") do |ssh|
|
52
|
+
# ssh.exec("sudo apt-get update")
|
53
|
+
# ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# @param hostname [String] the remote hostname (IP address, FQDN, etc.)
|
57
|
+
# @param username [String] the username for the remote host
|
58
|
+
# @param options [Hash] configuration options
|
59
|
+
# @option options [Logger] :logger the logger to use
|
60
|
+
# (default: `::Logger.new(STDOUT)`)
|
61
|
+
# @yield [self] if a block is given then the constructed object yields
|
62
|
+
# itself and calls `#shutdown` at the end, closing the remote connection
|
40
63
|
def initialize(hostname, username, options = {})
|
41
64
|
@hostname = hostname
|
42
65
|
@username = username
|
@@ -49,6 +72,10 @@ module Kitchen
|
|
49
72
|
end
|
50
73
|
end
|
51
74
|
|
75
|
+
# Execute a command on the remote host.
|
76
|
+
#
|
77
|
+
# @param cmd [String] command string to execute
|
78
|
+
# @raise [SSHFailed] if the command does not exit with a 0 code
|
52
79
|
def exec(cmd)
|
53
80
|
logger.debug("[SSH] #{self} (#{cmd})")
|
54
81
|
exit_code = exec_with_exit(cmd)
|
@@ -58,9 +85,16 @@ module Kitchen
|
|
58
85
|
end
|
59
86
|
end
|
60
87
|
|
88
|
+
# Uploads a local file to remote host.
|
89
|
+
#
|
90
|
+
# @param local [String] path to local file
|
91
|
+
# @param remote [String] path to remote file destination
|
92
|
+
# @param options [Hash] configuration options that are passed to
|
93
|
+
# `Net::SCP.upload`
|
94
|
+
# @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
|
61
95
|
def upload!(local, remote, options = {}, &progress)
|
62
96
|
if progress.nil?
|
63
|
-
progress = lambda { |
|
97
|
+
progress = lambda { |_ch, name, sent, total|
|
64
98
|
if sent == total
|
65
99
|
logger.debug("Uploaded #{name} (#{total} bytes)")
|
66
100
|
end
|
@@ -70,12 +104,21 @@ module Kitchen
|
|
70
104
|
session.scp.upload!(local, remote, options, &progress)
|
71
105
|
end
|
72
106
|
|
107
|
+
# Uploads a recursive directory to remote host.
|
108
|
+
#
|
109
|
+
# @param local [String] path to local file or directory
|
110
|
+
# @param remote [String] path to remote file destination
|
111
|
+
# @param options [Hash] configuration options that are passed to
|
112
|
+
# `Net::SCP.upload`
|
113
|
+
# @option options [true,false] :recursive recursive copy (default: `true`)
|
114
|
+
# @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
|
73
115
|
def upload_path!(local, remote, options = {}, &progress)
|
74
116
|
options = { :recursive => true }.merge(options)
|
75
117
|
|
76
118
|
upload!(local, remote, options, &progress)
|
77
119
|
end
|
78
120
|
|
121
|
+
# Shuts down the session connection, if it is still active.
|
79
122
|
def shutdown
|
80
123
|
return if @session.nil?
|
81
124
|
|
@@ -85,45 +128,82 @@ module Kitchen
|
|
85
128
|
@session = nil
|
86
129
|
end
|
87
130
|
|
131
|
+
# Blocks until the remote host's SSH TCP port is listening.
|
88
132
|
def wait
|
89
133
|
logger.info("Waiting for #{hostname}:#{port}...") until test_ssh
|
90
134
|
end
|
91
135
|
|
136
|
+
# Builds a LoginCommand which can be used to open an interactive session
|
137
|
+
# on the remote host.
|
138
|
+
#
|
139
|
+
# @return [LoginCommand] the login command
|
92
140
|
def login_command
|
93
|
-
args = %W
|
94
|
-
args += %W
|
95
|
-
args += %W
|
96
|
-
args += %W
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
args += %W
|
141
|
+
args = %W[ -o UserKnownHostsFile=/dev/null ]
|
142
|
+
args += %W[ -o StrictHostKeyChecking=no ]
|
143
|
+
args += %W[ -o IdentitiesOnly=yes ] if options[:keys]
|
144
|
+
args += %W[ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} ]
|
145
|
+
if options.key?(:forward_agent)
|
146
|
+
args += %W[ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} ]
|
147
|
+
end
|
148
|
+
Array(options[:keys]).each { |ssh_key| args += %W[ -i #{ssh_key} ] }
|
149
|
+
args += %W[ -p #{port} ]
|
150
|
+
args += %W[ #{username}@#{hostname} ]
|
101
151
|
|
102
152
|
LoginCommand.new(["ssh", *args])
|
103
153
|
end
|
104
154
|
|
105
155
|
private
|
106
156
|
|
107
|
-
|
157
|
+
# TCP socket exceptions
|
158
|
+
SOCKET_EXCEPTIONS = [
|
159
|
+
SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
160
|
+
Errno::ENETUNREACH, IOError
|
161
|
+
]
|
162
|
+
|
163
|
+
# @return [String] the remote hostname
|
164
|
+
# @api private
|
165
|
+
attr_reader :hostname
|
166
|
+
|
167
|
+
# @return [String] the username for the remote host
|
168
|
+
# @api private
|
169
|
+
attr_reader :username
|
170
|
+
|
171
|
+
# @return [Hash] SSH options, passed to `Net::SSH.start`
|
172
|
+
attr_reader :options
|
173
|
+
|
174
|
+
# @return [Logger] the logger to use
|
175
|
+
# @api private
|
176
|
+
attr_reader :logger
|
108
177
|
|
178
|
+
# Builds the Net::SSH session connection or returns the existing one if
|
179
|
+
# built.
|
180
|
+
#
|
181
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
182
|
+
# @api private
|
109
183
|
def session
|
110
184
|
@session ||= establish_connection
|
111
185
|
end
|
112
186
|
|
187
|
+
# Establish a connection session to the remote host.
|
188
|
+
#
|
189
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
190
|
+
# @api private
|
113
191
|
def establish_connection
|
114
192
|
rescue_exceptions = [
|
115
193
|
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
|
116
194
|
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
117
|
-
Net::SSH::Disconnect
|
195
|
+
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed
|
118
196
|
]
|
119
|
-
retries = 3
|
197
|
+
retries = options[:ssh_retries] || 3
|
120
198
|
|
121
199
|
begin
|
122
200
|
logger.debug("[SSH] opening connection to #{self}")
|
123
201
|
Net::SSH.start(hostname, username, options)
|
124
202
|
rescue *rescue_exceptions => e
|
125
|
-
|
203
|
+
retries -= 1
|
204
|
+
if retries > 0
|
126
205
|
logger.info("[SSH] connection failed, retrying (#{e.inspect})")
|
206
|
+
sleep options[:ssh_timeout] || 1
|
127
207
|
retry
|
128
208
|
else
|
129
209
|
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
@@ -132,31 +212,42 @@ module Kitchen
|
|
132
212
|
end
|
133
213
|
end
|
134
214
|
|
215
|
+
# String representation of object, reporting its connection details and
|
216
|
+
# configuration.
|
217
|
+
#
|
218
|
+
# @api private
|
135
219
|
def to_s
|
136
220
|
"#{username}@#{hostname}:#{port}<#{options.inspect}>"
|
137
221
|
end
|
138
222
|
|
223
|
+
# @return [Integer] SSH port (default: 22)
|
224
|
+
# @api private
|
139
225
|
def port
|
140
226
|
options.fetch(:port, 22)
|
141
227
|
end
|
142
228
|
|
229
|
+
# Execute a remote command and return the command's exit code.
|
230
|
+
#
|
231
|
+
# @param cmd [String] command string to execute
|
232
|
+
# @return [Integer] the exit code of the command
|
233
|
+
# @api private
|
143
234
|
def exec_with_exit(cmd)
|
144
235
|
exit_code = nil
|
145
236
|
session.open_channel do |channel|
|
146
237
|
|
147
238
|
channel.request_pty
|
148
239
|
|
149
|
-
channel.exec(cmd) do |
|
240
|
+
channel.exec(cmd) do |_ch, _success|
|
150
241
|
|
151
|
-
channel.on_data do |
|
242
|
+
channel.on_data do |_ch, data|
|
152
243
|
logger << data
|
153
244
|
end
|
154
245
|
|
155
|
-
channel.on_extended_data do |
|
246
|
+
channel.on_extended_data do |_ch, _type, data|
|
156
247
|
logger << data
|
157
248
|
end
|
158
249
|
|
159
|
-
channel.on_request("exit-status") do |
|
250
|
+
channel.on_request("exit-status") do |_ch, data|
|
160
251
|
exit_code = data.read_long
|
161
252
|
end
|
162
253
|
end
|
@@ -165,12 +256,16 @@ module Kitchen
|
|
165
256
|
exit_code
|
166
257
|
end
|
167
258
|
|
259
|
+
# Test a remote TCP socket (presumably SSH) for connectivity.
|
260
|
+
#
|
261
|
+
# @return [true,false] a truthy value if the socket is ready and false
|
262
|
+
# otherwise
|
263
|
+
# @api private
|
168
264
|
def test_ssh
|
169
265
|
socket = TCPSocket.new(hostname, port)
|
170
266
|
IO.select([socket], nil, nil, 5)
|
171
|
-
rescue
|
172
|
-
|
173
|
-
sleep 2
|
267
|
+
rescue *SOCKET_EXCEPTIONS
|
268
|
+
sleep options[:ssh_timeout] || 2
|
174
269
|
false
|
175
270
|
rescue Errno::EPERM, Errno::ETIMEDOUT
|
176
271
|
false
|
data/lib/kitchen/state_file.rb
CHANGED
@@ -18,18 +18,20 @@
|
|
18
18
|
|
19
19
|
if RUBY_VERSION <= "1.9.3"
|
20
20
|
# ensure that Psych and not Syck is used for Ruby 1.9.2
|
21
|
-
require
|
22
|
-
YAML::ENGINE.yamler =
|
21
|
+
require "yaml"
|
22
|
+
YAML::ENGINE.yamler = "psych"
|
23
23
|
end
|
24
|
-
require
|
24
|
+
require "safe_yaml/load"
|
25
25
|
|
26
26
|
module Kitchen
|
27
27
|
|
28
28
|
# Exception class for any exceptions raised when reading and parsing a state
|
29
29
|
# file from disk
|
30
|
-
class StateFileLoadError < StandardError
|
30
|
+
class StateFileLoadError < StandardError; end
|
31
31
|
|
32
32
|
# State persistence manager for instances between actions and invocations.
|
33
|
+
#
|
34
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
33
35
|
class StateFile
|
34
36
|
|
35
37
|
# Constructs an new instance taking the kitchen root and instance name.
|
@@ -49,7 +51,7 @@ module Kitchen
|
|
49
51
|
# @raise [StateFileLoadError] if there is a problem loading the state file
|
50
52
|
# from disk and loading it into a Hash
|
51
53
|
def read
|
52
|
-
if File.
|
54
|
+
if File.exist?(file_name) && !File.zero?(file_name)
|
53
55
|
Util.symbolized_hash(deserialize_string(read_file))
|
54
56
|
else
|
55
57
|
Hash.new
|
@@ -63,13 +65,13 @@ module Kitchen
|
|
63
65
|
dir = File.dirname(file_name)
|
64
66
|
serialized_string = serialize_hash(Util.stringified_hash(state))
|
65
67
|
|
66
|
-
FileUtils.mkdir_p(dir) if !
|
68
|
+
FileUtils.mkdir_p(dir) if !File.directory?(dir)
|
67
69
|
File.open(file_name, "wb") { |f| f.write(serialized_string) }
|
68
70
|
end
|
69
71
|
|
70
72
|
# Destroys a state file on disk if it exists.
|
71
73
|
def destroy
|
72
|
-
FileUtils.rm_f(file_name) if File.
|
74
|
+
FileUtils.rm_f(file_name) if File.exist?(file_name)
|
73
75
|
end
|
74
76
|
|
75
77
|
# Returns a Hash of configuration and other useful diagnostic information.
|
@@ -84,18 +86,33 @@ module Kitchen
|
|
84
86
|
|
85
87
|
private
|
86
88
|
|
89
|
+
# @return [String] absolute path to the yaml state file on disk
|
90
|
+
# @api private
|
87
91
|
attr_reader :file_name
|
88
92
|
|
93
|
+
# @return [String] a string representation of the yaml state file
|
94
|
+
# @api private
|
89
95
|
def read_file
|
90
96
|
IO.read(file_name)
|
91
97
|
end
|
92
98
|
|
99
|
+
# Parses a YAML string and returns a Hash.
|
100
|
+
#
|
101
|
+
# @param string [String] a yaml document as a string
|
102
|
+
# @return [Hash] a hash
|
103
|
+
# @raise [StateFileLoadError] if the string document cannot be parsed
|
104
|
+
# @api private
|
93
105
|
def deserialize_string(string)
|
94
106
|
SafeYAML.load(string)
|
95
107
|
rescue SyntaxError, Psych::SyntaxError => ex
|
96
108
|
raise StateFileLoadError, "Error parsing #{file_name} (#{ex.message})"
|
97
109
|
end
|
98
110
|
|
111
|
+
# Serializes a Hash into a YAML string.
|
112
|
+
#
|
113
|
+
# @param hash [Hash] a hash
|
114
|
+
# @return [String] a yaml document as a string
|
115
|
+
# @api private
|
99
116
|
def serialize_hash(hash)
|
100
117
|
::YAML.dump(hash)
|
101
118
|
end
|
data/lib/kitchen/thor_tasks.rb
CHANGED
@@ -16,9 +16,9 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
19
|
+
require "thor"
|
20
20
|
|
21
|
-
require
|
21
|
+
require "kitchen"
|
22
22
|
|
23
23
|
module Kitchen
|
24
24
|
|
@@ -42,19 +42,24 @@ module Kitchen
|
|
42
42
|
|
43
43
|
private
|
44
44
|
|
45
|
+
# @return [Config] a Kitchen::Config
|
45
46
|
attr_reader :config
|
46
47
|
|
48
|
+
# Generates a test Thor task for each instance and one to test all
|
49
|
+
# instances in serial.
|
50
|
+
#
|
51
|
+
# @api private
|
47
52
|
def define
|
48
53
|
config.instances.each do |instance|
|
49
54
|
self.class.desc instance.name, "Run #{instance.name} test instance"
|
50
|
-
self.class.send(:define_method, instance.name.gsub(/-/,
|
55
|
+
self.class.send(:define_method, instance.name.gsub(/-/, "_")) do
|
51
56
|
instance.test(:always)
|
52
57
|
end
|
53
58
|
end
|
54
59
|
|
55
60
|
self.class.desc "all", "Run all test instances"
|
56
61
|
self.class.send(:define_method, :all) do
|
57
|
-
config.instances.each { |i| invoke i.name.gsub(/-/,
|
62
|
+
config.instances.each { |i| invoke i.name.gsub(/-/, "_") }
|
58
63
|
end
|
59
64
|
end
|
60
65
|
end
|
data/lib/kitchen/util.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
4
4
|
#
|
5
|
-
# Copyright (C) 2012, Fletcher Nichol
|
5
|
+
# Copyright (C) 2012, 2013, 2014, Fletcher Nichol
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
@@ -63,9 +63,9 @@ module Kitchen
|
|
63
63
|
# @return [Object] a converted hash with all keys as symbols
|
64
64
|
def self.symbolized_hash(obj)
|
65
65
|
if obj.is_a?(Hash)
|
66
|
-
obj.inject({}) { |h, (k, v)| h[k.to_sym] = symbolized_hash(v)
|
66
|
+
obj.inject({}) { |h, (k, v)| h[k.to_sym] = symbolized_hash(v); h }
|
67
67
|
elsif obj.is_a?(Array)
|
68
|
-
obj.inject([]) { |a,
|
68
|
+
obj.inject([]) { |a, e| a << symbolized_hash(e); a }
|
69
69
|
else
|
70
70
|
obj
|
71
71
|
end
|
@@ -80,9 +80,9 @@ module Kitchen
|
|
80
80
|
# @return [Object] a converted hash with all keys as strings
|
81
81
|
def self.stringified_hash(obj)
|
82
82
|
if obj.is_a?(Hash)
|
83
|
-
obj.inject({}) { |h, (k, v)| h[k.to_s] = stringified_hash(v)
|
83
|
+
obj.inject({}) { |h, (k, v)| h[k.to_s] = stringified_hash(v); h }
|
84
84
|
elsif obj.is_a?(Array)
|
85
|
-
obj.inject([]) { |a,
|
85
|
+
obj.inject([]) { |a, e| a << stringified_hash(e); a }
|
86
86
|
else
|
87
87
|
obj
|
88
88
|
end
|
@@ -96,7 +96,41 @@ module Kitchen
|
|
96
96
|
total = 0 if total.nil?
|
97
97
|
minutes = (total / 60).to_i
|
98
98
|
seconds = (total - (minutes * 60))
|
99
|
-
"(%dm%.2fs)"
|
99
|
+
format("(%dm%.2fs)", minutes, seconds)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Generates a command (or series of commands) wrapped so that it can be
|
103
|
+
# invoked on a remote instance or locally.
|
104
|
+
#
|
105
|
+
# This method uses the Bourne shell (/bin/sh) to maximize the chance of
|
106
|
+
# cross platform portability on Unixlike systems.
|
107
|
+
#
|
108
|
+
# @param [String] the command
|
109
|
+
# @return [String] a wrapped command string
|
110
|
+
def self.wrap_command(cmd)
|
111
|
+
cmd = "false" if cmd.nil?
|
112
|
+
cmd = "true" if cmd.to_s.empty?
|
113
|
+
cmd = cmd.sub(/\n\Z/, "") if cmd =~ /\n\Z/
|
114
|
+
|
115
|
+
"sh -c '\n#{cmd}\n'"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Modifes the given string to strip leading whitespace on each line, the
|
119
|
+
# amount which is calculated by using the first line of text.
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
#
|
123
|
+
# string = <<-STRING
|
124
|
+
# a
|
125
|
+
# b
|
126
|
+
# c
|
127
|
+
# STRING
|
128
|
+
# Util.outdent!(string) # => "a\n b\nc\n"
|
129
|
+
#
|
130
|
+
# @param string [String] the string that will be modified
|
131
|
+
# @return [String] the modified string
|
132
|
+
def self.outdent!(string)
|
133
|
+
string.gsub!(/^ {#{string.index(/[^ ]/)}}/, "")
|
100
134
|
end
|
101
135
|
|
102
136
|
# Returns a set of Bourne Shell (AKA /bin/sh) compatible helper
|
@@ -105,118 +139,9 @@ module Kitchen
|
|
105
139
|
#
|
106
140
|
# @return [String] a string representation of useful helper functions
|
107
141
|
def self.shell_helpers
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
exists() {
|
112
|
-
if command -v $1 >/dev/null 2>&1
|
113
|
-
then
|
114
|
-
return 0
|
115
|
-
else
|
116
|
-
return 1
|
117
|
-
fi
|
118
|
-
}
|
119
|
-
|
120
|
-
# do_wget URL FILENAME
|
121
|
-
do_wget() {
|
122
|
-
echo "trying wget..."
|
123
|
-
wget -O "$2" "$1" 2>/tmp/stderr
|
124
|
-
# check for bad return status
|
125
|
-
test $? -ne 0 && return 1
|
126
|
-
# check for 404 or empty file
|
127
|
-
grep "ERROR 404" /tmp/stderr 2>&1 >/dev/null
|
128
|
-
if test $? -eq 0 || test ! -s "$2"; then
|
129
|
-
return 1
|
130
|
-
fi
|
131
|
-
return 0
|
132
|
-
}
|
133
|
-
|
134
|
-
# do_curl URL FILENAME
|
135
|
-
do_curl() {
|
136
|
-
echo "trying curl..."
|
137
|
-
curl -L "$1" > "$2"
|
138
|
-
# check for bad return status
|
139
|
-
[ $? -ne 0 ] && return 1
|
140
|
-
# check for bad output or empty file
|
141
|
-
grep "The specified key does not exist." "$2" 2>&1 >/dev/null
|
142
|
-
if test $? -eq 0 || test ! -s "$2"; then
|
143
|
-
return 1
|
144
|
-
fi
|
145
|
-
return 0
|
146
|
-
}
|
147
|
-
|
148
|
-
# do_fetch URL FILENAME
|
149
|
-
do_fetch() {
|
150
|
-
echo "trying fetch..."
|
151
|
-
fetch -o "$2" "$1" 2>/tmp/stderr
|
152
|
-
# check for bad return status
|
153
|
-
test $? -ne 0 && return 1
|
154
|
-
return 0
|
155
|
-
}
|
156
|
-
|
157
|
-
# do_perl URL FILENAME
|
158
|
-
do_perl() {
|
159
|
-
echo "trying perl..."
|
160
|
-
perl -e "use LWP::Simple; getprint($ARGV[0]);" "$1" > "$2"
|
161
|
-
# check for bad return status
|
162
|
-
test $? -ne 0 && return 1
|
163
|
-
# check for bad output or empty file
|
164
|
-
# grep "The specified key does not exist." "$2" 2>&1 >/dev/null
|
165
|
-
# if test $? -eq 0 || test ! -s "$2"; then
|
166
|
-
# unable_to_retrieve_package
|
167
|
-
# fi
|
168
|
-
return 0
|
169
|
-
}
|
170
|
-
|
171
|
-
# do_python URL FILENAME
|
172
|
-
do_python() {
|
173
|
-
echo "trying python..."
|
174
|
-
python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2"
|
175
|
-
# check for bad return status
|
176
|
-
test $? -ne 0 && return 1
|
177
|
-
# check for bad output or empty file
|
178
|
-
#grep "The specified key does not exist." "$2" 2>&1 >/dev/null
|
179
|
-
#if test $? -eq 0 || test ! -s "$2"; then
|
180
|
-
# unable_to_retrieve_package
|
181
|
-
#fi
|
182
|
-
return 0
|
183
|
-
}
|
184
|
-
|
185
|
-
# do_download URL FILENAME
|
186
|
-
do_download() {
|
187
|
-
PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
188
|
-
export PATH
|
189
|
-
|
190
|
-
echo "downloading $1"
|
191
|
-
echo " to file $2"
|
192
|
-
|
193
|
-
# we try all of these until we get success.
|
194
|
-
# perl, in particular may be present but LWP::Simple may not be installed
|
195
|
-
|
196
|
-
if exists wget; then
|
197
|
-
do_wget $1 $2 && return 0
|
198
|
-
fi
|
199
|
-
|
200
|
-
if exists curl; then
|
201
|
-
do_curl $1 $2 && return 0
|
202
|
-
fi
|
203
|
-
|
204
|
-
if exists fetch; then
|
205
|
-
do_fetch $1 $2 && return 0
|
206
|
-
fi
|
207
|
-
|
208
|
-
if exists perl; then
|
209
|
-
do_perl $1 $2 && return 0
|
210
|
-
fi
|
211
|
-
|
212
|
-
if exists python; then
|
213
|
-
do_python $1 $2 && return 0
|
214
|
-
fi
|
215
|
-
|
216
|
-
echo ">>>>>> wget, curl, fetch, perl or python not found on this instance."
|
217
|
-
return 16
|
218
|
-
}
|
219
|
-
HELPERS
|
142
|
+
IO.read(File.join(
|
143
|
+
File.dirname(__FILE__), %w[.. .. support download_helpers.sh]
|
144
|
+
))
|
220
145
|
end
|
221
146
|
end
|
222
147
|
end
|