gofer 0.2.6 → 0.3.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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWQwYzJjNDA5ZGZiOGQ3MDRkYWFhYmRhNGQ4MmZlNmM5OGU5MmY0OQ==
5
+ data.tar.gz: !binary |-
6
+ MTcwYTIwZDJjZWZlOGZhYWI5OTZjOWQ4NzUzM2NmYTBhNTU0YjhiMA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NmM0OTY3MjQ4ZDZjYjkxZDNlY2M0OTdlMzMyNmFkODA4N2Q5OTkzMzUyNGQ2
10
+ OWNiYjNkOTNjN2NlMDg2MDNiYTUwODEyMjUyYTFlNDg3OGNiNDE1YjI4NTNh
11
+ ZTJkMDlmMzcyMmMzZDI0NGEyZGQ3ZTc2Yzg3NzA4MTFkYjIyOGM=
12
+ data.tar.gz: !binary |-
13
+ ZDE5ODhmYmM5NzEwZjg4MjA5YzRmYTE5ZTcxMzgzN2NmYjRkYjExMTBmMWQ1
14
+ NzgwYjc4OTBhYjM2YTBhZjk3ODRhYzExMzFiMzU0NDYwYTUwNGEwYzcwNDcy
15
+ NGNmODY4NDY4MzY0YWIxNGI2YTNhMzFiZTFiYzE5N2Y1OTBhNDI=
data/.gitignore CHANGED
@@ -4,3 +4,4 @@
4
4
  Gemfile.lock
