gofer 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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