ridley 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,50 +2,97 @@ module Ridley
2
2
  class SSH
3
3
  # @author Jamie Winsor <jamie@vialstudios.com>
4
4
  class ResponseSet
5
- # @return [Array<SSH::Response>]
6
- attr_reader :oks
7
- # @return [Array<SSH::Response>]
8
- attr_reader :errors
5
+ class << self
6
+ # Merges the responses of the other ResponseSet with the target ResponseSet
7
+ # and returns the mutated target
8
+ #
9
+ # @param [SSH::ResponseSet] target
10
+ # @param [SSH::ResponseSet] other
11
+ #
12
+ # @return [SSH::ResponseSet]
13
+ def merge!(target, other)
14
+ if other.is_a?(self)
15
+ target.add_response(other.responses)
16
+ end
9
17
 
10
- def initialize
11
- @oks = Array.new
12
- @errors = Array.new
18
+ target
19
+ end
13
20
  end
14
21
 
15
- # Add an "OK" response to the ResponseSet
16
- #
17
- # @param [SSH::Response] response
18
- def add_ok(response)
19
- self.oks << response
22
+ extend Forwardable
23
+ include Enumerable
24
+
25
+ attr_reader :failures
26
+ attr_reader :successes
27
+
28
+ def_delegator :responses, :each
29
+
30
+ def initialize(responses = Array.new)
31
+ @failures = Array.new
32
+ @successes = Array.new
33
+
34
+ add_response Array(responses)
20
35
  end
21
36
 
22
- # Add an "Error" response to the ResponseSet
37
+ # @param [SSH::Response, Array<SSH::Response>] response
23
38
  #
24
- # @param [SSH::Response] response
25
- def add_error(response)
26
- self.errors << response
39
+ # @return [Array<SSH::Response>]
40
+ def add_response(response)
41
+ if response.is_a?(Array)
42
+ until response.empty?
43
+ add_response(response.pop)
44
+ end
45
+ return responses
46
+ end
47
+
48
+ response.error? ? add_failure(response) : add_success(response)
49
+ responses
50
+ end
51
+ alias_method :<<, :add_response
52
+
53
+ def responses
54
+ successes + failures
27
55
  end
28
56
 
29
57
  # Return true if the response set contains any errors
30
58
  #
31
59
  # @return [Boolean]
32
60
  def has_errors?
33
- self.errors.any?
61
+ self.failures.any?
34
62
  end
35
63
 
36
- # Return one of the responses
64
+ # Merges the responses of another ResponseSet with self and returns
65
+ # a new instance of ResponseSet
66
+ #
67
+ # @param [Ridley::SSH::ResponseSet] other
37
68
  #
38
- # @return [SSH::Response]
39
- def first
40
- (self.oks + self.errors).first
69
+ # @return [Ridley::SSH::ResponseSet]
70
+ def merge(other)
71
+ target = self.class.new(self.responses) # Why the fuck can't I use #dup here?
72
+ self.class.merge!(target, other)
41
73
  end
42
74
 
43
- # Returns how many responses are in the set
75
+ # Merges the respones of another ResponseSet with self and returns
76
+ # mutated self
44
77
  #
45
- # @return [Integer]
46
- def length
47
- self.oks.length + self.errors.length
78
+ # @param [Ridley::SSH::ResponseSet] other
79
+ #
80
+ # @return [self]
81
+ def merge!(other)
82
+ self.class.merge!(self, other)
48
83
  end
84
+
85
+ private
86
+
87
+ # @param [SSH::Response] response
88
+ def add_failure(response)
89
+ self.failures << response
90
+ end
91
+
92
+ # @param [SSH::Response] response
93
+ def add_success(response)
94
+ self.successes << response
95
+ end
49
96
  end
50
97
  end
51
98
  end
@@ -5,11 +5,18 @@ module Ridley
5
5
  class Worker
6
6
  include Celluloid
7
7
  include Celluloid::Logger
8
+
9
+ attr_reader :sudo
10
+ attr_reader :user
11
+ attr_reader :options
8
12
 
9
13
  # @param [Hash] options
10
14
  def initialize(options = {})
11
- @options = options
12
- @user = options.fetch(:user)
15
+ @options = options.dup
16
+ @sudo = @options[:sudo]
17
+ @user = @options[:user]
18
+
19
+ @options[:paranoid] = false
13
20
  end