5
5
  pkg/*
6
6
  doc
7
+ .ruby-version
data/README.md CHANGED
@@ -14,82 +14,118 @@
14
14
 
15
15
  ### Instantiation
16
16
 
17
- h = Gofer::Host.new('my.host.com', 'ubuntu', :keys => ['key.pem'])
17
+ ``` ruby
18
+ h = Gofer::Host.new('my.host.com', 'ubuntu', :keys => ['key.pem'])
19
+ ```
18
20
 
19
21
  ### Run a command
20
22
 
21
- h.run "sudo stop mysqld"
23
+ ``` ruby
24
+ h.run "sudo stop mysqld"
25
+ ```
22
26
 
23
27
  ### Copy some files
24
28
 
25
- h.upload 'file', 'remote_file'
26
- h.download 'remote_dir', 'dir'
29
+ ``` ruby
30
+ h.upload 'file', 'remote_file'
31
+ h.download 'remote_dir', 'dir'
32
+ ```
27
33
 
28
34
  ### Interact with the filesystem
29
35
 
30
- if h.exist?('remote_directory')
31
- h.run "rm -rf 'remote_directory'"
32
- end
36
+ ``` ruby
37
+ if h.exist?('remote_directory')
38
+ h.run "rm -rf 'remote_directory'"
39
+ end
40
+
41
+ h.write("a string buffer", 'a_remote_file')
42
+ puts h.read('a_remote_file')
43
+ puts h.ls('a_remote_dir').join(", ")
44
+ ```
33
45
 
34
- h.write("a string buffer", 'a_remote_file')
35
- puts h.read('a_remote_file')
36
- puts h.ls('a_remote_dir').join(", ")
37
-
38
46
  ### Respond to command errors
39
47
 
40
- h.run "false" # this will raise an error
41
- response = h.run "false", :capture_exit_status => true # this won't ...
42
- puts response.exit_status # and will make the exit status available
48
+ ``` ruby
49
+ h.run "false" # this will raise an error
50
+ response = h.run "false", :capture_exit_status => true # this won't ...
51
+ puts response.exit_status # and will make the exit status available
52
+ ```
43
53
 
44
54
  ### Capture output
45
55
 
46
- response = h.run "echo hello; echo goodbye 1>&2\n"
47
- puts response # will print "hello\n"
48
- puts response.stdout # will also print "hello\n"
49
- puts response.stderr # will print "goodbye\n"
50
- puts response.output # will print "hello\ngoodbye\n"
56
+ ``` ruby
57
+ response = h.run "echo hello; echo goodbye 1>&2\n"
58
+ puts response # will print "hello\n"
59
+ puts response.stdout # will also print "hello\n"
60
+ puts response.stderr # will print "goodbye\n"
61
+ puts response.output # will print "hello\ngoodbye\n"
62
+ ```
51
63
 
52
64
  ### Suppress output
53
65
 
54
- h.run "echo noisy", :quiet => true # don't print stdout
55
- h.run "echo noisier 1>&2", :quiet_stderr => true # don't print stderr
56
- h.quiet = true # never print stdout
66
+ ``` ruby
67
+ h.run "echo noisy", :quiet => true # don't print stdout
68
+ h.run "echo noisier 1>&2", :quiet_stderr => true # don't print stderr
69
+ h.quiet = true # never print stdout
70
+ ```
57
71
 
58
72
  ### Run multiple commands
59
-
60
- response = h.run_multiple(['echo hello', 'echo goodbye'], :quiet => true)
61
- puts response.stdout # will print "hello\ngoodbye\n"
62
-
73
+
74
+ ``` ruby
75
+ response = h.run_multiple(['echo hello', 'echo goodbye'], :quiet => true)
76
+ puts response.stdout # will print "hello\ngoodbye\n"
77
+ ```
78
+
79
+ ### Run the same commands on multiple hosts
80
+
81
+ ``` ruby
82
+ cluster = Gopher::Cluster.new
83
+ cluster << Gofer::Host.new('my.host.com', 'ubuntu', :keys => ['key.pem'])
84
+ cluster << Gofer::Host.new('other.host.com', 'ubuntu', :keys => ['key.pem'])
85
+
86
+ cluster.run do |c|
87
+ c.run("hostname") # This will run on both hosts at once
88
+ end
89
+
90
+ cluster.run(:max_concurrency => 1) do |c|
91
+ c.run("sudo /etc/init.d/apache2 restart") # This will run on one machine at a time
92
+ end
93
+ ```
94
+
63
95
  ## Planned Features
64
96
 
65
- # constant connection (no reconnect for each action)
66
- Gofer::Host.new(...).open do |h|
67
- h.run( ... )
68
- end
69
-
70
- # overriding defaults
71
- h.set :quiet => true
72
- h.set :capture_exit_status => false
73
-
74
- # Local system usage, too:
75
- Gofer::Localhost.new.run "hostname" # > my.macbook.com
97
+ ``` ruby
98
+ # constant connection (no reconnect for each action)
99
+ Gofer::Host.new(...).open do |h|
100
+ h.run( ... )
101
+ end
102
+
103
+ # overriding defaults
104
+ h.set :quiet => true
105
+ h.set :capture_exit_status => false
106
+
107
+ # Local system usage, too:
108
+ Gofer::Localhost.new.run "hostname" # > my.macbook.com
109
+ ```
76
110
 
77
111
  ## Testing
78
-
112
+
79
113
  * Ensure that your user can ssh as itself to localhost using the key in `~/.ssh/id_rsa`.
80
114
  * Run `rspec spec` or `bundle install && rake spec`
81
115
 
82
116
  ## TODO
83
-
84
- * ls, exists?, directory? should use sftp if available rather than shell commands
85
- * wrap STDOUT with host prefix for easy identification of system output
86
- * Deal with timeouts/disconnects on persistent connections
117
+
118
+ * ls, exists?, directory? should use sftp if available rather than shell commands
119
+ * wrap STDOUT with host prefix for easy identification of system output
120
+ * Deal with timeouts/disconnects on persistent connections
121
+ * CHANGELOG.md
122
+ * Release 1.0 & use Semver
87
123
 
88
124
  ## License
89
125
 
90
126
  (The MIT License)
91
127
 
92
- Copyright (c) 2011 Michael Pearson
128
+ Copyright (c) 2011-13 Michael Pearson
93
129
 
94
130
  Permission is hereby granted, free of charge, to any person obtaining
95
131
  a copy of this software and associated documentation files (the
@@ -1,8 +1,9 @@
1
1
  require 'gofer/ssh_wrapper'
2
2
  require 'gofer/response'
3
3
  require 'gofer/host'
4
+ require 'gofer/cluster'
4
5
  require 'gofer/version'
5
6
 
6
7
  # See Gofer::Host
7
8
  module Gofer
8
- end
9
+ end
@@ -0,0 +1,73 @@
1
+ require 'thread'
2
+
3
+ module Gofer
4
+ class Cluster
5
+ attr_reader :hosts
6
+ attr_accessor :max_concurrency
7
+ def initialize(parties=[])
8
+ @hosts = []
9
+ @max_concurrency = nil
10
+
11
+ parties.each do |i|
12
+ self << i
13
+ end
14
+ end
15
+
16
+ def <<(other)
17
+ case other
18
+ when Cluster
19
+ other.hosts.each do |host|
20
+ @hosts << host
21
+ end
22
+ when Host
23
+ @hosts << other
24
+ end
25
+ end
26
+
27
+ def run(opts={}, &block)
28
+ concurrency = opts[:max_concurrency] || max_concurrency || hosts.length
29
+ block.call(ClusterCommandRunner.new(hosts, concurrency))
30
+ end
31
+
32
+ class ClusterCommandRunner
33
+ def initialize(hosts, concurrency)
34
+ @concurrency = concurrency
35
+ @hosts = hosts
36
+ end
37
+
38
+ # Spawn +concurrency+ worker threads, each of which pops work off the
39
+ # +_in+ queue, and writes values to the +_out+ queue for syncronisation.
40
+ def run(cmd, opts={})
41
+ _in = run_queue
42
+ length = run_queue.length
43
+ _out = Queue.new
44
+ results = {}
45
+ (0...@concurrency).map do
46
+ Thread.new do
47
+ loop do
48
+ host = _in.pop(false) rescue Thread.exit
49
+
50
+ results[host] = host.run(cmd, opts)
51
+ _out << true
52
+ end
53
+ end
54
+ end
55
+
56
+ length.times do
57
+ _out.pop
58
+ end
59
+
60
+ return results
61
+ end
62
+
63
+ def run_queue
64
+ Queue.new.tap do |q|
65
+ @hosts.each do |h|
66
+ q << h
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -1,9 +1,9 @@
1
1
  module Gofer
2
2
 
3
- # Response container for the various outputs from Gofer::Host#run
3
+ # Response container for the various outputs from Gofer::Host#run
4
4
  class Response < String
5
5
  attr_reader :stdout, :stderr, :output, :exit_status
6
-
6
+
7
7
  def initialize (_stdout, _stderr, _output, _exit_status)
8
8
  super _stdout
9
9
  @stdout = _stdout
@@ -12,4 +12,4 @@ module Gofer
12
12
  @exit_status = _exit_status
13
13
  end
14
14
  end
15
- end
15
+ end
@@ -13,7 +13,7 @@ module Gofer
13
13
  def run command, opts={}
14
14
  ssh_execute(ssh, command, opts)
15
15
  end
16
-
16
+
17
17
  def read_file path
18
18
  scp.download! path
19
19
  end
@@ -27,12 +27,12 @@ module Gofer
27
27
  end
28
28
 
29
29
  private
30
-
30
+
31
31
  def ssh
32
32
  @ssh ||= Net::SSH.start(*@net_ssh_args)
33
33
  end
34
-
35
- def scp
34
+
35
+ def scp
36
36
  @scp ||= Net::SCP.new(ssh)
37
37
  end
38
38
 
@@ -40,7 +40,7 @@ module Gofer
40
40
  stdout, stderr, output = '', '', ''
41
41
  exit_code = 0
42
42
  ssh.open_channel do |channel|
43
-
43
+
44
44
  channel.exec(command) do |ch, success|
45
45
  unless success
46
46
  raise "Couldn't execute command #{command} (ssh channel failure)"
@@ -63,7 +63,7 @@ module Gofer
63
63
  exit_code = data.read_long
64
64
  channel.close # Necessary or backgrounded processes will 'hang' the channel
65
65
  end
66
-
66
+
67
67
  end
68
68
  end
69
69
 
@@ -1,3 +1,3 @@
1
1
  module Gofer # :nodoc:
2
- VERSION = "0.2.6"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -202,4 +202,44 @@ describe Gofer do
202
202
  end
203
203
  end
204
204
  end
205
+
206
+ describe :cluster do
207
+ it "should run commands in parallel" do
208
+ cluster = Gofer::Cluster.new
209
+ # Cheat and use the same host repeatedly
210
+ host1 = Gofer::Host.new(HOSTNAME, USERNAME, :keys => [IDENTITY_FILE], :quiet => true)
211
+ host2 = Gofer::Host.new(HOSTNAME, USERNAME, :keys => [IDENTITY_FILE], :quiet => true)
212
+ cluster << host1
213
+ cluster << host2
214
+
215
+ results = cluster.run do |c|
216
+ c.run "date '+%s%N'; sleep 1; date '+%s%N'"
217
+ end
218
+
219
+ res1 = results[host1].stdout.lines.to_a
220
+ res2 = results[host2].stdout.lines.to_a
221
+
222
+ expect(res1[1].to_f).to be > res2[0].to_f
223
+
224
+ end
225
+
226
+ it "should respect max_concurrency" do
227
+ cluster = Gofer::Cluster.new
228
+ # Cheat and use the same host repeatedly
229
+ host1 = Gofer::Host.new(HOSTNAME, USERNAME, :keys => [IDENTITY_FILE], :quiet => true)
230
+ host2 = Gofer::Host.new(HOSTNAME, USERNAME, :keys => [IDENTITY_FILE], :quiet => true)
231
+ cluster << host1
232
+ cluster << host2
233
+
234
+ results = cluster.run(:max_concurrency => 1) do |c|
235
+ c.run "date '+%s%N'; sleep 1; date '+%s%N'"
236
+ end
237
+
238
+ res1 = results[host1].stdout.lines.to_a
239
+ res2 = results[host2].stdout.lines.to_a
240
+
241
+ expect(res2[0].to_f).to be >= res1[1].to_f
242
+
243
+ end
244
+ end
205
245
  end
metadata CHANGED
@@ -1,68 +1,56 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: gofer
3
- version: !ruby/object:Gem::Version
4
- hash: 27
5
- prerelease:
6
- segments:
7
- - 0
8
- - 2
9
- - 6
10
- version: 0.2.6
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Michael Pearson
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2012-08-29 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
11
+ date: 2013-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
21
14
  name: net-ssh
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 33
29
- segments:
30
- - 2
31
- - 0
32
- - 23
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
33
19
  version: 2.0.23
34
20
  type: :runtime
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: net-scp
38
21
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- hash: 31
45
- segments:
46
- - 1
47
- - 0
48
- - 4
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.23
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-scp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
49
33
  version: 1.0.4
50
34
  type: :runtime
51
- version_requirements: *id002
52
- description: |
53
-
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.4
41
+ description: ! '
42
+
54
43
  Gofer provides a flexible and reliable model for performing tasks on remote
44
+
55
45
  server using Net::SSH
56
46
 
57
- email:
47
+ '
48
+ email:
58
49
  - mipearson@gmail.com
59
50
  executables: []
60
-
61
51
  extensions: []
62
-
63
52
  extra_rdoc_files: []
64
-
65
- files:
53
+ files:
66
54
  - .gitignore
67
55
  - Gemfile
68
56
  - HISTORY.md
@@ -70,6 +58,7 @@ files:
70
58
  - Rakefile
71
59
  - gofer.gemspec
72
60
  - lib/gofer.rb
61
+ - lib/gofer/cluster.rb
73
62
  - lib/gofer/host.rb
74
63
  - lib/gofer/response.rb
75
64
  - lib/gofer/ssh_wrapper.rb
@@ -78,37 +67,27 @@ files:
78
67
  - spec/spec_helper.rb
79
68
  homepage: https://github.com/mipearson/gofer
80
69
  licenses: []
81
-
70
+ metadata: {}
82
71
  post_install_message:
83
72
  rdoc_options: []
84
-
85
- require_paths:
73
+ require_paths:
86
74
  - lib
87
- required_ruby_version: !ruby/object:Gem::Requirement
88
- none: false
89
- requirements:
90
- - - ">="
91
- - !ruby/object:Gem::Version
92
- hash: 3
93
- segments:
94
- - 0
95
- version: "0"
96
- required_rubygems_version: !ruby/object:Gem::Requirement
97
- none: false
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- hash: 3
102
- segments:
103
- - 0
104
- version: "0"
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
105
85
  requirements: []
106
-
107
86
  rubyforge_project:
108
- rubygems_version: 1.8.17
87
+ rubygems_version: 2.0.3
109
88
  signing_key:
110
- specification_version: 3
89
+ specification_version: 4
111
90
  summary: run commands on remote servers using SSH
112
- test_files:
91
+ test_files:
113
92
  - spec/gofer/integration_spec.rb
114
93
  - spec/spec_helper.rb