sshkit 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6cd81576d8df6878627ee2ca1b298c2079698085
4
- data.tar.gz: 9676306af0f0de95b974134d580fed271486004f
3
+ metadata.gz: a17c8de5efa195cef64bac5bd868dce70e430939
4
+ data.tar.gz: 90f2200bf01fa80d3549ed747a4e8ef545806ffe
5
5
  SHA512:
6
- metadata.gz: 4a0cfdd77807a8e280376f8f5a07b0f7bac96339091c67bb2c3e7e2424247e192436451ca189772352095f9132afc57e7c660381180812ca315c2ad0715e58f4
7
- data.tar.gz: 0e4037910686419d816927cddae4a065021c8e7ebae2530c55d62ea299414310e1bb9fcb44fd88dabde85de069667e537aec5a4ff2dc5f9dff55cf9bc0bc592f
6
+ metadata.gz: ce8989e38ff9dab3be7673183786c838ea9fa71d174dfec0c5ae0d812a47619fcc1a2b768939cb531b9cf8dbabd4993b3f46c60bee81d01c7aff9976d5b5d77f
7
+ data.tar.gz: 906b9b7317960551ed9b137f9cf1611e11821d3a042cc0d990853cedfbc5e970c1b4a4d1a92a0b4896b662e162395fc3244b91db8f29ba92a52a596c9145cc06
data/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@ appear at the top.
8
8
  * Add your entries here, remember to credit yourself however you want to be
9
9
  credited!
10
10
 