14
21
 
15
22
  # @param [String] host
@@ -17,11 +24,15 @@ module Ridley
17
24
  #
18
25
  # @return [Array]
19
26
  def run(host, command)
20
- response = Response.new("", "")
27
+ response = Response.new(host)
21
28
  debug "Running SSH command: '#{command}' on: '#{host}' as: '#{user}'"
22
29
 
23
- Net::SSH.start(host, user, options) do |ssh|
30
+ Net::SSH.start(host, user, options.slice(*Net::SSH::VALID_OPTIONS)) do |ssh|
24
31
  ssh.open_channel do |channel|
32
+ if self.sudo
33
+ channel.request_pty
34
+ end
35
+
25
36
  channel.exec(command) do |ch, success|
26
37
  unless success
27
38
  raise "FAILURE: could not execute command"
@@ -29,10 +40,12 @@ module Ridley
29
40
 
30
41
  channel.on_data do |ch, data|
31
42
  response.stdout += data
43
+ info "NODE[#{host}] #{data}" if data.present? and data != "\r\n"
32
44
  end
33
45
 
34
46
  channel.on_extended_data do |ch, type, data|
35
47
  response.stderr += data
48
+ info "NODE[#{host}] #{data}" if data.present? and data != "\r\n"
36
49
  end
37
50
 
38
51
  channel.on_request("exit-status") do |ch, data|
@@ -50,22 +63,24 @@ module Ridley
50
63
 
51
64
  case response.exit_code
52
65
  when 0
53
- debug "Successfully ran SSH command: '#{command}' on: '#{host}' as: '#{user}' and it succeeded"
66
+ debug "Successfully ran SSH command on: '#{host}' as: '#{user}'"
54
67
  [ :ok, response ]
55
68
  else
56
- debug "Successfully ran SSH command: '#{command}' on: '#{host}' as: '#{user}' but it failed"
69
+ error "Successfully ran SSH command on: '#{host}' as: '#{user}', but it failed"
70
+ error response.stdout
57
71
  [ :error, response ]
58
72
  end
59
73
  rescue => e
60
- debug "Failed to run SSH command: '#{command}' on: '#{host}' as: '#{user}'"
61
- [ :error, e.message ]
74
+ error "Failed to run SSH command on: '#{host}' as: '#{user}'"
75
+ error "#{e.class}: #{e.message}"
76
+ response.exit_code = -1
77
+ response.stderr = e.message
78
+ [ :error, response ]
62
79
  end
63
80
 
64
81
  private
65
82
 
66
83
  attr_reader :runner
67
- attr_reader :user
68
- attr_reader :options
69
84
  end
70
85
  end
71
86
  end
data/lib/ridley/ssh.rb CHANGED
@@ -29,46 +29,28 @@ module Ridley
29
29
  # @param [Hash] options
30
30
  # @see Net::SSH
31
31
  def initialize(nodes, options = {})
32
- @nodes = nodes
32
+ @nodes = Array(nodes)
33
33
  @options = options
34
-
35
- self.options[:timeout] ||= 1.5
36
- end
37
-
38
- # @return [Array<SSH::Worker>]
39
- def workers
40
- @workers ||= Array(nodes).collect do |node|
41
- Worker.new_link(current_actor, node.public_hostname, options)
42
- end
43
34
  end
44
35
 
45
36
  # @param [String] command
46
37
  #
47
38
  # @return [Array]
48
39
  def run(command)
49
- workers.collect { |worker| worker.async.run(command) }
40
+ workers = Array.new
41
+ futures = self.nodes.collect do |node|
42
+ workers << worker = Worker.new_link(self.options.freeze)
43
+ worker.future.run(node.public_hostname, command)
44
+ end
50
45
 
51
- ResponseSet.new.tap do |responses|
52
- until responses.length == workers.length
53
- receive { |msg|
54
- status, response = msg
55
-
56
- case status
57
- when :ok
58
- responses.add_ok(response)
59
- when :error
60
- responses.add_error(response)
61
- else
62
- error "SSH Failure: #{command}. terminating..."
63
- terminate
64
- end
65
- }
46
+ ResponseSet.new.tap do |response_set|
47
+ futures.each do |future|
48
+ status, response = future.value
49
+ response_set.add_response(response)
66
50
  end
67
51
  end
