kitchen-ssh 0.0.5 → 0.0.6
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.
- data/README.md +5 -1
- data/kitchen-ssh.gemspec +1 -0
- data/lib/kitchen/driver/ssh_base_gzip.rb +251 -0
- data/lib/kitchen/driver/ssh_gzip.rb +30 -0
- data/lib/kitchen/ssh/version.rb +1 -1
- metadata +21 -3
data/README.md
CHANGED
@@ -12,6 +12,10 @@ specify driver parameters
|
|
12
12
|
* ssh_key
|
13
13
|
* forward_agent
|
14
14
|
|
15
|
+
There is also a second driver called ssh_gzip that will also gzip file before transfer which can provide
|
16
|
+
a big performance improvement when alot of files are transfered.
|
17
|
+
|
18
|
+
|
15
19
|
## Installation
|
16
20
|
|
17
21
|
Add this line to your application's Gemfile:
|
@@ -28,5 +32,5 @@ Or install it yourself as:
|
|
28
32
|
|
29
33
|
## Usage
|
30
34
|
|
31
|
-
In your .kitchen.yml file set driver to be 'ssh'
|
35
|
+
In your .kitchen.yml file set driver to be 'ssh' or 'ssh_gzip'.
|
32
36
|
|
data/kitchen-ssh.gemspec
CHANGED
@@ -9,6 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ["Neill Turner"]
|
10
10
|
s.email = ["neillwturner@gmail.com"]
|
11
11
|
s.homepage = "https://github.com/neillturner/kitchen-ssh"
|
12
|
+
s.add_dependency('minitar', '~> 0.5')
|
12
13
|
s.summary = "ssh driver for test-kitchen for any running server with an ip address"
|
13
14
|
candidates = Dir.glob("{lib}/**/*") + ['README.md', 'LICENSE.txt', 'kitchen-ssh.gemspec']
|
14
15
|
s.files = candidates.sort
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012, 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
|
+
require 'archive/tar/minitar'
|
20
|
+
|
21
|
+
module Kitchen
|
22
|
+
|
23
|
+
module Driver
|
24
|
+
|
25
|
+
# Base class for a driver that uses SSH to communication with an instance.
|
26
|
+
# A subclass must implement the following methods:
|
27
|
+
# * #create(state)
|
28
|
+
# * #destroy(state)
|
29
|
+
#
|
30
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
31
|
+
class SSHBaseGzip < Base
|
32
|
+
|
33
|
+
default_config :sudo, true
|
34
|
+
default_config :port, 22
|
35
|
+
default_config :sandbox_archive, 'testkitchen-sandbox.tar.gz'
|
36
|
+
|
37
|
+
# (see Base#create)
|
38
|
+
def create(state) # rubocop:disable Lint/UnusedMethodArgument
|
39
|
+
raise ClientError, "#{self.class}#create must be implemented"
|
40
|
+
end
|
41
|
+
|
42
|
+
# (see Base#converge)
|
43
|
+
def converge(state)
|
44
|
+
provisioner = instance.provisioner
|
45
|
+
provisioner.create_sandbox
|
46
|
+
|
47
|
+
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
|
48
|
+
run_remote(provisioner.install_command, conn)
|
49
|
+
run_remote(provisioner.init_command, conn)
|
50
|
+
do_sandbox_transfer provisioner, conn
|
51
|
+
run_remote(provisioner.prepare_command, conn)
|
52
|
+
run_remote(provisioner.run_command, conn)
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
provisioner && provisioner.cleanup_sandbox
|
56
|
+
end
|
57
|
+
|
58
|
+
# (see Base#setup)
|
59
|
+
def setup(state)
|
60
|
+
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
|
61
|
+
run_remote(busser.setup_cmd, conn)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# (see Base#verify)
|
66
|
+
def verify(state)
|
67
|
+
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
|
68
|
+
run_remote(busser.sync_cmd, conn)
|
69
|
+
run_remote(busser.run_cmd, conn)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# (see Base#destroy)
|
74
|
+
def destroy(state) # rubocop:disable Lint/UnusedMethodArgument
|
75
|
+
raise ClientError, "#{self.class}#destroy must be implemented"
|
76
|
+
end
|
77
|
+
|
78
|
+
# (see Base#login_command)
|
79
|
+
def login_command(state)
|
80
|
+
SSH.new(*build_ssh_args(state)).login_command
|
81
|
+
end
|
82
|
+
|
83
|
+
# Executes an arbitrary command on an instance over an SSH connection.
|
84
|
+
#
|
85
|
+
# @param state [Hash] mutable instance and driver state
|
86
|
+
# @param command [String] the command to be executed
|
87
|
+
# @raise [ActionFailed] if the command could not be successfully completed
|
88
|
+
def remote_command(state, command)
|
89
|
+
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
|
90
|
+
run_remote(command, conn)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# **(Deprecated)** Executes a remote command over SSH.
|
95
|
+
#
|
96
|
+
# @param ssh_args [Array] ssh arguments
|
97
|
+
# @param command [String] remote command to invoke
|
98
|
+
# @deprecated This method should no longer be called directly and exists
|
99
|
+
# to support very old drivers. This will be removed in the future.
|
100
|
+
def ssh(ssh_args, command)
|
101
|
+
Kitchen::SSH.new(*ssh_args) do |conn|
|
102
|
+
run_remote(command, conn)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Builds arguments for constructing a `Kitchen::SSH` instance.
|
109
|
+
#
|
110
|
+
# @param state [Hash] state hash
|
111
|
+
# @return [Array] SSH constructor arguments
|
112
|
+
# @api private
|
113
|
+
def build_ssh_args(state)
|
114
|
+
combined = config.to_hash.merge(state)
|
115
|
+
|
116
|
+
opts = Hash.new
|
117
|
+
opts[:user_known_hosts_file] = "/dev/null"
|
118
|
+
opts[:paranoid] = false
|
119
|
+
opts[:keys_only] = true if combined[:ssh_key]
|
120
|
+
opts[:password] = combined[:password] if combined[:password]
|
121
|
+
opts[:forward_agent] = combined[:forward_agent] if combined.key? :forward_agent
|
122
|
+
opts[:port] = combined[:port] if combined[:port]
|
123
|
+
opts[:keys] = Array(combined[:ssh_key]) if combined[:ssh_key]
|
124
|
+
opts[:logger] = logger
|
125
|
+
|
126
|
+
[combined[:hostname], combined[:username], opts]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Adds http and https proxy environment variables to a command, if set
|
130
|
+
# in configuration data.
|
131
|
+
#
|
132
|
+
# @param cmd [String] command string
|
133
|
+
# @return [String] command string
|
134
|
+
# @api private
|
135
|
+
def env_cmd(cmd)
|
136
|
+
env = "env"
|
137
|
+
env << " http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
|
138
|
+
env << " https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
|
139
|
+
|
140
|
+
env == "env" ? cmd : "#{env} #{cmd}"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Executes a remote command over SSH.
|
144
|
+
#
|
145
|
+
# @param command [String] remove command to run
|
146
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
147
|
+
# @raise [ActionFailed] if an exception occurs
|
148
|
+
# @api private
|
149
|
+
def run_remote(command, connection)
|
150
|
+
return if command.nil?
|
151
|
+
|
152
|
+
connection.exec(env_cmd(command))
|
153
|
+
rescue SSHFailed, Net::SSH::Exception => ex
|
154
|
+
raise ActionFailed, ex.message
|
155
|
+
end
|
156
|
+
|
157
|
+
# Transfers one or more local paths over SSH.
|
158
|
+
#
|
159
|
+
# @param locals [Array<String>] array of local paths
|
160
|
+
# @param remote [String] remote destination path
|
161
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
162
|
+
# @raise [ActionFailed] if an exception occurs
|
163
|
+
# @api private
|
164
|
+
def transfer_path(locals, remote, connection)
|
165
|
+
return if locals.nil? || Array(locals).empty?
|
166
|
+
|
167
|
+
info("Transferring files to #{instance.to_str}")
|
168
|
+
locals.each { |local| connection.upload_path!(local, remote) }
|
169
|
+
debug("Transfer complete")
|
170
|
+
rescue SSHFailed, Net::SSH::Exception => ex
|
171
|
+
raise ActionFailed, ex.message
|
172
|
+
end
|
173
|
+
|
174
|
+
# Blocks until a TCP socket is available where a remote SSH server
|
175
|
+
# should be listening.
|
176
|
+
#
|
177
|
+
# @param hostname [String] remote SSH server host
|
178
|
+
# @param username [String] SSH username (default: `nil`)
|
179
|
+
# @param options [Hash] configuration hash (default: `{}`)
|
180
|
+
# @api private
|
181
|
+
def wait_for_sshd(hostname, username = nil, options = {})
|
182
|
+
SSH.new(hostname, username, { :logger => logger }.merge(options)).wait
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# Creates a temporary folder containing an archive of the current
|
187
|
+
# TestKitchen sandbox.
|
188
|
+
#
|
189
|
+
# @param sandbox_path [String]
|
190
|
+
def archive_sandbox(sandbox_path)
|
191
|
+
archive_dir = Dir.mktmpdir("#{instance.name}-sandbox-archive-")
|
192
|
+
archive_file = "#{archive_dir}/#{self[:sandbox_archive]}"
|
193
|
+
|
194
|
+
Dir.chdir(sandbox_path) do |dir|
|
195
|
+
tgz = Zlib::GzipWriter.new(File.open(archive_file, 'wb'), Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY)
|
196
|
+
Archive::Tar::Minitar.pack('.', tgz)
|
197
|
+
end
|
198
|
+
|
199
|
+
archive_dir
|
200
|
+
end
|
201
|
+
|
202
|
+
# Transfers the local sandbox to the instance.
|
203
|
+
# - Archives/extracts if the tar command is available remotely.
|
204
|
+
#
|
205
|
+
# @param provisioner [Kitchen::Provisioner::Base] the provisioner
|
206
|
+
# @param connection [Kitchen:SSH] an SSH connection
|
207
|
+
def do_sandbox_transfer(provisioner, connection)
|
208
|
+
root_path = provisioner[:root_path]
|
209
|
+
sandbox_path = provisioner.sandbox_path
|
210
|
+
archive_file = self[:sandbox_archive]
|
211
|
+
archive_path = false
|
212
|
+
do_archive = remote_supports_tar? connection
|
213
|
+
|
214
|
+
begin
|
215
|
+
# Archive sandbox if enabled (We keep a copy of the archive path so that we do not)
|
216
|
+
# delete the sandbox if an exception is thrown
|
217
|
+
if do_archive
|
218
|
+
info 'Creating sandbox archive'
|
219
|
+
archive_path = archive_sandbox sandbox_path
|
220
|
+
sandbox_path = archive_path
|
221
|
+
end
|
222
|
+
|
223
|
+
# Initiate transfer
|
224
|
+
transfer_path(Dir.glob("#{sandbox_path}/*"), root_path, connection)
|
225
|
+
|
226
|
+
# Extract archive if enabled (and cleanup locally)
|
227
|
+
if do_archive
|
228
|
+
info 'Extracting sandbox archive remotely'
|
229
|
+
run_remote("tar xf #{root_path}/#{archive_file} -C #{root_path}", connection)
|
230
|
+
end
|
231
|
+
ensure
|
232
|
+
# Ensure archive temporary directory is removed, if used.
|
233
|
+
FileUtils.rmtree(archive_path) if archive_path
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Checks whether the remote instance supports archive extraction using
|
238
|
+
# the `tar` command.
|
239
|
+
#
|
240
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
241
|
+
def remote_supports_tar?(connection)
|
242
|
+
begin
|
243
|
+
run_remote('tar --version > /dev/null 2>&1', connection)
|
244
|
+
return true
|
245
|
+
rescue ActionFailed => ex
|
246
|
+
return false
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'kitchen'
|
2
|
+
require 'kitchen/driver/ssh_base_gzip'
|
3
|
+
|
4
|
+
module Kitchen
|
5
|
+
module Driver
|
6
|
+
class SshGzip < SSHBaseGzip
|
7
|
+
def create(state)
|
8
|
+
state[:sudo] = config[:sudo]
|
9
|
+
state[:port] = config[:port]
|
10
|
+
state[:ssh_key] = config[:ssh_key]
|
11
|
+
state[:forward_agent] = config[:forward_agent]
|
12
|
+
state[:username] = config[:username]
|
13
|
+
state[:hostname] = config[:hostname]
|
14
|
+
state[:password] = config[:password]
|
15
|
+
print "Kitchen-sshGzip does not start your server '#{state[:hostname]}' but will look for an ssh connection with user '#{state[:username]}'"
|
16
|
+
wait_for_sshd(state[:hostname], state[:username], {:port => state[:port]})
|
17
|
+
print "Kitchen-sshGzip found ssh ready on host '#{state[:hostname]}' with user '#{state[:username]}'\n"
|
18
|
+
debug("ssh:create '#{state[:hostname]}'")
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy(state)
|
22
|
+
print "Kitchen-sshGzip does not destroy your server '#{state[:hostname]}' by shutting it down..."
|
23
|
+
print "Shutdown your server '#{state[:hostname]}' natively with user '#{state[:username]}'"
|
24
|
+
print 'in your cloud or virtualisation console etc.\n'
|
25
|
+
debug("ssh:destroy '#{state[:hostname]}'")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/kitchen/ssh/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kitchen-ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
12
|
+
date: 2015-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitar
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.5'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.5'
|
14
30
|
description: ! '== DESCRIPTION:
|
15
31
|
|
16
32
|
|
@@ -34,6 +50,8 @@ files:
|
|
34
50
|
- README.md
|
35
51
|
- kitchen-ssh.gemspec
|
36
52
|
- lib/kitchen/driver/ssh.rb
|
53
|
+
- lib/kitchen/driver/ssh_base_gzip.rb
|
54
|
+
- lib/kitchen/driver/ssh_gzip.rb
|
37
55
|
- lib/kitchen/ssh/version.rb
|
38
56
|
homepage: https://github.com/neillturner/kitchen-ssh
|
39
57
|
licenses: []
|