sshkit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/.yardoc/checksums +13 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -0
- data/EXAMPLES.md +167 -0
- data/FAQ.md +17 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.md +674 -0
- data/README.md +181 -0
- data/Rakefile +37 -0
- data/Vagrantfile +18 -0
- data/assets/images/logo.png +0 -0
- data/example.rb +70 -0
- data/lib/core_ext/array.rb +5 -0
- data/lib/core_ext/hash.rb +11 -0
- data/lib/sshkit/all.rb +13 -0
- data/lib/sshkit/backends/abstract.rb +83 -0
- data/lib/sshkit/backends/netssh.rb +82 -0
- data/lib/sshkit/backends/printer.rb +28 -0
- data/lib/sshkit/command.rb +153 -0
- data/lib/sshkit/configuration.rb +29 -0
- data/lib/sshkit/connection_manager.rb +93 -0
- data/lib/sshkit/dsl.rb +15 -0
- data/lib/sshkit/host.rb +151 -0
- data/lib/sshkit/version.rb +3 -0
- data/lib/sshkit.rb +27 -0
- data/sshkit.gemspec +34 -0
- data/test/functional/test_connection_manager.rb +17 -0
- data/test/functional/test_ssh_server_comes_up_for_functional_tests.rb +23 -0
- data/test/helper.rb +125 -0
- data/test/integration/backends/test_netssh.rb +99 -0
- data/test/unit/backends/test_netssh.rb +14 -0
- data/test/unit/backends/test_printer.rb +62 -0
- data/test/unit/core_ext/test_string.rb +12 -0
- data/test/unit/test_command.rb +113 -0
- data/test/unit/test_configuration.rb +47 -0
- data/test/unit/test_connection_manager.rb +78 -0
- data/test/unit/test_host.rb +65 -0
- metadata +301 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module SSHKit
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
attr_writer :command_map
|
6
|
+
attr_accessor :output, :format, :runner, :backend
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@output = $stdout
|
10
|
+
@format = :dot
|
11
|
+
@runner = :parallel
|
12
|
+
@backend = SSHKit::Backend::Netssh
|
13
|
+
end
|
14
|
+
|
15
|
+
def command_map
|
16
|
+
@command_map ||= begin
|
17
|
+
Hash.new do |hash, command|
|
18
|
+
if %w{if test time}.include? command.to_s
|
19
|
+
hash[command] = command.to_s
|
20
|
+
else
|
21
|
+
hash[command] = "/usr/bin/env #{command}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
attr_reader :hosts, :block
|
6
|
+
|
7
|
+
def initialize(hosts, &block)
|
8
|
+
@hosts = Array(hosts)
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class ParallelRunner < Runner
|
15
|
+
def execute
|
16
|
+
threads = []
|
17
|
+
hosts.each do |host|
|
18
|
+
threads << Thread.new(host) do |h|
|
19
|
+
SSHKit.config.backend.new(host, &block).run
|
20
|
+
end
|
21
|
+
end
|
22
|
+
threads.map(&:join)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class SequentialRunner < Runner
|
27
|
+
attr_writer :wait_interval
|
28
|
+
def execute
|
29
|
+
hosts.each do |host|
|
30
|
+
SSHKit.config.backend.new(host, &block).run
|
31
|
+
sleep wait_interval
|
32
|
+
end
|
33
|
+
end
|
34
|
+
private
|
35
|
+
def wait_interval
|
36
|
+
@wait_interval ||= 2
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class GroupRunner < SequentialRunner
|
41
|
+
attr_writer :group_size
|
42
|
+
def execute
|
43
|
+
hosts.each_slice(group_size).collect do |group_hosts|
|
44
|
+
ParallelRunner.new(group_hosts, &block).execute
|
45
|
+
sleep wait_interval
|
46
|
+
end.flatten
|
47
|
+
end
|
48
|
+
private
|
49
|
+
def group_size
|
50
|
+
@group_size ||= 2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module SSHKit
|
55
|
+
|
56
|
+
NoValidHosts = Class.new(StandardError)
|
57
|
+
|
58
|
+
class ConnectionManager
|
59
|
+
|
60
|
+
attr_accessor :hosts
|
61
|
+
|
62
|
+
def initialize(raw_hosts)
|
63
|
+
@raw_hosts = Array(raw_hosts)
|
64
|
+
raise NoValidHosts unless Array(raw_hosts).any?
|
65
|
+
resolve_hosts!
|
66
|
+
end
|
67
|
+
|
68
|
+
def each(options={}, &block)
|
69
|
+
options = default_options.merge(options)
|
70
|
+
case options[:in]
|
71
|
+
when :parallel then ParallelRunner
|
72
|
+
when :sequence then SequentialRunner
|
73
|
+
when :groups then GroupRunner
|
74
|
+
else
|
75
|
+
raise RuntimeError, "Don't know how to handle run style #{options[:in].inspect}"
|
76
|
+
end.new(hosts, &block).execute
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_accessor :cooldown
|
82
|
+
|
83
|
+
def default_options
|
84
|
+
{ in: :parallel }
|
85
|
+
end
|
86
|
+
|
87
|
+
def resolve_hosts!
|
88
|
+
@hosts = @raw_hosts.collect { |rh| rh.is_a?(Host) ? rh : Host.new(rh) }.uniq
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/lib/sshkit/dsl.rb
ADDED
data/lib/sshkit/host.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module SSHKit
|
2
|
+
|
3
|
+
UnparsableHostStringError = Class.new(StandardError)
|
4
|
+
|
5
|
+
class Host
|
6
|
+
|
7
|
+
attr_reader :hostname, :port, :username
|
8
|
+
|
9
|
+
attr_accessor :password
|
10
|
+
|
11
|
+
def initialize(host_string)
|
12
|
+
|
13
|
+
suitable_parsers = [
|
14
|
+
SimpleHostParser,
|
15
|
+
HostWithPortParser,
|
16
|
+
IPv6HostWithPortParser,
|
17
|
+
HostWithUsernameParser,
|
18
|
+
HostWithUsernameAndPortParser
|
19
|
+
].select do |p|
|
20
|
+
p.suitable?(host_string)
|
21
|
+
end
|
22
|
+
|
23
|
+
if suitable_parsers.any?
|
24
|
+
suitable_parsers.first.tap do |parser|
|
25
|
+
@username, @hostname, @port = parser.new(host_string).attributes
|
26
|
+
end
|
27
|
+
else
|
28
|
+
raise UnparsableHostStringError, "Cannot parse host string #{host_string}"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def hash
|
34
|
+
username.hash ^ hostname.hash ^ port.hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def eql?(other_host)
|
38
|
+
other_host.hash == hash
|
39
|
+
end
|
40
|
+
alias :== :eql?
|
41
|
+
alias :equal? :eql?
|
42
|
+
|
43
|
+
def to_key
|
44
|
+
to_s.to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
sprintf("%s@%s:%d", username, hostname, port)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# @private
|
54
|
+
# :nodoc:
|
55
|
+
class SimpleHostParser
|
56
|
+
|
57
|
+
def self.suitable?(host_string)
|
58
|
+
!host_string.match /[:|@]/
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(host_string)
|
62
|
+
@host_string = host_string
|
63
|
+
end
|
64
|
+
|
65
|
+
def username
|
66
|
+
`whoami`.chomp
|
67
|
+
end
|
68
|
+
|
69
|
+
def port
|
70
|
+
22
|
71
|
+
end
|
72
|
+
|
73
|
+
def hostname
|
74
|
+
@host_string
|
75
|
+
end
|
76
|
+
|
77
|
+
def attributes
|
78
|
+
[username, hostname, port]
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# @private
|
84
|
+
# :nodoc:
|
85
|
+
class HostWithPortParser < SimpleHostParser
|
86
|
+
|
87
|
+
def self.suitable?(host_string)
|
88
|
+
!host_string.match /[@|\[|\]]/
|
89
|
+
end
|
90
|
+
|
91
|
+
def port
|
92
|
+
@host_string.split(':').last.to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
def hostname
|
96
|
+
@host_string.split(':').first
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# @private
|
102
|
+
# :nodoc:
|
103
|
+
class IPv6HostWithPortParser < SimpleHostParser
|
104
|
+
|
105
|
+
def self.suitable?(host_string)
|
106
|
+
host_string.match /[a-fA-F0-9:]+:\d+/
|
107
|
+
end
|
108
|
+
|
109
|
+
def port
|
110
|
+
@host_string.split(':').last.to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
def hostname
|
114
|
+
@host_string.gsub!(/\[|\]/, '')
|
115
|
+
@host_string.split(':')[0..-2].join(':')
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
# @private
|
121
|
+
# :nodoc:
|
122
|
+
class HostWithUsernameParser < SimpleHostParser
|
123
|
+
def self.suitable?(host_string)
|
124
|
+
host_string.match(/@/) && !host_string.match(/\:/)
|
125
|
+
end
|
126
|
+
def username
|
127
|
+
@host_string.split('@').first
|
128
|
+
end
|
129
|
+
def hostname
|
130
|
+
@host_string.split('@').last
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# @private
|
135
|
+
# :nodoc:
|
136
|
+
class HostWithUsernameAndPortParser < SimpleHostParser
|
137
|
+
def self.suitable?(host_string)
|
138
|
+
host_string.match /@.*:\d+/
|
139
|
+
end
|
140
|
+
def username
|
141
|
+
@host_string.split(/:|@/)[0]
|
142
|
+
end
|
143
|
+
def hostname
|
144
|
+
@host_string.split(/:|@/)[1]
|
145
|
+
end
|
146
|
+
def port
|
147
|
+
@host_string.split(/:|@/)[2].to_i
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/sshkit.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require_relative 'sshkit/all'
|
3
|
+
|
4
|
+
module SSHKit
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :config
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.capture_output(io, &block)
|
11
|
+
original_io = config.output
|
12
|
+
config.output = io
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
config.output = original_io
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
@@config ||= Configuration.new
|
20
|
+
yield config
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.config
|
24
|
+
@@config ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/sshkit.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sshkit/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
|
6
|
+
gem.authors = ["Lee Hambley", "Tom Clements"]
|
7
|
+
gem.email = ["lee.hambley@gmail.com", "seenmyfate@gmail.com"]
|
8
|
+
gem.summary = %q{SSHKit makes it easy to write structured, testable SSH commands in Ruby}
|
9
|
+
gem.description = %q{A comprehensive toolkit for remotely running commands in a structured manner on groups of servers.}
|
10
|
+
gem.homepage = "http://wacku.github.com/sshkit"
|
11
|
+
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- test/*`.split("\n")
|
15
|
+
gem.name = "sshkit"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = SSHKit::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency('net-ssh')
|
20
|
+
gem.add_dependency('term-ansicolor')
|
21
|
+
|
22
|
+
gem.add_development_dependency('minitest', ['>= 2.11.3', '< 2.12.0'])
|
23
|
+
gem.add_development_dependency('autotest')
|
24
|
+
gem.add_development_dependency('rake')
|
25
|
+
gem.add_development_dependency('turn')
|
26
|
+
gem.add_development_dependency('unindent')
|
27
|
+
gem.add_development_dependency('mocha')
|
28
|
+
gem.add_development_dependency('debugger')
|
29
|
+
gem.add_development_dependency('vagrant')
|
30
|
+
|
31
|
+
gem.add_development_dependency('yard')
|
32
|
+
gem.add_development_dependency('redcarpet')
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
|
5
|
+
class TestHost < FunctionalTest
|
6
|
+
|
7
|
+
def host
|
8
|
+
@_host ||= Host.new('')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_that_it_works
|
12
|
+
assert true
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_creating_a_user_gives_us_back_his_private_key_as_a_string
|
16
|
+
keys = create_user_with_key(:peter)
|
17
|
+
assert_equal [:one, :two, :three], keys.keys
|
18
|
+
assert keys.values.all?
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'tempfile'
|
11
|
+
require 'minitest/unit'
|
12
|
+
require 'mocha'
|
13
|
+
require 'turn'
|
14
|
+
require 'unindent'
|
15
|
+
require 'debugger'
|
16
|
+
require 'vagrant'
|
17
|
+
require 'stringio'
|
18
|
+
|
19
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
20
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
21
|
+
require 'sshkit'
|
22
|
+
|
23
|
+
module Vagrant
|
24
|
+
module Communication
|
25
|
+
class SSH
|
26
|
+
def download(from)
|
27
|
+
scp_connect do |scp|
|
28
|
+
return StringIO.new.tap do |sio|
|
29
|
+
scp.download!(from, sio)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
rescue RuntimeError => e
|
33
|
+
raise if e.message !~ /Permission denied/
|
34
|
+
raise Vagrant::Errors::SCPPermissionDenied, :path => from.to_s
|
35
|
+
end
|
36
|
+
private
|
37
|
+
def scp_connect
|
38
|
+
connect do |connection|
|
39
|
+
scp = Net::SCP.new(connection)
|
40
|
+
return yield scp
|
41
|
+
end
|
42
|
+
rescue Net::SCP::Error => e
|
43
|
+
raise Vagrant::Errors::SCPUnavailable if e.message =~ /\(127\)/
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class UnitTest < MiniTest::Unit::TestCase
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class FunctionalTest < MiniTest::Unit::TestCase
|
55
|
+
|
56
|
+
attr_accessor :venv
|
57
|
+
|
58
|
+
def setup
|
59
|
+
@venv = Vagrant::Environment.new
|
60
|
+
venv.vms.each do |name, vm|
|
61
|
+
warn "#{name} (of #{venv.vms.size}) needs to be booted, please wait" unless vm.state == :running
|
62
|
+
end
|
63
|
+
venv.cli "up"
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def vm_hosts
|
69
|
+
venv.vms.collect do |name, vm|
|
70
|
+
port = vm.env.config.for_vm(name).vm.forwarded_ports.first[:hostport]
|
71
|
+
SSHKit::Host.new("vagrant@localhost:#{port}").tap do |h|
|
72
|
+
h.password = 'vagrant'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_user_with_key(username, password = :secret)
|
78
|
+
username, password = username.to_s, password.to_s
|
79
|
+
keys = venv.vms.collect do |hostname, vm|
|
80
|
+
|
81
|
+
# Remove the user, make it again, force-generate a key for him
|
82
|
+
# short keys save us a few microseconds
|
83
|
+
vm.channel.sudo("userdel -rf #{username}; true") # The `rescue nil` of the shell world
|
84
|
+
vm.channel.sudo("useradd -m #{username}")
|
85
|
+
vm.channel.sudo("echo y | ssh-keygen -b 1024 -f #{username} -N ''")
|
86
|
+
vm.channel.sudo("chown vagrant:vagrant #{username}*")
|
87
|
+
vm.channel.sudo("echo #{username}:#{password} | chpasswd")
|
88
|
+
|
89
|
+
# Make the .ssh directory, change the ownership and the
|
90
|
+
vm.channel.sudo("mkdir -p /home/#{username}/.ssh")
|
91
|
+
vm.channel.sudo("chown #{username}:#{username} /home/#{username}/.ssh")
|
92
|
+
vm.channel.sudo("chmod 700 /home/#{username}/.ssh")
|
93
|
+
|
94
|
+
# Move the key to authorized keys and chown and chmod it
|
95
|
+
vm.channel.sudo("cat #{username}.pub > /home/#{username}/.ssh/authorized_keys")
|
96
|
+
vm.channel.sudo("chown #{username}:#{username} /home/#{username}/.ssh/authorized_keys")
|
97
|
+
vm.channel.sudo("chmod 600 /home/#{username}/.ssh/authorized_keys")
|
98
|
+
|
99
|
+
sio = vm.channel.download("/home/vagrant/#{username}")
|
100
|
+
|
101
|
+
# Clean Up Files
|
102
|
+
vm.channel.sudo("rm #{username} #{username}.pub")
|
103
|
+
|
104
|
+
# Rewind the stringio, and read it as a string
|
105
|
+
sio.tap { |s| s.rewind }.read
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
Hash[venv.vms.collect { |n,vm| n }.zip(keys)]
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
class IntegrationTest < MiniTest::Unit::TestCase
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Force colours in Autotest
|
121
|
+
#
|
122
|
+
Turn.config.ansi = true
|
123
|
+
Turn.config.format = :pretty
|
124
|
+
|
125
|
+
MiniTest::Unit.autorun
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
|
5
|
+
module Backend
|
6
|
+
|
7
|
+
class ToSIoFormatter < StringIO
|
8
|
+
extend Forwardable
|
9
|
+
attr_reader :original_output
|
10
|
+
def_delegators :@original_output, :read, :rewind
|
11
|
+
def initialize(oio)
|
12
|
+
@original_output = oio
|
13
|
+
end
|
14
|
+
def write(obj)
|
15
|
+
warn "What: #{obj.to_hash}"
|
16
|
+
original_output.write "> Executing #{obj}\n"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TestPrinter < FunctionalTest
|
21
|
+
|
22
|
+
def block_to_run
|
23
|
+
lambda do |host|
|
24
|
+
execute 'date'
|
25
|
+
execute :ls, '-l', '/some/directory'
|
26
|
+
with rails_env: :production do
|
27
|
+
within '/tmp' do
|
28
|
+
as :root do
|
29
|
+
execute :touch, 'restart.txt'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def a_host
|
37
|
+
vm_hosts.first
|
38
|
+
end
|
39
|
+
|
40
|
+
def printer
|
41
|
+
Netssh.new(a_host, &block_to_run)
|
42
|
+
end
|
43
|
+
|
44
|
+
def simple_netssh
|
45
|
+
sio = ToSIoFormatter.new(StringIO.new)
|
46
|
+
SSHKit.capture_output(sio) do
|
47
|
+
printer.run
|
48
|
+
end
|
49
|
+
sio.rewind
|
50
|
+
result = sio.read
|
51
|
+
assert_equal <<-EOEXPECTED.unindent, result
|
52
|
+
> Executing if test ! -d /opt/sites/example.com; then echo "Directory does not exist '/opt/sites/example.com'" 2>&1; false; fi
|
53
|
+
> Executing cd /opt/sites/example.com && /usr/bin/env date
|
54
|
+
> Executing cd /opt/sites/example.com && /usr/bin/env ls -l /some/directory
|
55
|
+
> Executing if test ! -d /opt/sites/example.com/tmp; then echo "Directory does not exist '/opt/sites/example.com/tmp'" 2>&1; false; fi
|
56
|
+
> Executing if ! sudo su -u root whoami > /dev/null; then echo "You cannot switch to user 'root' using sudo, please check the sudoers file" 2>&1; false; fi
|
57
|
+
> Executing cd /opt/sites/example.com/tmp && ( RAILS_ENV=production ( sudo su -u root /usr/bin/env touch restart.txt ) )
|
58
|
+
EOEXPECTED
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_capture
|
62
|
+
File.open('/dev/null', 'w') do |dnull|
|
63
|
+
SSHKit.capture_output(dnull) do
|
64
|
+
captured_command_result = ""
|
65
|
+
Netssh.new(a_host) do |host|
|
66
|
+
captured_command_result = capture(:hostname)
|
67
|
+
end.run
|
68
|
+
assert_equal "lucid32", captured_command_result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_exit_status
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_raising_an_error_if_a_command_returns_a_bad_exit_status
|
78
|
+
skip "Where to implement this?"
|
79
|
+
# NOTE: I think that it might be wise to have Command raise when
|
80
|
+
# Command#exit_status=() is called, it would allow an option to
|
81
|
+
# be passed to command (raise_errors: false), which could also be
|
82
|
+
# inherited through Backend#command and specified on the (not yet
|
83
|
+
# existing) backend configurations.
|
84
|
+
assert_raises RuntimeError do
|
85
|
+
File.open('/dev/null', 'w') do |dnull|
|
86
|
+
SSHKit.capture_output(dnull) do
|
87
|
+
Netssh.new(a_host) do |host|
|
88
|
+
execute :false
|
89
|
+
end.run
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
module Backend
|
5
|
+
class TestNetssh < UnitTest
|
6
|
+
def backend
|
7
|
+
Netssh.new(Host.new('example.com'), Proc.new)
|
8
|
+
end
|
9
|
+
def test_net_ssh_configuration_timeout
|
10
|
+
skip "No configuration on the Netssh class yet"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
|
5
|
+
module Backend
|
6
|
+
|
7
|
+
class ToSIoFormatter < StringIO
|
8
|
+
extend Forwardable
|
9
|
+
attr_reader :original_output
|
10
|
+
def_delegators :@original_output, :read, :rewind
|
11
|
+
def initialize(oio)
|
12
|
+
@original_output = oio
|
13
|
+
end
|
14
|
+
def write(obj)
|
15
|
+
original_output.write "> Executing #{obj}\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TestPrinter < UnitTest
|
20
|
+
|
21
|
+
def block_to_run
|
22
|
+
lambda do |host|
|
23
|
+
within '/opt/sites/example.com' do
|
24
|
+
execute 'date'
|
25
|
+
execute :ls, '-l', '/some/directory'
|
26
|
+
with rails_env: :production do
|
27
|
+
within :tmp do
|
28
|
+
as :root do
|
29
|
+
execute :touch, 'restart.txt'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def printer
|
38
|
+
Printer.new(Host.new(:'example.com'), &block_to_run)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_simple_printing
|
42
|
+
sio = ToSIoFormatter.new(StringIO.new)
|
43
|
+
SSHKit.capture_output(sio) do
|
44
|
+
printer.run
|
45
|
+
end
|
46
|
+
sio.rewind
|
47
|
+
result = sio.read
|
48
|
+
assert_equal <<-EOEXPECTED.unindent, result
|
49
|
+
> Executing if test ! -d /opt/sites/example.com; then echo "Directory does not exist '/opt/sites/example.com'" 1>&2; false; fi
|
50
|
+
> Executing cd /opt/sites/example.com && /usr/bin/env date
|
51
|
+
> Executing cd /opt/sites/example.com && /usr/bin/env ls -l /some/directory
|
52
|
+
> Executing if test ! -d /opt/sites/example.com/tmp; then echo "Directory does not exist '/opt/sites/example.com/tmp'" 1>&2; false; fi
|
53
|
+
> Executing if ! sudo su root -c whoami > /dev/null; then echo "You cannot switch to user 'root' using sudo, please check the sudoers file" 1>&2; false; fi
|
54
|
+
> Executing cd /opt/sites/example.com/tmp && ( RAILS_ENV=production sudo su root -c /usr/bin/env touch restart.txt )
|
55
|
+
EOEXPECTED
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|