68
- end
69
-
70
- def finalize
71
- workers.collect(&:terminate)
52
+ ensure
53
+ workers.map(&:terminate)
72
54
  end
73
55
  end
74
56
  end
@@ -1,3 +1,3 @@
1
1
  module Ridley
2
- VERSION = "0.5.2"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/ridley.rb CHANGED
@@ -8,6 +8,7 @@ require 'active_model'
8
8
  require 'active_support/inflector'
9
9
  require 'forwardable'
10
10
  require 'thread'
11
+ require 'pathname'
11
12
 
12
13
  if jruby?
13
14
  require 'json/pure'
@@ -41,7 +42,13 @@ module Ridley
41
42
  autoload :SSH, 'ridley/ssh'
42
43
 
43
44
  class << self
44
- attr_accessor :logger
45
+ extend Forwardable
46
+
47
+ def_delegator "Ridley::Logging", :logger
48
+ alias_method :log, :logger
49
+
50
+ def_delegator "Ridley::Logging", :logger=
51
+ def_delegator "Ridley::Logging", :set_logger
45
52
 
46
53
  def connection(*args)
47
54
  Connection.new(*args)
@@ -51,19 +58,6 @@ module Ridley
51
58
  Connection.sync(*args, &block)
52
59
  end
53
60
 
54
- # @return [Logger]
55
- def logger
56
- Ridley::Logging.logger
57
- end
58
- alias_method :log, :logger
59
-
60
- # @param [Logger, nil] obj
61
- #
62
- # @return [Logger]
63
- def set_logger(obj)
64
- Ridley::Logging.set_logger(obj)
65
- end
66
-
67
61
  # @return [Pathname]
68
62
  def root
69
63
  @root ||= Pathname.new(File.expand_path('../', File.dirname(__FILE__)))
@@ -71,6 +65,4 @@ module Ridley
71
65
  end
72
66
  end
73
67
 
74
- Celluloid.logger = Ridley.logger
75
-
76
68
  require 'ridley/middleware'
data/ridley.gemspec CHANGED
@@ -7,6 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.description = %q{A reliable Chef API client with a clean syntax}
8
8
  s.summary = s.description
9
9
  s.homepage = "https://github.com/reset/ridley"
10
+ s.license = "Apache 2.0"
10
11
 
11
12
  s.files = `git ls-files`.split($\)
12
13
  s.executables = Array.new
@@ -0,0 +1,9 @@
1
+ RSpec.configuration.before(:each) do
2
+ class Celluloid::ActorProxy
3
+ unless @rspec_compatible
4
+ @rspec_compatible = true
5
+ undef_method :should_receive
6
+ undef_method :stub
7
+ end
8
+ end
9
+ end
@@ -146,12 +146,12 @@ shared_examples_for "a Ridley Resource" do |resource_klass|
146
146
  end
147
147
 
148
148
  context "when there is an HTTPConflict" do
149
- it "sends an update message to the implemeneting class" do
149
+ it "sends the update message to self" do
150
150
  updated = double('updated')
151
151
  updated.stub(:[]).and_return(Hash.new)
152
152
  updated.stub(:attributes).and_return(Hash.new)
153
153
  subject.class.should_receive(:create).and_raise(Ridley::Errors::HTTPConflict.new(updated))
154
- subject.class.should_receive(:update).with(connection, subject).and_return(updated)
154
+ subject.should_receive(:update).and_return(updated)
155
155
 
156
156
  subject.save
157
157
  end
@@ -161,7 +161,7 @@ shared_examples_for "a Ridley Resource" do |resource_klass|
161
161
  context "when the object is invalid" do
162
162
  before(:each) { subject.stub(:valid?).and_return(false) }
163
163
 
164
- it "raises an InvalidObject error" do
164
+ it "raises an InvalidResource error" do
165
165
  lambda {
166
166
  subject.save
167
167
  }.should raise_error(Ridley::Errors::InvalidResource)
@@ -169,6 +169,39 @@ shared_examples_for "a Ridley Resource" do |resource_klass|
169
169
  end
170
170
  end
171
171
 
