sshkit 1.19.0 → 1.21.2

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/Dangerfile CHANGED
@@ -1 +1 @@
1
- danger.import_dangerfile(github: "capistrano/danger")
1
+ danger.import_dangerfile(github: "capistrano/danger", branch: "no-changelog")
@@ -171,6 +171,7 @@ individual hosts:
171
171
  SSHKit::Backend::Netssh.configure do |ssh|
172
172
  ssh.connection_timeout = 30
173
173
  ssh.ssh_options = {
174
+ user: 'adifferentuser',
174
175
  keys: %w(/home/user/.ssh/id_rsa),
175
176
  forward_agent: false,
176
177
  auth_methods: %w(publickey password)
data/Gemfile CHANGED
@@ -2,18 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- platforms :rbx do
6
- gem 'rubysl', '~> 2.0'
7
- gem 'json'
8
- end
9
-
10
- # Chandler requires Ruby >= 2.1.0, but depending on the Travis environment,
11
- # we may not meet that requirement. Only include the chandler gem if the Ruby
12
- # requirement is met. (Chandler is used only for `rake release`; see Rakefile.)
13
- if Gem::Requirement.new('>= 2.1.0').satisfied_by?(Gem::Version.new(RUBY_VERSION))
14
- gem 'chandler', '>= 0.1.1'
15
- end
16
-
17
5
  # public_suffix 3+ requires ruby 2.1+
18
6
  if Gem::Requirement.new('< 2.1').satisfied_by?(Gem::Version.new(RUBY_VERSION))
19
7
  gem 'public_suffix', '< 3'
data/README.md CHANGED
@@ -6,29 +6,31 @@ more servers.
6
6
  [![Gem Version](https://badge.fury.io/rb/sshkit.svg)](https://rubygems.org/gems/sshkit)
7
7
  [![Build Status](https://travis-ci.org/capistrano/sshkit.svg?branch=master)](https://travis-ci.org/capistrano/sshkit)
8
8
 
9
- ## How might it work?
9
+ ## Example
10
10
 
11
- The typical use-case looks something like this:
11
+ - Connect to 2 servers
12
+ - Execute commands as `deploy` user with `RAILS_ENV=production`
13
+ - Execute commands in serial (default is `:parallel`)
12
14
 
13
15
  ```ruby
14
16
  require 'sshkit'
15
17
  require 'sshkit/dsl'
16
18
  include SSHKit::DSL
17
19
 
18
- on %w{1.example.com 2.example.com}, in: :sequence, wait: 5 do |host|
20
+ on ["1.example.com", "2.example.com"], in: :sequence do |host|
21
+ puts "Now executing on #{host}"
19
22
  within "/opt/sites/example.com" do
20
23
  as :deploy do
21
- with rails_env: :production do
22
- rake "assets:precompile"
23
- runner "S3::Sync.notify"
24
- execute :node, "socket_server.js"
24
+ with RAILS_ENV: 'production' do
25
+ execute :rake, "assets:precompile"
26
+ execute :rails, "runner", "S3::Sync.notify"
25
27
  end
26
28
  end
27
29
  end
28
30
  end
29
31
  ```
30
32
 
31
- You can find many other examples of how to use SSHKit over in [EXAMPLES.md](EXAMPLES.md).
33
+ Many other examples are in [EXAMPLES.md](EXAMPLES.md).
32
34
 
33
35
  ## Basic usage
34
36
 
@@ -37,7 +39,7 @@ You can pass one or more hosts as parameters; this runs commands via SSH. Altern
37
39
  pass `:local` to run commands locally. By default SSKit will run the commands on all hosts in
38
40
  parallel.
39
41
 
40
- #### Running commands
42
+ ### Running commands
41
43
 
42
44
  All backends support the `execute(*args)`, `test(*args)` & `capture(*args)` methods
43
45
  for executing a command. You can call any of these methods in the context of an `on()`
@@ -63,7 +65,7 @@ end
63
65
  By default the `capture` methods strips whitespace. If you need to preserve whitespace
64
66
  you can pass the `strip: false` option: `capture(:ls, '-l', strip: false)`
65
67
 
66
- #### Transferring files
68
+ ### Transferring files
67
69
 
68
70
  All backends also support the `upload!` and `download!` methods for transferring files.
69
71
  For the remote backend, the file is transferred with scp.
@@ -75,7 +77,7 @@ on '1.example.com' do
75
77
  end
76
78
  ```
77
79
 
78
- #### Users, working directories, environment variables and umask
80
+ ### Users, working directories, environment variables and umask
79
81
 
80
82
  When running commands, you can tell SSHKit to set up the context for those
81
83
  commands using the following methods:
@@ -113,6 +115,11 @@ the raised error.
113
115
  Helpers such as `runner()` and `rake()` which expand to `execute(:rails, "runner", ...)` and
114
116
  `execute(:rake, ...)` are convenience helpers for Ruby, and Rails based apps.
115
117
 
118
+ ### Verbosity / Silence
119
+
120
+ - raise verbosity of a command: `execute "echo DEAD", verbosity: :ERROR`
121
+ - hide a command from output: `execute "echo HIDDEN", verbosity: :DEBUG`
122
+
116
123
  ## Parallel
117
124
 
118
125
  Notice on the `on()` call the `in: :sequence` option, the following will do
@@ -567,10 +574,20 @@ In order to do special gymnastics with SSH, tunneling, aliasing, complex options
567
574
 
568
575
  These system level files are the preferred way of setting up tunneling and proxies because the system implementations of these things are faster and better than the Ruby implementations you would get if you were to configure them through Net::SSH. In cases where it's not possible (Windows?), it should be possible to make use of the Net::SSH APIs to setup tunnels and proxy commands before deferring control to Capistrano/SSHKit..
569
576
 
570
- ## SSHKit Related Blog Posts
577
+ ## Proxying
571
578
 
572
- [SSHKit Gem Basics](http://www.rubyplus.com/articles/591)
579
+ To connect to the target host via a jump/bastion host, use a `Net::SSH::Proxy::Jump`
573
580
 
574
- [SSHKit Gem Part 2](http://www.rubyplus.com/articles/601)
581
+ ```ruby
582
+ host = SSHKit::Host.new(
583
+ hostname: 'target.host.com',
584
+ ssh_options: { proxy: Net::SSH::Proxy::Jump.new("proxy.bar.com") }
585
+ )
586
+ on [host] do
587
+ execute :echo, '1'
588
+ end
589
+ ```
590
+
591
+ ## SSHKit Related Blog Posts
575
592
 
576
593
  [Embedded Capistrano with SSHKit](http://ryandoyle.net/posts/embedded-capistrano-with-sshkit/)
@@ -5,7 +5,6 @@
5
5
  * You must have commit rights to the SSHKit repository.
6
6
  * You must have push rights for the sshkit gem on rubygems.org.
7
7
  * You must be using Ruby >= 2.1.0.
8
- * Your `~/.netrc` must be configured with your GitHub credentials, [as explained here](https://github.com/mattbrictson/chandler#2-configure-netrc).
9
8
 
10
9
  ## How to release
11
10
 
@@ -13,6 +12,6 @@
13
12
  2. **Ensure the tests are passing by running `rake test`.** If functional tests fail, ensure you have [Vagrant](https://www.vagrantup.com) installed and have started it with `vagrant up`.
14
13
  3. Determine which would be the correct next version number according to [semver](http://semver.org/).
15
14
  4. Update the version in `./lib/sshkit/version.rb`.
16
- 5. Update the `CHANGELOG`.
17
- 6. Commit the changelog and version in a single commit, the message should be "Preparing vX.Y.Z"
18
- 7. Run `rake release`; this will tag, push to GitHub, publish to rubygems.org, and upload the latest changelog entry to the [GitHub releases page](https://github.com/capistrano/sshkit/releases).
15
+ 5. Commit the `version.rb` change with a message like "Preparing vX.Y.Z"
16
+ 6. Run `rake release`; this will tag, push to GitHub, and publish to rubygems.org
17
+ 7. Update the draft release on the [GitHub releases page](https://github.com/capistrano/sshkit/releases) to point to the new tag and publish the release
data/Rakefile CHANGED
@@ -30,10 +30,7 @@ RuboCop::RakeTask.new(:lint) do |task|
30
30
  task.options = ['--lint']
31
31
  end
32
32
 
33
- task "release:rubygem_push" do
34
- # Delay loading Chandler until this point, since it requires Ruby >= 2.1,
35
- # which may not be available in all environments (e.g. Travis).
36
- # We assume that the person doing `rake release` has Ruby >= 2.1.
37
- require "chandler/tasks"
38
- Rake.application.invoke_task("chandler:push")
33
+ Rake::Task["release"].enhance do
34
+ puts "Don't forget to publish the release on GitHub!"
35
+ system "open https://github.com/capistrano/sshkit/releases"
39
36
  end
@@ -2,6 +2,11 @@ VAGRANTFILE_API_VERSION = "2"
2
2
 
3
3
  Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
4
4
  config.vm.box = 'hashicorp/precise64'
5
+ config.vm.provision "shell", inline: <<-SHELL
6
+ echo 'ClientAliveInterval 1' >> /etc/ssh/sshd_config
7
+ echo 'ClientAliveCountMax 1' >> /etc/ssh/sshd_config
8
+ service ssh restart
9
+ SHELL
5
10
 
6
11
  json_config_path = File.join("test", "boxes.json")
7
12
  list = File.open(json_config_path).read
@@ -0,0 +1,9 @@
1
+ # tiny example so you can play with the sshkit or make a failing example for an issue
2
+ require 'bundler/setup'
3
+ require 'sshkit'
4
+ require 'sshkit/dsl'
5
+ include SSHKit::DSL
6
+
7
+ on [ENV.fetch("HOST")] do
8
+ execute "echo hello"
9
+ end
@@ -82,9 +82,10 @@ module SSHKit
82
82
 
83
83
  def within(directory, &_block)
84
84
  (@pwd ||= []).push directory.to_s
85
+ escaped = Command.shellescape_except_tilde(pwd_path)
85
86
  execute <<-EOTEST, verbosity: Logger::DEBUG
86
- if test ! -d #{File.join(@pwd).shellescape}
87
- then echo "Directory does not exist '#{File.join(@pwd).shellescape}'" 1>&2
87
+ if test ! -d #{escaped}
88
+ then echo "Directory does not exist '#{escaped}'" 1>&2
88
89
  false
89
90
  fi
90
91
  EOTEST
@@ -36,12 +36,12 @@ class SSHKit::Backend::ConnectionPool::Cache
36
36
  def evict
37
37
  # Peek at the first connection to see if it is still fresh. If so, we can
38
38
  # return right away without needing to use `synchronize`.
39
- first_expires_at, _connection = connections.first
40
- return if first_expires_at.nil? || fresh?(first_expires_at)
39
+ first_expires_at, first_conn = connections.first
40
+ return if (first_expires_at.nil? || fresh?(first_expires_at)) && !closed?(first_conn)
41
41
 
42
42
  connections.synchronize do
43
- fresh, stale = connections.partition do |expires_at, _|
44
- fresh?(expires_at)
43
+ fresh, stale = connections.partition do |expires_at, conn|
44
+ fresh?(expires_at) && !closed?(conn)
45
45
  end
46
46
  connections.replace(fresh)
47
47
  stale.each { |_, conn| closer.call(conn) }
@@ -71,6 +71,13 @@ class SSHKit::Backend::ConnectionPool::Cache
71
71
  end
72
72
 
73
73
  def closed?(conn)
74
- conn.respond_to?(:closed?) && conn.closed?
74
+ return true if conn.respond_to?(:closed?) && conn.closed?
75
+ # test if connection is alive
76
+ conn.process(0) if conn.respond_to?(:process)
77
+ return false
78
+ rescue IOError => e
79
+ # connection is closed by server
80
+ return true if e.message == 'closed stream'
81
+ raise
75
82
  end
76
83
  end
@@ -109,7 +109,8 @@ module SSHKit
109
109
  message = "#{action} #{name} #{percentage.round(2)}%"
110
110
  percentage_r = (percentage / log_percent).truncate * log_percent
111
111
  if percentage_r > 0 && (last_name != name || last_percentage != percentage_r)
112
- info message
112
+ verbosity = (options[:verbosity] || :INFO).downcase # TODO: ideally reuse command.rb logic
113
+ public_send verbosity, message
113
114
  last_name = name
114
115
  last_percentage = percentage_r
115
116
  else
@@ -143,7 +143,7 @@ module SSHKit
143
143
 
144
144
  def within(&_block)
145
145
  return yield unless options[:in]
146
- "cd #{options[:in].shellescape} && #{yield}"
146
+ "cd #{self.class.shellescape_except_tilde(options[:in])} && #{yield}"
147
147
  end
148
148
 
149
149
  def environment_hash
@@ -219,6 +219,11 @@ module SSHKit
219
219
  end
220
220
  end
221
221
 
222
+ # allow using home directory but escape everything else like spaces etc
223
+ def self.shellescape_except_tilde(file)
224
+ file.shellescape.gsub("\\~", "~")
225
+ end
226
+
222
227
  private
223
228
 
224
229
  def default_options
@@ -234,7 +239,7 @@ module SSHKit
234
239
 
235
240
  def call_interaction_handler(stream_name, data, channel)
236
241
  interaction_handler = options[:interaction_handler]
237
- interaction_handler = MappingInteractionHandler.new(interaction_handler) if interaction_handler.kind_of?(Hash)
242
+ interaction_handler = MappingInteractionHandler.new(interaction_handler) if interaction_handler.kind_of?(Hash) or interaction_handler.kind_of?(Proc)
238
243
  interaction_handler.on_data(self, stream_name, data, channel) if interaction_handler.respond_to?(:on_data)
239
244
  end
240
245
 
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.19.0".freeze
2
+ VERSION = "1.21.2".freeze
3
3
  end
@@ -9,6 +9,9 @@ Gem::Specification.new do |gem|
9
9
  gem.description = %q{A comprehensive toolkit for remotely running commands in a structured manner on groups of servers.}
10
10
  gem.homepage = "http://github.com/capistrano/sshkit"
11
11
  gem.license = "MIT"
12
+ gem.metadata = {
13
+ "changelog_uri" => "https://github.com/capistrano/sshkit/releases"
14
+ }
12
15
 
13
16
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
17
  gem.files = `git ls-files`.split("\n")
@@ -99,6 +99,24 @@ module SSHKit
99
99
  end.run
100
100
  assert_equal("Enter Data\nCaptured SOME DATA", captured_command_result)
101
101
  end
102
+
103
+ def test_interaction_handler_with_proc
104
+ captured_command_result = nil
105
+ Local.new do
106
+ command = 'echo Enter Data; read the_data; echo Captured $the_data;'
107
+ captured_command_result = capture(command, interaction_handler:
108
+ lambda { |data|
109
+ case data
110
+ when "Enter Data\n"
111
+ "SOME DATA\n"
112
+ when "Captured SOME DATA\n"
113
+ nil
114
+ end
115
+ }
116
+ )
117
+ end.run
118
+ assert_equal("Enter Data\nCaptured SOME DATA", captured_command_result)
119
+ end
102
120
  end
103
121
  end
104
122
  end
@@ -180,7 +180,7 @@ module SSHKit
180
180
  size = 25
181
181
  fills = SecureRandom.random_bytes(1024*1024)
182
182
  file_name = "/tmp/file-#{size}.txt"
183
- File.open(file_name, 'w') do |f|
183
+ File.open(file_name, 'wb') do |f|
184
184
  (size).times {f.write(fills) }
185
185
  end
186
186
  file_contents = ""
@@ -188,7 +188,7 @@ module SSHKit
188
188
  upload!(file_name, file_name)
189
189
  file_contents = download!(file_name)
190
190
  end.run
191
- assert_equal File.open(file_name).read, file_contents
191
+ assert_equal File.open(file_name, 'rb').read, file_contents
192
192
  end
193
193
 
194
194
  def test_upload_via_pathname
@@ -212,6 +212,20 @@ module SSHKit
212
212
  end.run
213
213
  assert_equal("Enter Data\nCaptured SOME DATA", captured_command_result)
214
214
  end
215
+
216
+ def test_connection_pool_keepalive
217
+ # ensure we enable connection pool
218
+ SSHKit::Backend::Netssh.pool.idle_timeout = 10
219
+ Netssh.new(a_host) do |_host|
220
+ test :false
221
+ end.run
222
+ sleep 2.5
223
+ captured_command_result = nil
224
+ Netssh.new(a_host) do |_host|
225
+ captured_command_result = capture(:echo, 'some_value')
226
+ end.run
227
+ assert_equal "some_value", captured_command_result
228
+ end
215
229
  end
216
230
 
217
231
  end
@@ -3,7 +3,7 @@ require 'bundler/setup'
3
3
  require 'tempfile'
4
4
  require 'minitest/autorun'
5
5
  require 'minitest/reporters'
6
- require 'mocha/setup'
6
+ require 'mocha/minitest'
7
7
  require 'stringio'
8
8
  require 'json'
9
9
 
@@ -99,6 +99,18 @@ module SSHKit
99
99
  assert_equal '/usr/bin/env cat file', backend.executed_command.to_command
100
100
  end
101
101
 
102
+ def test_within_home
103
+ backend = ExampleBackend.new do
104
+ within '~/foo' do
105
+ execute :cat, 'file', :strip => false
106
+ end
107
+ end
108
+
109
+ backend.run
110
+
111
+ assert_equal 'cd ~/foo && /usr/bin/env cat file', backend.executed_command.to_command
112
+ end
113
+
102
114
  def test_background_logs_deprecation_warnings
103
115
  deprecation_out = ''
104
116
  SSHKit.config.deprecation_output = deprecation_out
@@ -54,6 +54,13 @@ module SSHKit
54
54
  end
55
55
  end
56
56
 
57
+ def test_transfer_summarizer_uses_verbosity
58
+ netssh = Netssh.new(Host.new('fake'))
59
+ summarizer = netssh.send(:transfer_summarizer, 'Transferring', verbosity: :ERROR)
60
+ netssh.expects(:error).with { |msg| msg.include?('Transferring afile 15.0%') }
61
+ summarizer.call(nil,'afile',15,100)
62
+ end
63
+
57
64
  if Net::SSH::Version::CURRENT >= Net::SSH::Version[3, 1, 0]
58
65
  def test_known_hosts_for_when_all_hosts_are_recognized
59
66
  perform_known_hosts_test('github', 'github.com')
@@ -115,7 +115,7 @@ module SSHKit
115
115
 
116
116
  def test_can_write_to_output_which_just_supports_append
117
117
  # Note output doesn't have to be an IO, it only needs to support <<
118
- output = stub(:<<)
118
+ output = stub(:<< => nil)
119
119
  pretty = SSHKit::Formatter::Pretty.new(output)
120
120
  simulate_command_lifecycle(pretty)
121
121
  end
@@ -84,6 +84,11 @@ module SSHKit
84
84
  assert_equal "cd /opt/sites && /usr/bin/env ls -l", c.to_command
85
85
  end
86
86
 
87
+ def test_working_in_home_directory
88
+ c = Command.new(:ls, '-l', in: "~/sites")
89
+ assert_equal "cd ~/sites && /usr/bin/env ls -l", c.to_command
90
+ end
91
+
87
92
  def test_working_in_a_given_weird_directory
88
93
  c = Command.new(:ls, '-l', in: "/opt/sites and stuff")
89
94
  assert_equal "cd /opt/sites\\ and\\ stuff && /usr/bin/env ls -l", c.to_command
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.19.0
4
+ version: 1.21.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Hambley
8
8
  - Tom Clements
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-07-01 00:00:00.000000000 Z
12
+ date: 2021-01-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
@@ -180,6 +180,8 @@ executables: []
180
180
  extensions: []
181
181
  extra_rdoc_files: []
182
182
  files:
183
+ - ".github/release-drafter.yml"
184
+ - ".github/workflows/push.yml"
183
185
  - ".gitignore"
184
186
  - ".rubocop.yml"
185
187
  - ".rubocop_todo.yml"
@@ -199,6 +201,7 @@ files:
199
201
  - Vagrantfile
200
202
  - examples/images/example_output.png
201
203
  - examples/images/logo.png
204
+ - examples/simple_connection.rb
202
205
  - lib/core_ext/array.rb
203
206
  - lib/core_ext/hash.rb
204
207
  - lib/sshkit.rb
@@ -270,8 +273,9 @@ files:
270
273
  homepage: http://github.com/capistrano/sshkit
271
274
  licenses:
272
275
  - MIT
273
- metadata: {}
274
- post_install_message:
276
+ metadata:
277
+ changelog_uri: https://github.com/capistrano/sshkit/releases
278
+ post_install_message:
275
279
  rdoc_options: []
276
280
  require_paths:
277
281
  - lib
@@ -286,8 +290,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
286
290
  - !ruby/object:Gem::Version
287
291
  version: '0'
288
292
  requirements: []
289
- rubygems_version: 3.0.4
290
- signing_key:
293
+ rubygems_version: 3.2.5
294
+ signing_key:
291
295
  specification_version: 4
292
296
  summary: SSHKit makes it easy to write structured, testable SSH commands in Ruby
293
297
  test_files: