gofer 0.5.0 → 0.6.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
+ ODJiNjE3ZGEwZmNhZmI4ZTE4ZDdjOWE5M2YzY2EyZmYxYmM3ODZmNQ==
5
+ data.tar.gz: !binary |-
6
+ Y2E3MzlkMTliYWNlM2E4ZGRlYjFmOTU0MWYwNjYzOTUyMmZlNDkyOQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDY1NWQ4Nzg4NGNjODFhOGU0MDk5M2NlODkyYzg2YWRhNGEyOWNkM2E2MDgz
10
+ MzQ5ZjE5MDI2MTk3Y2ZkZmRiMmFhYzc2YWNkOTE2NDY4ZGFlNWRhMjU2NzEx
11
+ ZWQ4YmZhN2ZkOGE5NzQ0YzEwMzkwNGU3OTQ4NWY4MzM2MDZhOTI=
12
+ data.tar.gz: !binary |-
13
+ NzBjNzdiMDQxYTZjOGJhZmIxNzAwNGY5NWMxYWM5Mjk2YTIyMGViZTQ1ZDFh
14
+ ZjQzZGRlZDdmYWNkYjc4ZDNiYmNlMDQ3NDEwZGMwZmE1NTU1Y2U2ZjU4ZThk
15
+ YWNjZjkyZjJlNDU3ODgwN2I3YzU0Nzg1ZDI4NmVmMjVjMjMzMGI=
@@ -1,5 +1,10 @@
1
1
  # Revision History
2
2
 
3
+ ### v0.6.0
4
+
5
+ * Add `Gofer::ClusterError` to encapsulate errors encountered during clustered runs
6
+ * Add support for `stdin` option on `Gofer::Host#run`
7
+
3
8
  ### v0.5.0
4
9
 
5
10
  * Deprecate legacy arguments in Gofer::Host.new
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Gofer!
2
2
 