172
+ describe "#update" do
173
+ context "when the object is valid" do
174
+ let(:updated) do
175
+ updated = double('updated')
176
+ updated.stub(:[]).and_return(Hash.new)
177
+ updated.stub(:attributes).and_return(Hash.new)
178
+ updated
179
+ end
180
+
181
+ before(:each) { subject.stub(:valid?).and_return(true) }
182
+
183
+ it "sends an update message to the implementing class" do
184
+ subject.class.should_receive(:update).with(anything, subject).and_return(updated)
185
+ subject.update
186
+ end
187
+
188
+ it "returns true" do
189
+ subject.class.should_receive(:update).with(anything, subject).and_return(updated)
190
+ subject.update.should eql(true)
191
+ end
192
+ end
193
+
194
+ context "when the object is invalid" do
195
+ before(:each) { subject.stub(:valid?).and_return(false) }
196
+
197
+ it "raises an InvalidResource error" do
198
+ lambda {
199
+ subject.update
200
+ }.should raise_error(Ridley::Errors::InvalidResource)
201
+ end
202
+ end
203
+ end
204
+
172
205
  describe "#chef_id" do
173
206
  it "returns the value of the chef_id attribute" do
174
207
  subject.class.attribute(:name)
@@ -17,6 +17,10 @@ describe Ridley::Connection do
17
17
  }
18
18
  end
19
19
 
20
+ it "exposes its options publicly" do
21
+ described_class::OPTIONS.should be_a Array
22
+ end
23
+
20
24
  describe "ClassMethods" do
21
25
  subject { Ridley::Connection }
22
26
 
@@ -35,6 +35,17 @@ describe Ridley::Node do
35
35
  subject.bootstrap(connection, "33.33.33.10", "33.33.33.11", boot_options)
36
36
  end
37
37
  end
38
+
39
+ describe "::merge_data" do
40
+ it "finds the target node and sends it the merge_data message" do
41
+ data = double('data')
42
+ node = double('node')
43
+ node.should_receive(:merge_data).with(data)
44
+ subject.should_receive(:find!).and_return(node)
45
+
46
+ subject.merge_data(connection, node, data)
47
+ end
48
+ end
38
49
  end
39
50
 
40
51
  subject { Ridley::Node.new(connection) }
@@ -285,4 +296,50 @@ describe Ridley::Node do
285
296
  describe "#chef_client" do
286
297
  pending
287
298
  end
299
+
300
+ describe "#put_secret" do
301
+ pending
302
+ end
303
+
304
+ describe "#merge_data" do
305
+ before(:each) do
306
+ subject.name = "reset.riotgames.com"
307
+ subject.should_receive(:update)
308
+ end
309
+
310
+ it "appends items to the run_list" do
311
+ subject.merge_data(run_list: ["cook::one", "cook::two"])
312
+
313
+ subject.run_list.should =~ ["cook::one", "cook::two"]
314
+ end
315
+
316
+ it "ensures the run_list is unique if identical items are given" do
317
+ subject.run_list = [ "cook::one" ]
318
+ subject.merge_data(run_list: ["cook::one", "cook::two"])
319
+
320
+ subject.run_list.should =~ ["cook::one", "cook::two"]
321
+ end
322
+
323
+ it "deep merges attributes into the normal attributes" do
324
+ subject.normal = {
325
+ one: {
326
+ two: {
327
+ four: :deep
328
+ }
329
+ }
330
+ }
331
+ subject.merge_data(attributes: {
332
+ one: {
333
+ two: {
334
+ three: :deep
335
+ }
336
+ }
337
+ })
338
+
339
+ subject.normal[:one][:two].should have_key(:four)
340
+ subject.normal[:one][:two][:four].should eql(:deep)
341
+ subject.normal[:one][:two].should have_key(:three)
342
+ subject.normal[:one][:two][:three].should eql(:deep)
343
+ end
344
+ end
288
345
  end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::SSH::ResponseSet do