11
+ ## 1.4.0
12
+
13
+ * Removed `invoke` alias for [`SSHKit::Backend::Printer.execute`](https://github.com/capistrano/sshkit/blob/master/lib/sshkit/backends/printer.rb#L20). This is to prevent collisions with
14
+ methods in capistrano with similar names, and to provide a cleaner API. See [capistrano issue 912](https://github.com/capistrano/capistrano/issues/912) and [issue 107](https://github.com/capistrano/sshkit/issues/107) for more details.
15
+ * Connection pooling now uses a thread local to store connection pool, giving each thread its own connection pool. Thank you @mcbriston see [#101](https://github.com/capistrano/sshkit/pull/101) for more.
16
+ * Command map indifferent towards strings and symbols thanks to @thomasfedb see [#91](https://github.com/capistrano/sshkit/pull/91)
17
+ * Moved vagrant wrapper to `support` directory, added ability to run tests with vagrant using ssh. @miry see [#64](https://github.com/capistrano/sshkit/pull/64)
18
+ * Removed unnecessary require `require_relative '../sshkit'` in `lib/sshkit/dsl.rb` prevents warnings thanks @brabic.
19
+ * Doc fixes thanks @seanhandley @vojto
20
+
11
21
  ## 1.3.0
12
22
 
13
23
  * Connection pooling. SSH connections are reused across multiple invocations
data/EXAMPLES.md CHANGED
@@ -64,21 +64,21 @@ This will output:
64
64
  **Note:** This example is a bit misleading, as the `www-data` user doesn't
65
65
  have a shell defined, one cannot switch to that user.
66
66
 
67
- ## Upload a file from disk
67
+ ## Upload a file from disk
68
68
 
69
69
  on hosts do |host|
70
- upload! '/config/database.yml', '/opt/my_project/shared/databse.yml'
70
+ upload! '/config/database.yml', '/opt/my_project/shared/database.yml'
71
71
  end
72
72
 
73
73
  **Note:** The `upload!()` method doesn't honor the values of `within()`, `as()`
74
74
  etc, this will be improved as the library matures, but we're not there yet.
75
75
 
76
- ## Upload a file from a stream
76
+ ## Upload a file from a stream
77
77
 
78
78
  on hosts do |host|
79
79
  file = File.open('/config/database.yml')
80
80
  io = StringIO.new(....)
81
- upload! file, '/opt/my_project/shared/databse.yml'
81
+ upload! file, '/opt/my_project/shared/database.yml'
82
82
  upload! io, '/opt/my_project/shared/io.io.io'
83
83
  end
84
84
 
@@ -110,7 +110,7 @@ available to `Net::{SCP,SFTP}`.
110
110
  Setting global SSH options, these will be overwritten by options set on the
111
111
  individual hosts:
112
112
 
113
- Netssh.configure do |ssh|
113
+ SSHKit::Backend::Netssh.configure do |ssh|
114
114
  ssh.connection_timeout = 30
115
115
  ssh.ssh_options = {
116
116
  keys: %w(/home/user/.ssh/id_rsa),
@@ -119,7 +119,7 @@ individual hosts:
119
119
  }
120
120
  end
121
121
 
122
- ## Run a command with a different effective group ID
122
+ ## Run a command with a different effective group ID
123
123
 
124
124
  on hosts do |host|
125
125
  as user: 'www-data', group: 'project-group' do
@@ -233,7 +233,7 @@ which will cause the command to abort.
233
233
  end
234
234
 
235
235
  The `test()` command behaves exactly the same as execute however will return
236
- false if the command exits with a non-zero exit (as `man 1 test` does). As ti
236
+ false if the command exits with a non-zero exit (as `man 1 test` does). As it
237
237
  returns boolean it can be used to direct the control flow within the block.
238
238
 
239
239
  ## Do something different on one host, or another depending on a host property
@@ -294,7 +294,7 @@ An extension of the behaviour above, if you write a command like this:
294
294
  known test cases, it works. The key thing is that `if` is not mapped to
295
295
  `/usr/bin/env if`, which would break with a syntax error.
296
296
 
297
- ## Using with Rake
297
+ ## Using with Rake
298
298
 
299
299
  Into the `Rakefile` simply put something like:
300
300
 
data/FAQ.md CHANGED
@@ -11,11 +11,6 @@ Since *Capistrano v3.0*, *SSHKit* is used by *Capistrano* to communicate with
11
11
  backend servers. Whilst Capistrano provides the structure for repeatable
12
12
  deployments.
13
13
 
14
- ## Production Ready?
15
-
16
- It's in private Beta use, and the documentation could use more work, but this
17
- is open source, that's more or less how it works.
18
-
19
14
  ## Why does <something> stop responding after I started it with `background()`?
20
15
 
21
16
  The answer is complicated, but it can be summed up by saying that under
data/Gemfile CHANGED
@@ -4,5 +4,5 @@ gemspec
4
4
 
5
5
  platforms :rbx do
6
6
  gem 'rubysl', '~> 2.0'
7
- gem 'rubysl-json'
7
+ gem 'json'
8
8
  end
data/README.md CHANGED
@@ -3,25 +3,28 @@
3
3
  **SSHKit** is a toolkit for running commands in a structured way on one or
4
4
  more servers.
5
5
 
6
- [![Build Status](https://travis-ci.org/leehambley/sshkit.png?branch=master)](https://travis-ci.org/leehambley/sshkit)
6
+ [![Build Status](https://travis-ci.org/capistrano/sshkit.png?branch=master)](https://travis-ci.org/capistrano/sshkit)
7
7
  [![Dependency Status](https://gemnasium.com/leehambley/sshkit.png)](https://gemnasium.com/leehambley/sshkit)
8
8
 
9
9
  ## How might it work?
10
10
 
11
11
  The typical use-case looks something like this:
12
12
 
13
- require 'sshkit/dsl'
14
-
15
- on %w{1.example.com 2.example.com}, in: :sequence, wait: 5 do
16
- within "/opt/sites/example.com" do
17
- as :deploy do
18
- with rails_env: :production do
19
- rake "assets:precompile"
20
- runner "S3::Sync.notify"
21
- end
22
- end
13
+ ```ruby
14
+ require 'sshkit/dsl'
15
+
16
+ on %w{1.example.com 2.example.com}, in: :sequence, wait: 5 do
17
+ within "/opt/sites/example.com" do
18
+ as :deploy do
19
+ with rails_env: :production do
20
+ rake "assets:precompile"
21
+ runner "S3::Sync.notify"
22
+ execute "node", "socket_server.js"
23
23
  end
24
24
  end
25
+ end
26
+ end
27
+ ```
25
28
 
26
29
  One will notice that it's quite low level, but exposes a convenient API, the
27
30
  `as()`/`within()`/`with()` are nestable in any order, repeatable, and stackable.
@@ -55,9 +58,11 @@ Helpers such as `runner()` and `rake()` which expand to `execute(:rails, "runner
55
58
  Notice on the `on()` call the `in: :sequence` option, the following will do
56
59
  what you might expect:
57
60
 
58
- on(in: :parallel) { ... }
59
- on(in: :sequence, wait: 5) { ... }
60
- on(in: :groups, limit: 2, wait: 5) { ... }
61
+ ```ruby
62
+ on(in: :parallel) { ... }
63
+ on(in: :sequence, wait: 5) { ... }
64
+ on(in: :groups, limit: 2, wait: 5) { ... }
65
+ ```
61
66
 
62
67
  The default is to run `in: :parallel` which has no limit. If you have 400 servers,
63
68
  this might be a problem and you might better look at changing that to run in
@@ -77,49 +82,68 @@ for all servers to complete before it returns.
77
82
 
78
83
  For example:
79
84
 
80
- all_servers = %w{one.example.com two.example.com three.example.com}
81
- site_dir = '/opt/sites/example.com'
82
-
83
- # Let's simulate a backup task, assuming that some servers take longer
84
- # then others to complete
85
- on servers do |host|
86
- in site_dir do
87
- execute :tar, '-czf', "backup-#{host.hostname}.tar.gz", 'current'
88
- # Will run: "/usr/bin/env tar -czf backup-one.example.com.tar.gz current"
89
- end
90
- end
91
-
92
- # Now we can do something with those backups, safe in the knowledge that
93
- # they will all exist (all tar commands exited with a success status, or
94
- # that we will have raised an exception if one of them failed.
95
- on servers do |host|
96
- in site_dir do
97
- backup_filename = "backup-#{host.hostname}.tar.gz"
98
- target_filename = "backups/#{Time.now.utc.iso8601}/#{host.hostname}.tar.gz"
99
- puts capture(:s3cmd, 'put', backup_filename, target_filename)
100
- end
101
- end
85
+ ```ruby
86
+ all_servers = %w{one.example.com two.example.com three.example.com}
87
+ site_dir = '/opt/sites/example.com'
88
+
89
+ # Let's simulate a backup task, assuming that some servers take longer
90
+ # then others to complete
91
+ on all_servers do |host|
92
+ in site_dir do
93
+ execute :tar, '-czf', "backup-#{host.hostname}.tar.gz", 'current'
94
+ # Will run: "/usr/bin/env tar -czf backup-one.example.com.tar.gz current"
95
+ end
96
+ end
97
+
98
+ # Now we can do something with those backups, safe in the knowledge that
99
+ # they will all exist (all tar commands exited with a success status, or
100
+ # that we will have raised an exception if one of them failed.
101
+ on all_servers do |host|
102
+ in site_dir do
103
+ backup_filename = "backup-#{host.hostname}.tar.gz"
104
+ target_filename = "backups/#{Time.now.utc.iso8601}/#{host.hostname}.tar.gz"
105
+ puts capture(:s3cmd, 'put', backup_filename, target_filename)
106
+ end
107
+ end
108
+ ```
102
109
 
103
110
  ## The Command Map
104
111
 
105
- It's often a problem that programatic SSH sessions don't share the same environmental
106
- variables as sessions that are started interactively.
112
+ It's often a problem that programmatic SSH sessions don't have the same environment
113
+ variables as interactive sessions.
107
114
 
108
- This problem often comes when calling out to executables, expected to be on
109
- the `$PATH` which, under conditions without dotfiles or other environmental
110
- configuration are not where they are expected to be.
115
+ A problem often arises when calling out to executables expected to be on
116
+ the `$PATH`. Under conditions without dotfiles or other environmental
117
+ configuration, `$PATH` may not be set as expected, and thus executables are not found where expected.
111
118
 
112
119
  To try and solve this there is the `with()` helper which takes a hash of variables and makes them
113
120
  available to the environment.
114
121
 
115
- with path: '/usr/local/bin/rbenv/shims:$PATH' do
116
- execute :ruby, '--version'
117
- end
122
+ ```ruby
123
+ with path: '/usr/local/bin/rbenv/shims:$PATH' do
124
+ execute :ruby, '--version'
125
+ end
126
+ ```
118
127
 
119
128
  Will execute:
120
129
 
121
130
  ( PATH=/usr/local/bin/rbenv/shims:$PATH /usr/bin/env ruby --version )
122
131
 
132
+ By contrast, the following won't modify the command at all:
133
+
134
+
135
+ ```ruby
136
+ with path: '/usr/local/bin/rbenv/shims:$PATH' do
137
+ execute 'ruby --version'
138
+ end
139
+ ```
140
+
141
+ Will execute, without mapping the environmental variables, or querying the command map:
142
+
143
+ ruby --version
144
+
145
+ (This behaviour is sometimes considered confusing, but it has mostly to do with shell escaping: in the case of whitespace in your command, or newlines, we have no way of reliably composing a correct shell command from the input given.)
146
+
123
147
  **Often more preferable is to use the *command map*.**
124
148
 
125
149
  The *command map* is used by default when instantiating a *Command* object
@@ -128,36 +152,43 @@ The *command map* exists on the configuration object, and in principle is
128
152
  quite simple, it's a *Hash* structure with a default key factory block
129
153
  specified, for example:
130
154
 
131
- puts SSHKit.config.command_map[:ruby]
132
- # => /usr/bin/env ruby
155
+ ```ruby
156
+ puts SSHKit.config.command_map[:ruby]
157
+ # => /usr/bin/env ruby
158
+ ```
133
159
 
134
- The `/usr/bin/env` prefix is applied to all commands, to make clear that the
135
- environment is being deferred to to make the decision, this is what happens
136
- anyway when one would simply attempt to execute `ruby`, however by making it
137
- explicit, it was hoped that it might lead people to explore the documentation.
160
+ To make clear the environment is being deferred to, the `/usr/bin/env` prefix is applied to all commands.
161
+ Although this is what happens anyway when one would simply attempt to execute `ruby`, making it
162
+ explicit hopefully leads people to explore the documentation.
138
163
 
139
164
  One can override the hash map for individual commands:
140
165
 
141
- SSHKit.config.command_map[:rake] = "/usr/local/rbenv/shims/rake"
142
- puts SSHKit.config.command_map[:rake]
143
- # => /usr/local/rbenv/shims/rake
166
+ ```ruby
167
+ SSHKit.config.command_map[:rake] = "/usr/local/rbenv/shims/rake"
168
+ puts SSHKit.config.command_map[:rake]
169
+ # => /usr/local/rbenv/shims/rake
170
+ ```
144
171
 
145
- Another oportunity is to add command prefixes:
172
+ Another opportunity is to add command prefixes:
146
173
 
147
- SSHKit.config.command_map.prefix[:rake].push("bundle exec")
148
- puts SSHKit.config.command_map[:rake]
149
- # => bundle exec rake
174
+ ```ruby
175
+ SSHKit.config.command_map.prefix[:rake].push("bundle exec")
176
+ puts SSHKit.config.command_map[:rake]
177
+ # => bundle exec rake
150
178
 
151
- SSHKit.config.command_map.prefix[:rake].unshift("/usr/local/rbenv/bin exec")
152
- puts SSHKit.config.command_map[:rake]
153
- # => /usr/local/rbenv/bin exec bundle exec rake
179
+ SSHKit.config.command_map.prefix[:rake].unshift("/usr/local/rbenv/bin exec")
180
+ puts SSHKit.config.command_map[:rake]
181
+ # => /usr/local/rbenv/bin exec bundle exec rake
182
+ ```
154
183
 
155
184
  One can also override the command map completely, this may not be wise, but it
156
185
  would be possible, for example:
157
186
 
158
- SSHKit.config.command_map = Hash.new do |hash, command|
159
- hash[command] = "/usr/local/rbenv/shims/#{command}"
160
- end
187
+ ```ruby
188
+ SSHKit.config.command_map = Hash.new do |hash, command|
189
+ hash[command] = "/usr/local/rbenv/shims/#{command}"
190
+ end
191
+ ```
161
192
 
162
193
  This would effectively make it impossible to call any commands which didn't
163
194
  provide an executable in that directory, but in some cases that might be
@@ -170,26 +201,27 @@ first argument before attempting to find it in the *command map*.
170
201
 
171
202
  ![Example Output](https://raw.github.com/leehambley/sshkit/master/assets/images/example_output.png)
172
203
 
173
- The output handling comprises two objects, first is the output itself, by
174
- default this is *$stdout*, but can be any object responding to a
175
- *StringIO*-like interface. The second part is the *formatter*.
204
+ By default, the output format is set to `:pretty`:
176
205
 
177
- The *formatter* and *output* have a strange relationship:
206
+ ```ruby
207
+ SSHKit.config.format = :pretty
208
+ ```
209
+
210
+ However, if you prefer minimal output, `:dot` format will simply output red or green dots based on the success or failure of operations.
178
211
 
179
- SSHKit.config.output = SSHKit.config.formatter.new($stdout)
212
+ To output directly to $stdout without any formatting, you can use:
180
213
 
181
- The *formatter* will typically delegate all calls to the *output*, depending
182
- on it's implementation it will almost certainly override the implementation of
183
- `write()` (alias `<<()`) and query the objects it receives to determine what
184
- should be printed.
214
+ ```ruby
215
+ SSHKit.config.output = $stdout
216
+ ```
185
217
 
186
- ## Verbosity
218
+ ## Output Verbosity
187
219
 
188
220
  By default calls to `capture()` and `test()` are not logged, they are used
189
221
  *so* frequently by backend tasks to check environmental settings that it
190
222
  produces a large amount of noise. They are tagged with a verbosity option on
191
223
  the `Command` instances of `Logger::DEBUG`. The default configuration for
192
- output verbosity is avaialble to override with `SSHKit.config.output_verbosity=`,
224
+ output verbosity is available to override with `SSHKit.config.output_verbosity=`,
193
225
  and defaults to `Logger::INFO`.
194
226
 
195
227
  At present the `Logger::WARN`, `ERROR` and `FATAL` are not used.
@@ -211,7 +243,7 @@ SSHKit::Backend::Netssh.pool.idle_timeout = 60 # seconds
211
243
  ```
212
244
 
213
245
  If you suspect the connection pooling is causing problems, you can disable the
214
- pooling behavior entirely by setting the idle_timeout to zero:
246
+ pooling behaviour entirely by setting the idle_timeout to zero:
215
247
 
216
248
  ```ruby
217
249
  SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
@@ -247,8 +279,8 @@ SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
247
279
  * No method for uploading or downloading files, or the same for saving/loading
248
280
  a string to/from a remote file.
249
281
  * No closing of connections, the abstract backend class should include a
250
- cleanup method which is empty but can be overriden by other implementations.
251
- * ~~No conncetion pooling, the `connection` method of the NetSSH backend could
282
+ cleanup method which is empty but can be overridden by other implementations.
283
+ * ~~No connection pooling, the `connection` method of the NetSSH backend could
252
284
  easily be modified to look into some connection factory for it's objects,
253
285
  saving half a second when running lots of `on()` blocks.~~
254
286
  * Documentation! (YARD style)
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+
3
+ require 'bundler/gem_tasks'
3
4
  require 'rake/testtask'
4
5
 
5
6
  namespace :test do
data/lib/sshkit.rb CHANGED
@@ -3,28 +3,31 @@ module SSHKit
3
3
  StandardError = Class.new(::StandardError)
4
4
 
5
5
  class << self
6
+
6
7
  attr_accessor :config
7
- end
8
-
9
- def self.capture_output(io, &block)
10
- original_io = config.output
11
- config.output = io
12
- yield
13
- ensure
14
- config.output = original_io
15
- end
16
-
17
- def self.configure
18
- @@config ||= Configuration.new
19
- yield config
20
- end
21
-
22
- def self.config
23
- @@config ||= Configuration.new
24
- end
25
8
 
26
- def self.reset_configuration!
27
- @@config = nil
9
+ def capture_output(io, &block)
10
+ original_io = config.output
11
+ config.output = io
12
+ config.output.extend(SSHKit::Utils::CaptureOutputMethods)
13
+ yield
14
+ ensure
15
+ config.output = original_io
16
+ end
17
+
18
+ def configure
19
+ @@config ||= Configuration.new
20
+ yield config
21
+ end
22
+
23
+ def config
24
+ @@config ||= Configuration.new
25
+ end
26
+
27
+ def reset_configuration!
28
+ @@config = nil
29
+ end
30
+
28
31
  end
29
32
 
30
33
  end
data/lib/sshkit/all.rb CHANGED
@@ -13,6 +13,7 @@ require_relative 'log_message'
13
13
 
14
14
  require_relative 'formatters/abstract'
15
15
  require_relative 'formatters/black_hole'
16
+ require_relative 'formatters/simple_text'
16
17
  require_relative 'formatters/pretty'
17
18
  require_relative 'formatters/dot'
18
19
 
@@ -28,3 +29,5 @@ require_relative 'backends/printer'
28
29
  require_relative 'backends/netssh'
29
30
  require_relative 'backends/local'
30
31
  require_relative 'backends/skipper'
32
+
33
+ require_relative 'utils/capture_output_methods'
@@ -10,7 +10,6 @@ module SSHKit
10
10
 
11
11
  def initialize
12
12
  self.idle_timeout = 30
13
- @connections = {}
14
13
  @monitor = Monitor.new
15
14
  end
16
15
 
@@ -31,25 +30,21 @@ module SSHKit
31
30
 
32
31
  private
33
32
 
33
+ def connections
34
+ Thread.current[:sshkit_pool] ||= {}
35
+ end
36
+
34
37
  def find_and_reject_invalid(key, &block)
35
- synchronize do
36
- entry = @connections[key]
37
- invalid = entry && yield(entry)
38
+ entry = connections[key]
39
+ invalid = entry && yield(entry)
38
40
 
39
- @connections.delete(entry) if invalid
41
+ connections.delete(entry) if invalid
40
42
 
41
- invalid ? nil : entry
42
- end
43
+ invalid ? nil : entry
43
44
  end
44
45
 
45
46
  def store_entry(key, connection)
46
- synchronize do
47
- @connections[key] = Entry.new(connection)
48
- end
49
- end
50
-
51
- def synchronize(&block)
52
- @monitor.synchronize(&block)
47
+ connections[key] = Entry.new(connection)
53
48
  end
54
49
 
55
50
 
@@ -26,7 +26,7 @@ module SSHKit
26
26
  end
27
27
 
28
28
  def capture(*args)
29
- options = args.extract_options!.merge(verbosity: Logger::DEBUG)
29
+ options = { verbosity: Logger::DEBUG }.merge(args.extract_options!)
30
30
  _execute(*[*args, options]).full_stdout.strip
31
31
  end
32
32
 
@@ -38,7 +38,13 @@ module SSHKit
38
38
 
39
39
  cmd.started = Time.now
40
40
 
41
- stdout, stderr, exit_status = Open3.capture3(cmd.to_command)
41
+ stdout, stderr, exit_status =
42
+ if RUBY_ENGINE == 'jruby'
43
+ _, o, e, t = Open3.popen3('/usr/bin/env', 'sh', '-c', cmd.to_command)
44
+ [o.read, e.read, t.value]
45
+ else
46
+ Open3.capture3(cmd.to_command)
47
+ end
42
48
 
43
49
  cmd.stdout = stdout
44
50
  cmd.full_stdout += stdout
@@ -72,7 +72,7 @@ module SSHKit
72
72
  end
73
73
 
74
74
  def capture(*args)
75
- options = args.extract_options!.merge(verbosity: Logger::DEBUG)
75
+ options = { verbosity: Logger::DEBUG }.merge(args.extract_options!)
76
76
  _execute(*[*args, options]).full_stdout.strip
77
77
  end
78
78
 
@@ -11,13 +11,12 @@ module SSHKit
11
11
 
12
12
  def execute(*args)
13
13
  command(*args).tap do |cmd|
14
- output << sprintf("%s\n", cmd)
14
+ output << cmd
15
15
  end
16
16
  end
17
17
  alias :upload! :execute
18
18
  alias :download! :execute
19
19
  alias :test :execute
20
- alias :invoke :execute
21
20
 
22
21
  def capture(*args)
23
22
  String.new.tap { execute(*args) }
@@ -87,10 +87,14 @@ module SSHKit
87
87
  def exit_status=(new_exit_status)
88
88
  @finished_at = Time.now
89
89
  @exit_status = new_exit_status
90
+
90
91
  if options[:raise_on_non_zero_exit] && exit_status > 0
91
92
  message = ""
93
+ message += "#{command} exit status: " + exit_status.to_s + "\n"
92
94
  message += "#{command} stdout: " + (stdout.strip.empty? ? "Nothing written" : stdout.strip) + "\n"
93
- message += "#{command} stderr: " + (stderr.strip.empty? ? "Nothing written" : stderr.strip) + "\n"
95
+
96
+ stderr_message = [stderr.strip, full_stderr.strip].delete_if(&:empty?).first
97
+ message += "#{command} stderr: " + (stderr_message || 'Nothing written') + "\n"
94
98
  raise Failed, message
95
99
  end
96
100
  end
@@ -147,7 +151,7 @@ module SSHKit
147
151
  (SSHKit.config.default_env || {}).merge(options[:env] || {})
148
152
  end
149
153
 
150
- def envivonment_string
154
+ def environment_string
151
155
  environment_hash.collect do |key,value|
152
156
  "#{key.to_s.upcase}=#{value}"
153
157
  end.join(' ')
@@ -155,7 +159,7 @@ module SSHKit
155
159
 
156
160
  def with(&block)
157
161
  return yield unless environment_hash.any?
158
- "( #{envivonment_string} %s )" % yield
162
+ "( #{environment_string} %s )" % yield
159
163
  end
160
164
 
161
165
  def user(&block)
@@ -165,7 +169,7 @@ module SSHKit
165
169
 
166
170
  def in_background(&block)
167
171
  return yield unless options[:run_in_background]
168
- "nohup %s > /dev/null &" % yield
172
+ "( nohup %s > /dev/null & )" % yield
169
173
  end
170
174
 
171
175
  def umask(&block)
@@ -1,8 +1,29 @@
1
1
  module SSHKit
2
2
  class CommandMap
3
+ class CommandHash
4
+ def initialize(defaults = {})
5
+ @storage = {}
6
+ @defaults = defaults
7
+ end
8
+
9
+ def [](key)
10
+ @storage[normalize_key(key)] ||= @defaults[key]
11
+ end
12
+
13
+ def []=(key, value)
14
+ @storage[normalize_key(key)] = value
15
+ end
16
+
17
+ private
18
+
19
+ def normalize_key(key)
20
+ key.to_sym
21
+ end
22
+ end
23
+
3
24
  class PrefixProvider
4
25
  def initialize
5
- @storage = {}
26
+ @storage = CommandHash.new
6
27
  end
7
28
 
8
29
  def [](command)
@@ -13,7 +34,7 @@ module SSHKit
13
34
  end
14
35
 
15
36
  def initialize(value = nil)
16
- @map = value || defaults
37
+ @map = CommandHash.new(value || defaults)
17
38
  end
18
39
 
19
40
  def [](command)
@@ -35,7 +56,7 @@ module SSHKit
35
56
  end
36
57
 
37
58
  def clear
38
- @map = defaults
59
+ @map = CommandHash.new(defaults)
39
60
  end
40
61
 
41
62
  def defaults
@@ -6,7 +6,7 @@ module SSHKit
6
6
 
7
7
  def initialize(raw_hosts)
8
8
  @raw_hosts = Array(raw_hosts)
9
- resolve_hosts! if Array(raw_hosts).any?
9
+ @hosts = @raw_hosts.any? ? resolve_hosts : []
10
10
  end
11
11
 
12
12
  def each(options={}, &block)
@@ -32,8 +32,8 @@ module SSHKit
32
32
  { in: :parallel }
33
33
  end
34
34
 
35
- def resolve_hosts!
36
- @hosts = @raw_hosts.collect { |rh| rh.is_a?(Host) ? rh : Host.new(rh) }.uniq
35
+ def resolve_hosts
36
+ @raw_hosts.collect { |rh| rh.is_a?(Host) ? rh : Host.new(rh) }.uniq
37
37
  end
38
38
 
39
39
  end
data/lib/sshkit/dsl.rb CHANGED
@@ -1,5 +1,3 @@
1
- require_relative '../sshkit'
2
-
3
1
  module SSHKit
4
2
 
5
3
  module DSL
@@ -0,0 +1,58 @@
1
+
2
+ module SSHKit
3
+
4
+ module Formatter
5
+
6
+ class SimpleText < Abstract
7
+
8
+ def write(obj)
9
+ return if obj.verbosity < SSHKit.config.output_verbosity
10
+ case obj
11
+ when SSHKit::Command then write_command(obj)
12
+ when SSHKit::LogMessage then write_log_message(obj)
13
+ else
14
+ original_output << "Output formatter doesn't know how to handle #{obj.class}\n"
15
+ end
16
+ end
17
+ alias :<< :write
18
+
19
+ private
20
+
21
+ def write_command(command)
22
+ unless command.started?
23
+ original_output << "Running #{String(command)} on #{command.host.to_s}\n"
24
+ if SSHKit.config.output_verbosity == Logger::DEBUG
25
+ original_output << "Command: #{command.to_command}" + "\n"
26
+ end
27
+ end
28
+
29
+ if SSHKit.config.output_verbosity == Logger::DEBUG
30
+ unless command.stdout.empty?
31
+ command.stdout.lines.each do |line|
32
+ original_output << "\t" + line
33
+ original_output << "\n" unless line[-1] == "\n"
34
+ end
35
+ end
36
+
37
+ unless command.stderr.empty?
38
+ command.stderr.lines.each do |line|
39
+ original_output << "\t" + line
40
+ original_output << "\n" unless line[-1] == "\n"
41
+ end
42
+ end
43
+ end
44
+
45
+ if command.finished?
46
+ original_output << "Finished in #{sprintf('%5.3f seconds', command.runtime)} with exit status #{command.exit_status} (#{ command.failure? ? 'failed' : 'successful' }).\n"
47
+ end
48
+ end
49
+
50
+ def write_log_message(log_message)
51
+ original_output << log_message.to_s + "\n"
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -9,7 +9,7 @@ module SSHKit
9
9
  threads = []
10
10
  hosts.each do |host|
11
11
  threads << Thread.new(host) do |h|
12
- backend(host, &block).run
12
+ backend(h, &block).run
13
13
  end
14
14
  end
15
15
  threads.map(&:join)
@@ -0,0 +1,13 @@
1
+ module SSHKit
2
+ module Utils
3
+ module CaptureOutputMethods
4
+ def <<(object)
5
+ if object.is_a?(SSHKit::Command) || object.is_a?(SSHKit::LogMessage)
6
+ super("#{object}\n")
7
+ else
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.3.0"
2
+ VERSION = "1.4.0"
3
3
  end
data/sshkit.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ["lib"]
18
18
  gem.version = SSHKit::VERSION
19
19
 
20
- gem.add_runtime_dependency('net-ssh')
20
+ gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
21
21
  gem.add_runtime_dependency('net-scp', '>= 1.1.2')
22
22
  gem.add_runtime_dependency('term-ansicolor')
23
23
 
data/test/boxes.json CHANGED
@@ -1,7 +1,10 @@
1
1
  [
2
2
  {
3
3
  "name": "one",
4
- "port": 3001
4
+ "port": 3001,
5
+ "user": "vagrant",
6
+ "password": "vagrant",
7
+ "hostname": "localhost"
5
8
  },
6
9
  {
7
10
  "name": "two",
@@ -23,7 +23,7 @@ module SSHKit
23
23
  execute :echo, "'Test capturing stderr' 1>&2; false"
24
24
  end.run
25
25
  end
26
- assert_equal "echo stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
26
+ assert_equal "echo exit status: 256\necho stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
27
27
  end
28
28
 
29
29
  def test_test
@@ -28,7 +28,7 @@ module SSHKit
28
28
  end
29
29
 
30
30
  def a_host
31
- VagrantWrapper.hosts["one"]
31
+ VagrantWrapper.hosts['one']
32
32
  end
33
33
 
34
34
  def printer
@@ -54,11 +54,13 @@ module SSHKit
54
54
  def test_capture
55
55
  File.open('/dev/null', 'w') do |dnull|
56
56
  SSHKit.capture_output(dnull) do
57
- captured_command_result = ""
57
+ captured_command_result = nil
58
58
  Netssh.new(a_host) do |host|
59
59
  captured_command_result = capture(:uname)
60
60
  end.run
61
- assert_equal "Linux", captured_command_result
61
+
62
+ assert captured_command_result
63
+ assert_match captured_command_result, /Linux|Darwin/
62
64
  end
63
65
  end
64
66
  end
@@ -69,7 +71,7 @@ module SSHKit
69
71
  execute :echo, "'Test capturing stderr' 1>&2; false"
70
72
  end.run
71
73
  end
72
- assert_equal "echo stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
74
+ assert_equal "echo exit status: 1\necho stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
73
75
  end
74
76
 
75
77
  def test_test_does_not_raise_on_non_zero_exit_status
@@ -130,7 +132,6 @@ module SSHKit
130
132
  end.run
131
133
  assert_equal File.open(file_name).read, file_contents
132
134
  end
133
-
134
135
  end
135
136
 
136
137
  end
@@ -13,6 +13,7 @@ module SSHKit
13
13
  end
14
14
 
15
15
  def test_creating_a_user_gives_us_back_his_private_key_as_a_string
16
+ skip 'It is not safe to create an user for non vagrant envs' unless VagrantWrapper.running?
16
17
  keys = create_user_with_key(:peter)
17
18
  assert_equal [:one, :two, :three], keys.keys
18
19
  assert keys.values.all?
data/test/helper.rb CHANGED
@@ -12,41 +12,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
12
12
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
13
  require 'sshkit'
14
14
 
15
- class VagrantWrapper
16
- class << self
17
- def hosts
18
- @vm_hosts ||= begin
19
- result = {}
20
- boxes_list.each do |vm|
21
- host = SSHKit::Host.new("vagrant@localhost:#{vm["port"]}").tap do |h|
22
- h.password = 'vagrant'
23
- end
24
-
25
- result[vm["name"]] = host
26
- end
27
-
28
- result
29
- end
30
- end
31
-
32
- def running?
33
- @running ||= begin
34
- status = `#{vagrant_binary} status`
35
- status.include?('running')
36
- end
37
- end
38
-
39
- def boxes_list
40
- json_config_path = File.join("test", "boxes.json")
41
- boxes = File.open(json_config_path).read
42
- JSON.parse(boxes)
43
- end
44
-
45
- def vagrant_binary
46
- 'vagrant'
47
- end
48
- end
49
- end
15
+ Dir[File.expand_path('test/support/*.rb')].each { |file| require file }
50
16
 
51
17
  class UnitTest < MiniTest::Unit::TestCase
52
18
 
@@ -54,13 +20,18 @@ class UnitTest < MiniTest::Unit::TestCase
54
20
  SSHKit.reset_configuration!
55
21
  end
56
22
 
23
+ SSHKit::Backend::ConnectionPool.class_eval do
24
+ def flush_connections
25
+ Thread.current[:sshkit_pool] = {}
26
+ end
27
+ end
57
28
  end
58
29
 
59
30
  class FunctionalTest < MiniTest::Unit::TestCase
60
31
 
61
32
  def setup
62
33
  unless VagrantWrapper.running?
63
- raise "Vagrant VMs are not running. Please, start it manually with `vagrant up`"
34
+ warn "Vagrant VMs are not running. Please, start it manually with `vagrant up`"
64
35
  end
65
36
  end
66
37
 
@@ -0,0 +1,56 @@
1
+ class VagrantWrapper
2
+ class << self
3
+ def hosts
4
+ @vm_hosts ||= begin
5
+ result = {}
6
+
7
+ boxes = boxes_list
8
+
9
+ unless running?
10
+ boxes.map! do |box|
11
+ box['user'] = ENV['USER']
12
+ box['port'] = '22'
13
+ box
14
+ end
15
+ end
16
+
17
+ boxes.each do |vm|
18
+ result[vm['name']] = vm_host(vm)
19
+ end
20
+
21
+ result
22
+ end
23
+ end
24
+
25
+ def running?
26
+ @running ||= begin
27
+ status = `#{vagrant_binary} status`
28
+ status.include?('running')
29
+ end
30
+ end
31
+
32
+ def boxes_list
33
+ json_config_path = File.join('test', 'boxes.json')
34
+ boxes = File.open(json_config_path).read
35
+ JSON.parse(boxes)
36
+ end
37
+
38
+ def vagrant_binary
39
+ 'vagrant'
40
+ end
41
+
42
+ private
43
+
44
+ def vm_host(vm)
45
+ host_options = {
46
+ user: vm['user'] || 'vagrant',
47
+ hostname: vm['hostname'] || 'localhost',
48
+ port: vm['port'] || '22'
49
+ }
50
+
51
+
52
+
53
+ SSHKit::Host.new(host_options)
54
+ end
55
+ end
56
+ end
@@ -39,7 +39,7 @@ module SSHKit
39
39
  assert_equal conn1, conn2
40
40
  end
41
41
 
42
- def test_zero_idle_timeout_disables_resuse
42
+ def test_zero_idle_timeout_disables_reuse
43
43
  pool.idle_timeout = 0
44
44
 
45
45
  conn1 = pool.create_or_reuse_connection("conn", &connect)
@@ -59,6 +59,8 @@ module SSHKit
59
59
  end
60
60
 
61
61
  def test_closed_connection_is_not_reused
62
+ # Ensure there aren't any other open connections
63
+ pool.flush_connections()
62
64
  conn1 = pool.create_or_reuse_connection("conn", &connect_and_close)
63
65
  conn2 = pool.create_or_reuse_connection("conn", &connect)
64
66
 
@@ -0,0 +1,18 @@
1
+ require 'helper'
2
+
3
+ module SSHKit
4
+ module Backend
5
+ class TestLocal < UnitTest
6
+
7
+ def local
8
+ @local ||= Local.new
9
+ end
10
+
11
+ def test_execute
12
+ assert_equal true, local.execute('uname -a')
13
+ assert_equal true, local.execute
14
+ assert_equal true, local.execute('cd && pwd')
15
+ end
16
+ end
17
+ end
18
+ end
@@ -59,6 +59,12 @@ module SSHKit
59
59
  assert_equal false, backend.config.ssh_options[:forward_agent]
60
60
  assert_equal %w(publickey password), backend.config.ssh_options[:auth_methods]
61
61
  end
62
+
63
+ def test_invoke_raises_no_method_error
64
+ assert_raises NoMethodError do
65
+ printer.invoke :echo
66
+ end
67
+ end
62
68
 
63
69
  end
64
70
 
@@ -93,17 +93,22 @@ module SSHKit
93
93
 
94
94
  def test_backgrounding_a_task
95
95
  c = Command.new(:sleep, 15, run_in_background: true)
96
- assert_equal "nohup /usr/bin/env sleep 15 > /dev/null &", c.to_command
96
+ assert_equal "( nohup /usr/bin/env sleep 15 > /dev/null & )", c.to_command
97
97
  end
98
98
 
99
99
  def test_backgrounding_a_task_as_a_given_user
100
100
  c = Command.new(:sleep, 15, run_in_background: true, user: :anotheruser)
101
- assert_equal "sudo su anotheruser -c \"nohup /usr/bin/env sleep 15 > /dev/null &\"", c.to_command
101
+ assert_equal "sudo su anotheruser -c \"( nohup /usr/bin/env sleep 15 > /dev/null & )\"", c.to_command
102
102
  end
103
103
 
104
104
  def test_backgrounding_a_task_as_a_given_user_with_env
105
105
  c = Command.new(:sleep, 15, run_in_background: true, user: :anotheruser, env: {a: :b})
106
- assert_equal "( A=b sudo su anotheruser -c \"nohup /usr/bin/env sleep 15 > /dev/null &\" )", c.to_command
106
+ assert_equal "( A=b sudo su anotheruser -c \"( nohup /usr/bin/env sleep 15 > /dev/null & )\" )", c.to_command
107
+ end
108
+
109
+ def test_backgrounding_a_task_with_working_directory
110
+ c = Command.new(:sleep, 15, run_in_background: true, in: '/opt')
111
+ assert_equal "cd /opt && ( nohup /usr/bin/env sleep 15 > /dev/null & )", c.to_command
107
112
  end
108
113
 
109
114
  def test_umask
@@ -205,7 +210,7 @@ module SSHKit
205
210
  error = assert_raises SSHKit::Command::Failed do
206
211
  Command.new(:whoami).exit_status = 1
207
212
  end
208
- assert_equal "whoami stdout: Nothing written\nwhoami stderr: Nothing written\n", error.message
213
+ assert_equal "whoami exit status: 1\nwhoami stdout: Nothing written\nwhoami stderr: Nothing written\n", error.message
209
214
  end
210
215
 
211
216
  end
@@ -36,5 +36,21 @@ module SSHKit
36
36
  assert_equal map[:rake], "/home/vagrant/.rbenv/bin/rbenv exec bundle exec rake"
37
37
  end
38
38
 
39
+ def test_indifferent_setter
40
+ map = CommandMap.new
41
+ map[:rake] = "/usr/local/rbenv/shims/rake"
42
+ map["rake"] = "/usr/local/rbenv/shims/rake2"
43
+
44
+ assert_equal "/usr/local/rbenv/shims/rake2", map[:rake]
45
+ end
46
+
47
+ def test_indifferent_prefix
48
+ map = CommandMap.new
49
+ map.prefix[:rake].push("/home/vagrant/.rbenv/bin/rbenv exec")
50
+ map.prefix["rake"].push("bundle exec")
51
+
52
+ assert_equal map[:rake], "/home/vagrant/.rbenv/bin/rbenv exec bundle exec rake"
53
+ end
54
+
39
55
  end
40
56
  end
@@ -21,6 +21,12 @@ module SSHKit
21
21
  end
22
22
  end
23
23
 
24
+ def test_the_connection_manager_handles_empty_argument
25
+ Coordinator.new([]).each do
26
+ raise "This should not be executed"
27
+ end
28
+ end
29
+
24
30
  def test_connection_manager_handles_a_single_argument
25
31
  h = Host.new('1.example.com')
26
32
  Host.expects(:new).with('1.example.com').once().returns(h)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Hambley
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-19 00:00:00.000000000 Z
12
+ date: 2014-04-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - '>='
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: 2.8.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '>='
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
27
+ version: 2.8.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: net-scp
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -172,6 +172,7 @@ files:
172
172
  - lib/sshkit/formatters/black_hole.rb
173
173
  - lib/sshkit/formatters/dot.rb
174
174
  - lib/sshkit/formatters/pretty.rb
175
+ - lib/sshkit/formatters/simple_text.rb
175
176
  - lib/sshkit/host.rb
176
177
  - lib/sshkit/log_message.rb
177
178
  - lib/sshkit/logger.rb
@@ -180,6 +181,7 @@ files:
180
181
  - lib/sshkit/runners/null.rb
181
182
  - lib/sshkit/runners/parallel.rb
182
183
  - lib/sshkit/runners/sequential.rb
184
+ - lib/sshkit/utils/capture_output_methods.rb
183
185
  - lib/sshkit/version.rb
184
186
  - sshkit.gemspec
185
187
  - test/boxes.json
@@ -188,7 +190,9 @@ files:
188
190
  - test/functional/test_coordinator.rb
189
191
  - test/functional/test_ssh_server_comes_up_for_functional_tests.rb
190
192
  - test/helper.rb
193
+ - test/support/vagrant_wrapper.rb
191
194
  - test/unit/backends/test_connection_pool.rb
195
+ - test/unit/backends/test_local.rb
192
196
  - test/unit/backends/test_netssh.rb
193
197
  - test/unit/backends/test_printer.rb
194
198
  - test/unit/core_ext/test_string.rb
@@ -231,7 +235,9 @@ test_files:
231
235
  - test/functional/test_coordinator.rb
232
236
  - test/functional/test_ssh_server_comes_up_for_functional_tests.rb
233
237
  - test/helper.rb
238
+ - test/support/vagrant_wrapper.rb
234
239
  - test/unit/backends/test_connection_pool.rb
240
+ - test/unit/backends/test_local.rb
235
241
  - test/unit/backends/test_netssh.rb
236
242
  - test/unit/backends/test_printer.rb
237
243
  - test/unit/core_ext/test_string.rb