sshkit 0.0.1
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/.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
|