3
- [![Code Climate](https://codeclimate.com/github/mipearson/gofer.png)](https://codeclimate.com/github/mipearson/gofer)
3
+ [![Code Climate](https://codeclimate.com/github/mipearson/gofer.png)](https://codeclimate.com/github/mipearson/gofer) [![Gem Version](https://badge.fury.io/rb/gofer.png)](http://badge.fury.io/rb/gofer) [![Dependency Status](https://gemnasium.com/mipearson/gofer.png)](https://gemnasium.com/mipearson/gofer)
4
+
4
5
 
5
6
  **Gofer** is a set of wrappers around the Net::SSH suite of tools to enable consistent access to remote systems.
6
7
 
@@ -66,6 +67,12 @@ puts response.stderr # will print "goodbye\n"
66
67
  puts response.output # will print "hello\ngoodbye\n"
67
68
  ```
68
69
 
70
+ ### Send input
71
+
72
+ ``` ruby
73
+ h.run "sed 's/foo/bar/' 1>&2\n", :stdin => "hello foo\n" # response will be "hello bar"
74
+ ```
75
+
69
76
  ### Prefix output
70
77
 
71
78
  ``` ruby
@@ -84,7 +91,7 @@ h.quiet = true # never print stdout
84
91
  ### Run the same commands on multiple hosts
85
92
 
86
93
  ``` ruby
87
- cluster = Gopher::Cluster.new
94
+ cluster = Gofer::Cluster.new
88
95
  cluster << Gofer::Host.new('my.host.com', 'ubuntu', :keys => ['key.pem'], :output_prefix => " my")
89
96
  cluster << Gofer::Host.new('other.host.com', 'ubuntu', :keys => ['key.pem'], :output_prefix => "other")
90
97
 
@@ -102,6 +109,17 @@ host.run("rake migrations")
102
109
  # Inspect the results from each host
103
110
  results = cluster.run "echo hostname"
104
111
  puts results.values.join(", ") # will print "my.host.com, other.host.com"
112
+
113
+ # Capture exceptions from each host
114
+ begin
115
+ cluster.run "rake deploy"
116
+ rescue Gofer::ClusterError => e
117
+ e.errors.each do |host, exception|
118
+ $stderr.puts "Failed on #{host} with #{exception}, rolling back ..."
119
+ host.run "rake rollback"
120
+ end
121
+ raise e
122
+ end
105
123
  ```
106
124
 
107
125
  ## Testing
@@ -3,6 +3,7 @@ require 'gofer/response'
3
3
  require 'gofer/host_error'
4
4
  require 'gofer/host'
5
5
  require 'gofer/cluster'
6
+ require 'gofer/cluster_error'
6
7
  require 'gofer/version'
7
8
 
8
9
  # See Gofer::Host or Gofer::Cluster
@@ -88,15 +88,23 @@ module Gofer
88
88
  # +_in+ queue, and writes values to the +_out+ queue for syncronisation.
89
89
  def threaded(meth, *args)
90
90
  _in = run_queue
91
- length = run_queue.length
91
+ length = _in.length
92
92
  _out = Queue.new
93
93
  results = {}
94
- (0...concurrency).map do
94
+ errors = {}
95
+ results_semaphore = Mutex.new
96
+ errors_semaphore = Mutex.new
97
+ concurrency.times do
95
98
  Thread.new do
96
99
  loop do
97
100
  host = _in.pop(false) rescue Thread.exit
98
101
 
99
- results[host] = host.send(meth, *args)
102
+ begin
103
+ result = host.send(meth, *args)
104
+ results_semaphore.synchronize { results[host] = result }
105
+ rescue Exception => e
106
+ errors_semaphore.synchronize { errors[host] = e }
107
+ end
100
108
  _out << true
101
109
  end
102
110
  end
@@ -106,7 +114,11 @@ module Gofer
106
114
  _out.pop
107
115
  end
108
116
 
109
- results
117
+ if errors.size > 0
118
+ raise Gofer::ClusterError.new(errors)
119
+ else
120
+ results
121
+ end
110
122
  end
111
123
 
112
124
  def run_queue
@@ -0,0 +1,13 @@
1
+ module Gofer
2
+ # Error(s) encountered performing a Gofer command on a cluster of hosts
3
+ class ClusterError < Exception
4
+
5
+ # Exception instances by host
6
+ attr_reader :errors
7
+
8
+ def initialize errors={}
9
+ @errors = errors
10
+ super errors.values.map(&:to_s).join(', ')
11
+ end
12
+ end
13
+ end
@@ -62,6 +62,7 @@ module Gofer
62
62
  # +quiet+:: Don't print +stdout+, can also be set with +quiet=+ on the instance
63
63
  # +quiet_stderr+:: Don't print +stderr+
64
64
  # +capture_exit_status+:: Don't raise an error on a non-zero exit status
65
+ # +stdin+:: Send a string on +stdin+ then close the file
65
66
  def run command, opts={}
66
67
  opts[:quiet] = quiet unless opts.include?(:quiet)
67
68
  opts[:output_prefix] = @output_prefix
@@ -65,6 +65,11 @@ module Gofer
65
65
  channel.close # Necessary or backgrounded processes will 'hang' the channel
66
66
  end
67
67
 
68
+ if opts[:stdin]
69
+ channel.send_data(opts[:stdin])
70
+ channel.eof!
71
+ end
72
+
68
73
  end
69
74
  end
70
75
 
@@ -1,3 +1,3 @@
1
1
  module Gofer # :nodoc:
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -15,7 +15,7 @@ describe Gofer::Cluster do
15
15
  after(:all) { clean_tmpdir }
16
16
 
17
17
  it "should run commands in parallel" do
18
- results = @cluster.run("ruby -e 'puts Time.now.to_f; sleep 0.1; puts Time.now.to_f'")
18
+ results = @cluster.run("bash -l -c \"ruby -e 'puts Time.now.to_f; sleep 0.1; puts Time.now.to_f'\"")
19
19
 
20
20
  res1 = results[@host1].stdout.lines.to_a
21
21
  res2 = results[@host2].stdout.lines.to_a
@@ -25,7 +25,7 @@ describe Gofer::Cluster do
25
25
 
26
26
  it "should respect max_concurrency" do
27
27
  @cluster.max_concurrency = 1
28
- results = @cluster.run("ruby -e 'puts Time.now.to_f; sleep 0.1; puts Time.now.to_f'")
28
+ results = @cluster.run("bash -l -c \"ruby -e 'puts Time.now.to_f; sleep 0.1; puts Time.now.to_f'\"")
29
29
 
30
30
  res1 = results[@host1].stdout.lines.to_a
31
31
  res2 = results[@host2].stdout.lines.to_a
@@ -33,6 +33,17 @@ describe Gofer::Cluster do
33
33
  expect(res2[0].to_f).to be >= res1[1].to_f
34
34
  end
35
35
 
36
+ it "should encapsulate errors in a Gofer::ClusterError container exception" do
37
+ expect { @cluster.run("false") }.to raise_error(Gofer::ClusterError)
38
+ begin
39
+ @cluster.run "false"
40
+ rescue Gofer::ClusterError => e
41
+ expect(e.errors.keys.length).to eq(2)
42
+ expect(e.errors[@host1]).to be_a(Gofer::HostError)
43
+ expect(e.errors[@host2]).to be_a(Gofer::HostError)
44
+ end
45
+ end
46
+
36
47
  # TODO: Make this a custom matcher?
37
48
  def results_should_eq expected, &block
38
49
  results = block.call
@@ -83,6 +83,10 @@ describe Gofer::Host do
83
83
  @combined.should eq "derp: foobar\nderp: baz\n"
84
84
  end
85
85
 
86
+ it "should process stdin when stdin is set" do
87
+ @host.run "sed 's/foo/baz/'", :stdin => "foobar", :quiet => false
88
+ @stdout.should eq "derp: bazbar\n"
89
+ end
86
90
  end
87
91
 
88
92
  it "should error if a command returns a non-zero response" do
metadata CHANGED
@@ -1,59 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gofer
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.5.0
4
+ version: 0.6.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Michael Pearson
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-06-07 00:00:00.000000000 Z
11
+ date: 2013-10-13 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: net-ssh
16
- version_requirements: !ruby/object:Gem::Requirement
15
+ requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
- - - ">="
17
+ - - ! '>='
19
18
  - !ruby/object:Gem::Version
20
19
  version: 2.0.23
21
- none: false
22
- requirement: !ruby/object:Gem::Requirement
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.0.23
27
- none: false
28
- prerelease: false
29
- type: :runtime
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: net-scp
32
- version_requirements: !ruby/object:Gem::Requirement
29
+ requirement: !ruby/object:Gem::Requirement
33
30
  requirements:
34
- - - ">="
31
+ - - ! '>='
35
32
  - !ruby/object:Gem::Version
36
33
  version: 1.0.4
37
- none: false
38
- requirement: !ruby/object:Gem::Requirement
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
39
37
  requirements:
40
- - - ">="
38
+ - - ! '>='
41
39
  - !ruby/object:Gem::Version
42
40
  version: 1.0.4
43
- none: false
44
- prerelease: false
45
- type: :runtime
46
- description: |2
41
+ description: ! '
47
42
 
48
43
  Gofer provides a flexible and reliable model for performing tasks on remote
44
+
49
45
  server using Net::SSH
46
+
47
+ '
50
48
  email:
51
49
  - mipearson@gmail.com
52
50
  executables: []
53
51
  extensions: []
54
52
  extra_rdoc_files: []
55
53
  files:
56
- - ".gitignore"
54
+ - .gitignore
57
55
  - CHANGELOG.md
58
56
  - Gemfile
59
57
  - README.md
@@ -61,6 +59,7 @@ files:
61
59
  - gofer.gemspec
62
60
  - lib/gofer.rb
63
61
  - lib/gofer/cluster.rb
62
+ - lib/gofer/cluster_error.rb
64
63
  - lib/gofer/host.rb
65
64
  - lib/gofer/host_error.rb
66
65
  - lib/gofer/response.rb
@@ -73,29 +72,26 @@ files:
73
72
  - test.sh
74
73
  homepage: https://github.com/mipearson/gofer
75
74
  licenses: []
76
- post_install_message:
75
+ metadata: {}
76
+ post_install_message:
77
77
  rdoc_options: []
78
78
  require_paths:
79
79
  - lib
80
80
  required_ruby_version: !ruby/object:Gem::Requirement
81
81
  requirements:
82
- - - ">="
82
+ - - ! '>='
83
83
  - !ruby/object:Gem::Version
84
- version: !binary |-
85
- MA==
86
- none: false
84
+ version: '0'
87
85
  required_rubygems_version: !ruby/object:Gem::Requirement
88
86
  requirements:
89
- - - ">="
87
+ - - ! '>='
90
88
  - !ruby/object:Gem::Version
91
- version: !binary |-
92
- MA==
93
- none: false
89
+ version: '0'
94
90
  requirements: []
95
- rubyforge_project:
96
- rubygems_version: 1.8.24
97
- signing_key:
98
- specification_version: 3
91
+ rubyforge_project:
92
+ rubygems_version: 2.0.3
93
+ signing_key:
94
+ specification_version: 4
99
95
  summary: run commands on remote servers using SSH
100
96
  test_files:
101
97
  - spec/gofer/cluster_spec.rb