gofer 0.0.1 → 0.1.0
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/HISTORY.md +10 -0
- data/README.md +43 -48
- data/lib/gofer.rb +1 -0
- data/lib/gofer/host.rb +10 -18
- data/lib/gofer/response.rb +13 -0
- data/lib/gofer/ssh_wrapper.rb +8 -6
- data/lib/gofer/version.rb +1 -1
- data/spec/gofer/integration_spec.rb +22 -23
- metadata +9 -31
- data/lib/gofer/options.rb +0 -37
data/HISTORY.md
ADDED
data/README.md
CHANGED
@@ -5,73 +5,67 @@
|
|
5
5
|
**Gofer** has been written to support the needs of system automation scripts. As such, **gofer** will:
|
6
6
|
|
7
7
|
* automatically raise an error if a command returns a non-zero exit status
|
8
|
-
* print and capture STDOUT automatically
|
9
|
-
*
|
10
|
-
* override the above: return non-zero exit status instead of raising an error,
|
8
|
+
* print and capture STDOUT and STDERR automatically
|
9
|
+
* allow you to access captured STDOUT and STDERR individually or as a combined string
|
10
|
+
* override the above: return non-zero exit status instead of raising an error, suppress output
|
11
11
|
|
12
12
|
## Examples
|
13
13
|
|
14
|
-
|
15
|
-
Gofer::Host.new('ubuntu', 'my.host.com', :identity_file => 'key.pem').within do
|
16
|
-
# Basic usage
|
17
|
-
run "sudo stop mysqld"
|
14
|
+
### Instantiation
|
18
15
|
|
19
|
-
|
20
|
-
upload 'file' 'remote_file'
|
21
|
-
download 'remote_dir', 'dir'
|
16
|
+
h = Gofer::Host.new('ubuntu', 'my.host.com', :identity_file => 'key.pem')
|
22
17
|
|
23
|
-
|
24
|
-
if exists?('remote_directory')
|
25
|
-
run "rm -rf 'remote_directory'"
|
26
|
-
end
|
18
|
+
### Run a command
|
27
19
|
|
28
|
-
|
29
|
-
puts read('a_remote_file')
|
30
|
-
puts ls('a_remote_dir').join(", ")
|
20
|
+
h.run "sudo stop mysqld"
|
31
21
|
|
32
|
-
|
33
|
-
run "false" # this will fail
|
34
|
-
run "false", :capture_exit_status => true # this won't ...
|
35
|
-
puts last_exit_status # and will make the exit status available
|
22
|
+
### Copy some files
|
36
23
|
|
37
|
-
|
38
|
-
|
39
|
-
puts hello # will print "hello\n"
|
24
|
+
h.upload 'file', 'remote_file'
|
25
|
+
h.download 'remote_dir', 'dir'
|
40
26
|
|
41
|
-
|
42
|
-
# goodbye will be empty, as we don't capture stderr by default
|
43
|
-
|
44
|
-
goodbye = run "echo goodbye 1>&2", :capture_stderr => true # unless you ask for it
|
45
|
-
|
46
|
-
# output suppression
|
47
|
-
run "echo noisy", :quiet => true # don't output from our command
|
48
|
-
run "echo noisier 1>&2", :quiet_stderr => true # don't even output stderr!
|
27
|
+
### Interact with the filesystem
|
49
28
|
|
29
|
+
if h.exists?('remote_directory')
|
30
|
+
h.run "rm -rf 'remote_directory'"
|
50
31
|
end
|
51
32
|
|
52
|
-
|
53
|
-
h
|
54
|
-
|
55
|
-
|
56
|
-
|
33
|
+
puts h.read('a_remote_file')
|
34
|
+
puts h.ls('a_remote_dir').join(", ")
|
35
|
+
|
36
|
+
### Respond to command errors
|
37
|
+
|
38
|
+
h.run "false" # this will fail
|
39
|
+
response = h.run "false", :capture_exit_status => true # this won't ...
|
40
|
+
puts response.exit_status # and will make the exit status available
|
41
|
+
|
42
|
+
### Capture output
|
43
|
+
|
44
|
+
response = h.run "echo hello; echo goodbye 1>&2\n"
|
45
|
+
puts response # will print "hello\n"
|
46
|
+
puts response.stdout # will also print "hello\n"
|
47
|
+
puts response.stderr # will print "goodbye\n"
|
48
|
+
puts response.output # will print "hello\ngoodbye\n"
|
49
|
+
|
50
|
+
### Suppress output
|
51
|
+
|
52
|
+
h.run "echo noisy", :quiet => true # don't output from our command
|
53
|
+
h.run "echo noisier 1>&2", :quiet_stderr => true # don't even output stderr!
|
57
54
|
|
58
55
|
## Planned Features
|
59
56
|
|
60
|
-
write("a string buffer", 'a_remote_file')
|
57
|
+
h.write("a string buffer", 'a_remote_file')
|
61
58
|
# constant connection (no reconnect for each action)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
Gofer::Host.new(...).open do |h|
|
60
|
+
h.run( ... )
|
61
|
+
end
|
62
|
+
|
66
63
|
# overriding defaults
|
67
|
-
set :quiet => true
|
68
|
-
set :capture_exit_status => false
|
69
|
-
|
70
|
-
# Separate the command from the arguments, system() style
|
71
|
-
run "echo" "Some" "arguments" "with" "'quotes'" "in" "them"
|
64
|
+
h.set :quiet => true
|
65
|
+
h.set :capture_exit_status => false
|
72
66
|
|
73
67
|
# Local system usage, too:
|
74
|
-
run "hostname" # > my.macbook.com
|
68
|
+
Gofer::Localhost.new.run "hostname" # > my.macbook.com
|
75
69
|
|
76
70
|
## Testing
|
77
71
|
|
@@ -82,6 +76,7 @@
|
|
82
76
|
|
83
77
|
* ls, exists?, directory? should use sftp if available rather than shell commands
|
84
78
|
* wrap STDOUT with host prefix for easy identification of system output
|
79
|
+
* RDoc
|
85
80
|
|
86
81
|
## License
|
87
82
|
|
data/lib/gofer.rb
CHANGED
data/lib/gofer/host.rb
CHANGED
@@ -7,7 +7,7 @@ module Gofer
|
|
7
7
|
|
8
8
|
class Host
|
9
9
|
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :hostname
|
11
11
|
|
12
12
|
def initialize username, _hostname, identity_file=nil
|
13
13
|
@hostname = _hostname
|
@@ -15,18 +15,15 @@ module Gofer
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def run command, opts={}
|
18
|
-
@ssh.run command, opts
|
19
|
-
if opts[:capture_exit_status]
|
20
|
-
@last_exit_status = @ssh.last_exit_status
|
21
|
-
elsif @ssh.last_exit_status != 0
|
18
|
+
response = @ssh.run command, opts
|
19
|
+
if !opts[:capture_exit_status] && response.exit_status != 0
|
22
20
|
raise HostError.new(self, "Command #{command} failed with exit status #{@ssh.last_exit_status}")
|
23
21
|
end
|
24
|
-
|
22
|
+
response
|
25
23
|
end
|
26
24
|
|
27
25
|
def exists? path
|
28
|
-
@ssh.run
|
29
|
-
@ssh.last_exit_status == 0
|
26
|
+
@ssh.run("sh -c '[ -e #{path} ]'").exit_status == 0
|
30
27
|
end
|
31
28
|
|
32
29
|
def read path
|
@@ -34,16 +31,15 @@ module Gofer
|
|
34
31
|
end
|
35
32
|
|
36
33
|
def directory? path
|
37
|
-
@ssh.run
|
38
|
-
@ssh.last_exit_status == 0
|
34
|
+
@ssh.run("sh -c '[ -d #{path} ]'").exit_status == 0
|
39
35
|
end
|
40
36
|
|
41
37
|
def ls path
|
42
|
-
@ssh.run "ls -1 #{path}", :quiet => true
|
43
|
-
if
|
44
|
-
|
38
|
+
response = @ssh.run "ls -1 #{path}", :quiet => true
|
39
|
+
if response.exit_status == 0
|
40
|
+
response.stdout.strip.split("\n")
|
45
41
|
else
|
46
|
-
raise HostError.new(self, "Could not list #{path}, exit status #{
|
42
|
+
raise HostError.new(self, "Could not list #{path}, exit status #{response.exit_status}")
|
47
43
|
end
|
48
44
|
end
|
49
45
|
|
@@ -54,9 +50,5 @@ module Gofer
|
|
54
50
|
def download from, to
|
55
51
|
@ssh.download from, to, :recursive => directory?(from)
|
56
52
|
end
|
57
|
-
|
58
|
-
def within &block
|
59
|
-
instance_eval &block
|
60
|
-
end
|
61
53
|
end
|
62
54
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Gofer
|
2
|
+
class Response < String
|
3
|
+
attr_reader :stdout, :stderr, :output, :exit_status
|
4
|
+
|
5
|
+
def initialize (_stdout, _stderr, _output, _exit_status)
|
6
|
+
super _stdout
|
7
|
+
@stdout = _stdout
|
8
|
+
@stderr = _stderr
|
9
|
+
@output = _output
|
10
|
+
@exit_status = _exit_status
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/gofer/ssh_wrapper.rb
CHANGED
@@ -15,9 +15,11 @@ module Gofer
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def run command, opts={}
|
18
|
+
response = nil
|
18
19
|
Net::SSH.start(*net_ssh_credentials) do |ssh|
|
19
|
-
ssh_execute(ssh, command, opts)
|
20
|
+
response = ssh_execute(ssh, command, opts)
|
20
21
|
end
|
22
|
+
response
|
21
23
|
end
|
22
24
|
|
23
25
|
def read_file path
|
@@ -55,7 +57,7 @@ module Gofer
|
|
55
57
|
end
|
56
58
|
|
57
59
|
def ssh_execute(ssh, command, opts={})
|
58
|
-
output = ''
|
60
|
+
stdout, stderr, output = '', '', ''
|
59
61
|
exit_code = 0
|
60
62
|
ssh.open_channel do |channel|
|
61
63
|
channel.exec(command) do |ch, success|
|
@@ -64,13 +66,15 @@ module Gofer
|
|
64
66
|
end
|
65
67
|
|
66
68
|
channel.on_data do |ch, data| # stdout
|
69
|
+
stdout += data
|
67
70
|
output += data
|
68
71
|
$stdout.print data unless opts[:quiet]
|
69
72
|
end
|
70
73
|
|
71
74
|
channel.on_extended_data do |ch, type, data|
|
72
75
|
next unless type == 1 # only handle stderr
|
73
|
-
|
76
|
+
stderr += data
|
77
|
+
output += data
|
74
78
|
$stderr.print data unless opts[:quiet_stderr]
|
75
79
|
end
|
76
80
|
|
@@ -83,9 +87,7 @@ module Gofer
|
|
83
87
|
end
|
84
88
|
|
85
89
|
ssh.loop
|
86
|
-
|
87
|
-
@last_exit_status = exit_code
|
88
|
-
@last_output = output
|
90
|
+
Gofer::Response.new(stdout, stderr, output, exit_code)
|
89
91
|
end
|
90
92
|
end
|
91
93
|
end
|
data/lib/gofer/version.rb
CHANGED
@@ -49,19 +49,26 @@ describe Gofer do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
describe :run do
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
52
|
+
describe "with stdout and stderr responses" do
|
53
|
+
before :all do
|
54
|
+
@response = @host.run "echo stdout; echo stderr 1>&2", :quiet => true, :quiet_stderr => true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should capture stdout in @response.stdout" do
|
58
|
+
@response.stdout.should == "stdout\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should capture stderr in @response.stderr" do
|
62
|
+
@response.stderr.should == "stderr\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should combine captured stdout / stderr in @response.output" do
|
66
|
+
@response.output.should == "stdout\nstderr\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "@response by itself should be the captured stdout" do
|
70
|
+
@response.should == "stdout\n"
|
71
|
+
end
|
65
72
|
end
|
66
73
|
|
67
74
|
it "should error if a command returns a non-zero response" do
|
@@ -69,8 +76,8 @@ describe Gofer do
|
|
69
76
|
end
|
70
77
|
|
71
78
|
it "should capture a non-zero exit status if asked" do
|
72
|
-
@host.run "false", :capture_exit_status => true
|
73
|
-
|
79
|
+
response = @host.run "false", :capture_exit_status => true
|
80
|
+
response.exit_status.should == 1
|
74
81
|
end
|
75
82
|
end
|
76
83
|
|
@@ -154,12 +161,4 @@ describe Gofer do
|
|
154
161
|
end
|
155
162
|
end
|
156
163
|
end
|
157
|
-
|
158
|
-
describe :within do
|
159
|
-
it "should execute commands in the context of the host instance" do
|
160
|
-
@host.within do
|
161
|
-
run("echo sup", :quiet => true).should == "sup\n"
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
164
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gofer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 1
|
10
|
-
version: 0.0.1
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Michael Pearson
|
@@ -15,7 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-04-
|
13
|
+
date: 2011-04-20 00:00:00 +10:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
@@ -26,11 +21,6 @@ dependencies:
|
|
26
21
|
requirements:
|
27
22
|
- - ">="
|
28
23
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 33
|
30
|
-
segments:
|
31
|
-
- 2
|
32
|
-
- 0
|
33
|
-
- 23
|
34
24
|
version: 2.0.23
|
35
25
|
type: :runtime
|
36
26
|
version_requirements: *id001
|
@@ -42,19 +32,12 @@ dependencies:
|
|
42
32
|
requirements:
|
43
33
|
- - ">="
|
44
34
|
- !ruby/object:Gem::Version
|
45
|
-
hash: 31
|
46
|
-
segments:
|
47
|
-
- 1
|
48
|
-
- 0
|
49
|
-
- 4
|
50
35
|
version: 1.0.4
|
51
36
|
type: :runtime
|
52
37
|
version_requirements: *id002
|
53
|
-
description:
|
54
|
-
|
55
|
-
|
56
|
-
server using Net::SSH
|
57
|
-
|
38
|
+
description: "\n\
|
39
|
+
Gofer provides a flexible and reliable model for performing tasks on remote\n\
|
40
|
+
server using Net::SSH\n"
|
58
41
|
email:
|
59
42
|
- mipearson@gmail.com
|
60
43
|
executables: []
|
@@ -66,12 +49,13 @@ extra_rdoc_files: []
|
|
66
49
|
files:
|
67
50
|
- .gitignore
|
68
51
|
- Gemfile
|
52
|
+
- HISTORY.md
|
69
53
|
- README.md
|
70
54
|
- Rakefile
|
71
55
|
- gofer.gemspec
|
72
56
|
- lib/gofer.rb
|
73
57
|
- lib/gofer/host.rb
|
74
|
-
- lib/gofer/
|
58
|
+
- lib/gofer/response.rb
|
75
59
|
- lib/gofer/ssh_wrapper.rb
|
76
60
|
- lib/gofer/version.rb
|
77
61
|
- spec/gofer/integration_spec.rb
|
@@ -90,23 +74,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
74
|
requirements:
|
91
75
|
- - ">="
|
92
76
|
- !ruby/object:Gem::Version
|
93
|
-
hash: 3
|
94
|
-
segments:
|
95
|
-
- 0
|
96
77
|
version: "0"
|
97
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
79
|
none: false
|
99
80
|
requirements:
|
100
81
|
- - ">="
|
101
82
|
- !ruby/object:Gem::Version
|
102
|
-
hash: 3
|
103
|
-
segments:
|
104
|
-
- 0
|
105
83
|
version: "0"
|
106
84
|
requirements: []
|
107
85
|
|
108
86
|
rubyforge_project:
|
109
|
-
rubygems_version: 1.
|
87
|
+
rubygems_version: 1.6.2
|
110
88
|
signing_key:
|
111
89
|
specification_version: 3
|
112
90
|
summary: run commands on remote servers using SSH
|
data/lib/gofer/options.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# Unused, keeping for later use.
|
2
|
-
module Gofer
|
3
|
-
class Options
|
4
|
-
VALID_OPTIONS => %w{identity_file}
|
5
|
-
|
6
|
-
def valid_options
|
7
|
-
VALID_OPTIONS
|
8
|
-
end
|
9
|
-
|
10
|
-
def initialize
|
11
|
-
@options = {}
|
12
|
-
end
|
13
|
-
|
14
|
-
def merge_in opts={}
|
15
|
-
opts.each |k,v|
|
16
|
-
set k, v
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def set k, v
|
21
|
-
k = option_valid_check(k)
|
22
|
-
@options[k] = v
|
23
|
-
end
|
24
|
-
|
25
|
-
def get k
|
26
|
-
k = option_valid_check(k)
|
27
|
-
@options[k]
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def option_valid_check(k)
|
33
|
-
k = k.to_s
|
34
|
-
raise "Invalid option #{k}" unless valid_options.include?(k)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|