kitchen-ssh 0.0.12 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +79 -75
- data/kitchen-ssh.gemspec +28 -28
- data/lib/kitchen/driver/ssh.rb +30 -30
- data/lib/kitchen/driver/ssh_base_gzip.rb +251 -251
- data/lib/kitchen/driver/ssh_gzip.rb +30 -30
- data/lib/kitchen/ssh/version.rb +5 -5
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04dc9f089e7879c4865320997a16719eae3f51e7
|
4
|
+
data.tar.gz: 9620d295f0898dc48d701c8fae2f6ee43eefd986
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b53fb093b1c144b1c77ee30df52b278f64eeb08ade2b1427a310a750073c19009b48d78c2868545044fca011cb645703b7e06f80b123d23dc6e55eff6a7c796f
|
7
|
+
data.tar.gz: b1af7c32a0580894a85236857d86f2794be2f99dbde0afa8dd0e59a9e6d748b9ab6edfbe98e15638a5338d361ba6d25a96a3af3a358df61b8a57a851e1b93331
|
data/README.md
CHANGED
@@ -1,75 +1,79 @@
|
|
1
|
-
# kitchen-ssh
|
2
|
-
|
3
|
-
ssh and ssh_gzip driver for test-kitchen for any running server with an ip address.
|
4
|
-
|
5
|
-
As well as ssh it supports a second driver called ssh_gzip that will also gzip file before transfer which can provide
|
6
|
-
a big performance improvement when alot of files are transfered.
|
7
|
-
|
8
|
-
server must be created and destroyed natively (e.g. via cloudformation, heat, or cloud or virtualization console).
|
9
|
-
specify driver parameters
|
10
|
-
* hostname
|
11
|
-
* port
|
12
|
-
* username
|
13
|
-
* password
|
14
|
-
* sudo
|
15
|
-
* ssh_key
|
16
|
-
* forward_agent
|
17
|
-
|
18
|
-
NOTE: ssh driver is compatibile with test-kitchen 1.4 while ssh_gzip has legacy driver compatiability
|
19
|
-
with test-kitchen 1.4
|
20
|
-
|
21
|
-
|
22
|
-
## Installation
|
23
|
-
|
24
|
-
Add this line to your application's Gemfile:
|
25
|
-
|
26
|
-
gem 'kitchen-ssh', group: :integration
|
27
|
-
|
28
|
-
And then execute:
|
29
|
-
|
30
|
-
$ bundle
|
31
|
-
|
32
|
-
Or install it yourself as:
|
33
|
-
|
34
|
-
$ gem install kitchen-ssh
|
35
|
-
|
36
|
-
## Usage
|
37
|
-
|
38
|
-
In your .kitchen.yml file set driver to be 'ssh' or 'ssh_gzip'.
|
39
|
-
|
40
|
-
##Example
|
41
|
-
|
42
|
-
```yaml
|
43
|
-
---
|
44
|
-
driver:
|
45
|
-
name: ssh
|
46
|
-
hostname: your-ip
|
47
|
-
port: 22
|
48
|
-
username: username
|
49
|
-
ssh_key: /path/to/id_rsa
|
50
|
-
```
|
51
|
-
|
52
|
-
or
|
53
|
-
|
54
|
-
```yaml
|
55
|
-
---
|
56
|
-
driver:
|
57
|
-
name: ssh_gzip
|
58
|
-
hostname: your-ip
|
59
|
-
port: 22
|
60
|
-
username: username
|
61
|
-
ssh_key: /path/to/id_rsa
|
62
|
-
```
|
63
|
-
|
64
|
-
### Bastion Host
|
65
|
-
|
66
|
-
If you use a bastion host, add the following lines:
|
67
|
-
|
68
|
-
```yaml
|
69
|
-
transport:
|
70
|
-
name: ssh
|
71
|
-
ssh_gateway: bastion-ip
|
72
|
-
ssh_gateway_username: bastion-user
|
73
|
-
```
|
74
|
-
|
75
|
-
Alternatively add `ProxyCommand ssh -W %h:%p bastion-user@bastion-ip` to your `ssh_config(5)`
|
1
|
+
# kitchen-ssh
|
2
|
+
|
3
|
+
ssh and ssh_gzip driver for test-kitchen for any running server with an ip address.
|
4
|
+
|
5
|
+
As well as ssh it supports a second driver called ssh_gzip that will also gzip file before transfer which can provide
|
6
|
+
a big performance improvement when alot of files are transfered.
|
7
|
+
|
8
|
+
server must be created and destroyed natively (e.g. via cloudformation, heat, or cloud or virtualization console).
|
9
|
+
specify driver parameters
|
10
|
+
* hostname
|
11
|
+
* port
|
12
|
+
* username
|
13
|
+
* password
|
14
|
+
* sudo
|
15
|
+
* ssh_key
|
16
|
+
* forward_agent
|
17
|
+
|
18
|
+
NOTE: ssh driver is compatibile with test-kitchen 1.4 while ssh_gzip has legacy driver compatiability
|
19
|
+
with test-kitchen 1.4
|
20
|
+
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
gem 'kitchen-ssh', group: :integration
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install kitchen-ssh
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
In your .kitchen.yml file set driver to be 'ssh' or 'ssh_gzip'.
|
39
|
+
|
40
|
+
##Example
|
41
|
+
|
42
|
+
```yaml
|
43
|
+
---
|
44
|
+
driver:
|
45
|
+
name: ssh
|
46
|
+
hostname: your-ip
|
47
|
+
port: 22
|
48
|
+
username: username
|
49
|
+
ssh_key: /path/to/id_rsa
|
50
|
+
```
|
51
|
+
|
52
|
+
or
|
53
|
+
|
54
|
+
```yaml
|
55
|
+
---
|
56
|
+
driver:
|
57
|
+
name: ssh_gzip
|
58
|
+
hostname: your-ip
|
59
|
+
port: 22
|
60
|
+
username: username
|
61
|
+
ssh_key: /path/to/id_rsa
|
62
|
+
```
|
63
|
+
|
64
|
+
### Bastion Host
|
65
|
+
|
66
|
+
If you use a bastion host, add the following lines:
|
67
|
+
|
68
|
+
```yaml
|
69
|
+
transport:
|
70
|
+
name: ssh
|
71
|
+
ssh_gateway: bastion-ip
|
72
|
+
ssh_gateway_username: bastion-user
|
73
|
+
```
|
74
|
+
|
75
|
+
Alternatively add `ProxyCommand ssh -W %h:%p bastion-user@bastion-ip` to your `ssh_config(5)`
|
76
|
+
|
77
|
+
## Tips
|
78
|
+
|
79
|
+
If you get a hang while running kitchen-ssh with a non-root user check that the user was not set to be NOPASSWORD in the sudoer file either. So it hang there waiting for input of the password prompted. After changing the user and key to be root in the .kitchen.yml file, everything worked.
|
data/kitchen-ssh.gemspec
CHANGED
@@ -1,28 +1,28 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
$:.unshift File.expand_path('../lib', __FILE__)
|
4
|
-
require 'kitchen/ssh/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = "kitchen-ssh"
|
8
|
-
s.license = 'Apache-2.0'
|
9
|
-
s.version = Kitchen::Ssh::VERSION
|
10
|
-
s.authors = ["Neill Turner"]
|
11
|
-
s.email = ["neillwturner@gmail.com"]
|
12
|
-
s.homepage = "https://github.com/neillturner/kitchen-ssh"
|
13
|
-
s.add_dependency('minitar', '~> 0.5')
|
14
|
-
s.summary = "ssh and ssh_gzip driver for test-kitchen for any running server with an ip address"
|
15
|
-
candidates = Dir.glob("{lib}/**/*") + ['README.md', 'LICENSE.txt', 'kitchen-ssh.gemspec', 'Gemfile']
|
16
|
-
s.files = candidates.sort
|
17
|
-
s.platform = Gem::Platform::RUBY
|
18
|
-
s.require_paths = ['lib']
|
19
|
-
s.rubyforge_project = '[none]'
|
20
|
-
s.description = <<-EOF
|
21
|
-
ssh and ssh_gzip driver for test-kitchen for any running server with an ip address
|
22
|
-
|
23
|
-
*** As well as ssh it supports a second driver called ssh_gzip that will also gzip file before transfer which can provide
|
24
|
-
a big performance improvement when alot of files are transfered. ****
|
25
|
-
|
26
|
-
EOF
|
27
|
-
|
28
|
-
end
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
require 'kitchen/ssh/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "kitchen-ssh"
|
8
|
+
s.license = 'Apache-2.0'
|
9
|
+
s.version = Kitchen::Ssh::VERSION
|
10
|
+
s.authors = ["Neill Turner"]
|
11
|
+
s.email = ["neillwturner@gmail.com"]
|
12
|
+
s.homepage = "https://github.com/neillturner/kitchen-ssh"
|
13
|
+
s.add_dependency('minitar', '~> 0.5')
|
14
|
+
s.summary = "ssh and ssh_gzip driver for test-kitchen for any running server with an ip address"
|
15
|
+
candidates = Dir.glob("{lib}/**/*") + ['README.md', 'LICENSE.txt', 'kitchen-ssh.gemspec', 'Gemfile']
|
16
|
+
s.files = candidates.sort
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
s.rubyforge_project = '[none]'
|
20
|
+
s.description = <<-EOF
|
21
|
+
ssh and ssh_gzip driver for test-kitchen for any running server with an ip address
|
22
|
+
|
23
|
+
*** As well as ssh it supports a second driver called ssh_gzip that will also gzip file before transfer which can provide
|
24
|
+
a big performance improvement when alot of files are transfered. ****
|
25
|
+
|
26
|
+
EOF
|
27
|
+
|
28
|
+
end
|
data/lib/kitchen/driver/ssh.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
|
-
require 'kitchen'
|
2
|
-
require 'kitchen/driver/ssh_base'
|
3
|
-
|
4
|
-
module Kitchen
|
5
|
-
module Driver
|
6
|
-
class Ssh < SSHBase
|
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-ssh 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-ssh 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-ssh 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.'
|
25
|
-
debug("ssh:destroy '#{state[:hostname]}'")
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
1
|
+
require 'kitchen'
|
2
|
+
require 'kitchen/driver/ssh_base'
|
3
|
+
|
4
|
+
module Kitchen
|
5
|
+
module Driver
|
6
|
+
class Ssh < SSHBase
|
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] if config[:forward_agent]?
|
12
|
+
state[:username] = config[:username]
|
13
|
+
state[:hostname] = config[:hostname]
|
14
|
+
state[:password] = config[:password] if config[:password]?
|
15
|
+
print "Kitchen-ssh 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-ssh 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-ssh 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.'
|
25
|
+
debug("ssh:destroy '#{state[:hostname]}'")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,251 +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 < Kitchen::Driver::SSHBase
|
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) - changed in kitchen >=1.4
|
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[:
|
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
|
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 < Kitchen::Driver::SSHBase
|
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) - changed in kitchen >=1.4
|
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[:verify_host_key] = 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[: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
|
@@ -1,30 +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.'
|
25
|
-
debug("ssh:destroy '#{state[:hostname]}'")
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
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.'
|
25
|
+
debug("ssh:destroy '#{state[:hostname]}'")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/kitchen/ssh/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
module Kitchen
|
2
|
-
module Ssh
|
3
|
-
VERSION = "
|
4
|
-
end
|
5
|
-
end
|
1
|
+
module Kitchen
|
2
|
+
module Ssh
|
3
|
+
VERSION = "1.0.1"
|
4
|
+
end
|
5
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kitchen-ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neill Turner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitar
|
@@ -64,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
64
|
version: '0'
|
65
65
|
requirements: []
|
66
66
|
rubyforge_project: "[none]"
|
67
|
-
rubygems_version: 2.
|
67
|
+
rubygems_version: 2.6.14.1
|
68
68
|
signing_key:
|
69
69
|
specification_version: 4
|
70
70
|
summary: ssh and ssh_gzip driver for test-kitchen for any running server with an ip
|