4
+ describe "ClassMethods" do
5
+ subject { described_class }
6
+
7
+ describe "::merge!" do
8
+ let(:target) { Ridley::SSH::ResponseSet.new }
9
+ let(:other) { Ridley::SSH::ResponseSet.new }
10
+
11
+ before(:each) do
12
+ other.add_response(Ridley::SSH::Response.new('host.local'))
13
+ end
14
+
15
+ it "returns the mutated target" do
16
+ result = subject.merge!(target, other)
17
+
18
+ result.should eql(target)
19
+ result.should have(1).item
20
+ end
21
+ end
22
+ end
23
+
24
+ subject { described_class.new }
25
+
26
+ describe "#add_response" do
27
+ it "accepts an array of responses" do
28
+ responses = [
29
+ Ridley::SSH::Response.new("one.riotgames.com"),
30
+ Ridley::SSH::Response.new("two.riotgames.com")
31
+ ]
32
+ subject.add_response(responses)
33
+
34
+ subject.responses.should have(2).items
35
+ end
36
+
37
+ it "accepts a single response" do
38
+ response = Ridley::SSH::Response.new("one.riotgames.com")
39
+ subject.add_response(response)
40
+
41
+ subject.responses.should have(1).item
42
+ end
43
+ end
44
+
45
+ describe "#responses" do
46
+ it "returns an array of Ridley::SSH::Response objects including both failures and successes" do
47
+ responses = [
48
+ double('success', error?: false),
49
+ double('failure', error?: true)
50
+ ]
51
+ subject.add_response(responses)
52
+
53
+ subject.responses.should have(2).items
54
+ end
55
+ end
56
+
57
+ describe "#successes" do
58
+ it "returns an array of Ridley::SSH::Response objects only including the successes" do
59
+ responses = [
60
+ double('success', error?: false),
61
+ double('failure', error?: true)
62
+ ]
63
+ subject.add_response(responses)
64
+
65
+ subject.successes.should have(1).item
66
+ end
67
+ end
68
+
69
+ describe "#failures" do
70
+ it "returns an array of Ridley::SSH::Response objects only including the failures" do
71
+ responses = [
72
+ double('success', error?: false),
73
+ double('failure', error?: true)
74
+ ]
75
+ subject.add_response(responses)
76
+
77
+ subject.failures.should have(1).item
78
+ end
79
+ end
80
+
81
+ describe "#merge" do
82
+ let(:target) { Ridley::SSH::ResponseSet.new }
83
+ let(:other) { Ridley::SSH::ResponseSet.new }
84
+
85
+ before(:each) do
86
+ other.add_response(Ridley::SSH::Response.new('host.local'))
87
+ end
88
+
89
+ it "returns a new Ridley::SSH::ResponseSet object" do
90
+ result = target.merge(other)
91
+
92
+ result.should have(1).item
93
+ target.should have(0).items
94
+ end
95
+ end
96
+
97
+ describe "#merge!" do
98
+ let(:target) { Ridley::SSH::ResponseSet.new }
99
+ let(:other) { Ridley::SSH::ResponseSet.new }
100
+
101
+ before(:each) do
102
+ other.add_response(Ridley::SSH::Response.new('host.local'))
103
+ end
104
+
105
+ it "returns the mutated target" do
106
+ result = target.merge!(other)
107
+
108
+ result.should have(1).item
109
+ target.should have(1).item
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::SSH::Worker do
4
+ describe "ClassMethods" do
5
+ subject { described_class }
6
+
7
+ describe "::new" do
8
+ it { subject.new(sudo: true).sudo.should be_true }
9
+ it { subject.new(sudo: false).sudo.should be_false }
10
+ it { subject.new().sudo.should be_false }
11
+ end
12
+ end
13
+ end
@@ -15,17 +15,34 @@ describe Ridley::SSH do
15
15
  subject { Ridley::SSH }
16
16
 
17
17
  describe "::start" do
18
- pending
18
+ let(:options) do
19
+ {
20
+ user: "vagrant",
21
+ password: "vagrant"
22
+ }
23
+ end
24
+
25
+ it "evaluates within the context of a new SSH and returns the last item in the block" do
26
+ result = subject.start([node_one, node_two], options) do |ssh|
27
+ ssh.run("ls")
28
+ end
29
+
30
+ result.should be_a(Ridley::SSH::ResponseSet)
31
+ end
32
+
33
+ it "raises a LocalJumpError if a block is not provided" do
34
+ expect {
35
+ subject.start([node_one, node_two], options)
36
+ }.to raise_error(LocalJumpError)
37
+ end
19
38
  end
20
39
  end
21
40
 
22
41
  subject { Ridley::SSH.new([node_one, node_two], user: "vagrant", password: "vagrant") }
23
42
 
24
- describe "#workers" do
25
- pending
26
- end
27
-
28
43
  describe "#run" do
29
- pending
44
+ it "returns an SSH::ResponseSet" do
45
+ subject.run("ls").should be_a(Ridley::SSH::ResponseSet)
46
+ end
30
47
  end
31
48
  end