ridley 0.10.0.rc1 → 0.10.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -40,7 +40,7 @@ module Ridley
40
40
  # @example setting and saving an environment level default attribute
41
41
  #
42
42
  # obj = environment.find("production")
43
- # obj.set_defualt_attribute("my_app.billing.enabled", false)
43
+ # obj.set_default_attribute("my_app.billing.enabled", false)
44
44
  # obj.save
45
45
  #
46
46
  # @param [String] key
@@ -1,4 +1,4 @@
1
- module Ridley
1
+ module Ridley
2
2
  # @author Jamie Winsor <reset@riotgames.com>
3
3
  class NodeResource < Ridley::Resource
4
4
  class << self
@@ -54,6 +54,65 @@ module Ridley
54
54
  Bootstrapper.new(args, options).run
55
55
  end
56
56
 
57
+ # Executes a Chef run using the best worker available for the given
58
+ # host.
59
+ #
60
+ # @param [Ridley::Client] client
61
+ # @param [String] host
62
+ #
63
+ # @return [HostConnector::Response]
64
+ def chef_run(client, host)
65
+ worker = configured_worker_for(client, host)
66
+ worker.chef_client
67
+ ensure
68
+ worker.terminate if worker && worker.alive?
69
+ end
70
+
71
+ # Puts a secret on the host using the best worker available for
72
+ # the given host.
73
+ #
74
+ # @param [Ridley::Client] client
75
+ # @param [String] host
76
+ # @param [String] encrypted_data_bag_secret_path
77
+ #
78
+ # @return [HostConnector::Response]
79
+ def put_secret(client, host, encrypted_data_bag_secret_path)
80
+ worker = configured_worker_for(client, host)
81
+ worker.put_secret(encrypted_data_bag_secret_path)
82
+ ensure
83
+ worker.terminate if worker && worker.alive?
84
+ end
85
+
86
+ # Executes an arbitrary ruby script using the best worker available
87
+ # for the given host.
88
+ #
89
+ # @param [Ridley::Client] client
90
+ # @param [String] host
91
+ # @param [Array<String>] command_lines
92
+ #
93
+ # @return [HostConnector::Response]
94
+ def ruby_script(client, host, command_lines)
95
+ worker = configured_worker_for(client, host)
96
+ worker.ruby_script(command_lines)
97
+ ensure
98
+ worker.terminate if worker && worker.alive?
99
+ end
100
+
101
+ # Executes the given command on a node using the best worker
102
+ # available for the given host.
103
+ #
104
+ # @param [Ridley::Client] client
105
+ # @param [String] host
106
+ # @param [String] command
107
+ #
108
+ # @return [Array<Symbol, HostConnector::Response>]
109
+ def execute_command(client, host, command)
110
+ worker = configured_worker_for(client, host)
111
+ worker.run(command)
112
+ ensure
113
+ worker.terminate if worker && worker.alive?
114
+ end
115
+
57
116
  # Merges the given data with the the data of the target node on the remote
58
117
  #
59
118
  # @param [Ridley::Client] client
@@ -72,8 +131,25 @@ module Ridley
72
131
  def merge_data(client, target, options = {})
73
132
  find!(client, target).merge_data(options)
74
133
  end
134
+
135
+ private
136
+ # @param [Ridley::Client] client
137
+ # @param [String] host
138
+ #
139
+ # @return [SSH::Worker, WinRM::Worker]
140
+ def configured_worker_for(client, host)
141
+ connector_options = Hash.new
142
+ connector_options[:ssh] = client.ssh
143
+ connector_options[:winrm] = client.winrm
144
+
145
+ HostConnector.best_connector_for(host, connector_options) do |host_connector|
146
+ host_connector::Worker.new(host, connector_options)
147
+ end
148
+ end
75
149
  end
76
150
 
151
+ include Ridley::Logging
152
+
77
153
  set_chef_id "name"
78
154
  set_chef_type "node"
79
155
  set_chef_json_class "Chef::Node"
@@ -114,8 +190,8 @@ module Ridley
114
190
  # attribute and value.
115
191
  #
116
192
  # @note It is not possible to set any other attribute level on a node and have it persist after
117
- # a Chef Run. This is because all other attribute levels are truncated at the start of a Chef Run.
118
- #
193
+ # a Chef Run. This is because all other attribute levels are truncated at the start of a Chef Run.
194
+ #
119
195
  # @example setting and saving a node level normal attribute
120
196
  #
121
197
  # obj = node.find("jwinsor-1")
@@ -165,7 +241,7 @@ module Ridley
165
241
  #
166
242
  # @return [nil, String]
167
243
  def cloud_provider
168
- self.cloud? ? self.automatic[:cloud][:provider] : nil
244
+ self.cloud? ? self.automatic[:cloud][:provider] : nil
169
245
  end
170
246
 
171
247
  # Returns true if the node is identified as a cloud node.
@@ -196,18 +272,25 @@ module Ridley
196
272
  self.cloud_provider == "rackspace"
197
273
  end
198
274
 
199
- # Run Chef-Client on the instantiated node
275
+ # Run Chef-Client on the instantiated node.
200
276
  #
201
277
  # @param [Hash] options
202
- # a hash of options to pass to {Ridley::SSH.start}
278
+ # a hash of options to pass to the best {Ridley::HostConnector}
203
279
  #
204
- # @return [SSH::Response]
280
+ # @return [HostConnector::Response]
205
281
  def chef_client(options = {})
206
- options = client.ssh.merge(options)
282
+ connector_options = Hash.new
283
+ connector_options[:ssh] = client.ssh
284
+ connector_options[:winrm] = client.winrm
285
+ connector_options.merge(options)
286
+
287
+ log.debug "Running Chef Client on: #{self.public_hostname}"
207
288
 
208
- Ridley.log.debug "Running Chef Client on: #{self.public_hostname}"
209
- Ridley::SSH.start(self, options) do |ssh|
210
- ssh.run("sudo chef-client").first
289
+ HostConnector.best_connector_for(self.public_hostname, connector_options) do |host_connector|
290
+ host_connector.start(self, connector_options) do |connector|
291
+ _, response = connector.chef_client
292
+ response
293
+ end
211
294
  end
212
295
  end
213
296
 
@@ -216,9 +299,9 @@ module Ridley
216
299
  # returned
217
300
  #
218
301
  # @param [Hash] options
219
- # a hash of options to pass to {Ridley::SSH.start}
302
+ # a hash of options to pass to the best {Ridley::HostConnector}
220
303
  #
221
- # @return [SSH::Response, nil]
304
+ # @return [HostConnector::Response, nil]
222
305
  def put_secret(options = {})
223
306
  if client.encrypted_data_bag_secret_path.nil? ||
224
307
  !File.exists?(client.encrypted_data_bag_secret_path)
@@ -226,13 +309,17 @@ module Ridley
226
309
  return nil
227
310
  end
228
311
 
229
- options = client.ssh.merge(options)
230
- secret = File.read(client.encrypted_data_bag_secret_path).chomp
231
- command = "echo '#{secret}' > /etc/chef/encrypted_data_bag_secret; chmod 0600 /etc/chef/encrypted_data_bag_secret"
312
+ connector_options = Hash.new
313
+ connector_options[:ssh] = client.ssh
314
+ connector_options[:winrm] = client.winrm
315
+
316
+ log.debug "Writing Encrypted Data Bag Secret to: #{self.public_hostname}"
232
317
 
233
- Ridley.log.debug "Writing Encrypted Data Bag Secret to: #{self.public_hostname}"
234
- Ridley::SSH.start(self, options) do |ssh|
235
- ssh.run(command).first
318
+ HostConnector.best_connector_for(self.public_hostname, connector_options) do |host_connector|
319
+ host_connector.start(self, connector_options) do |connector|
320
+ _, response = connector.put_secret(client.encrypted_data_bag_secret_path)
321
+ response
322
+ end
236
323
  end
237
324
  end
238
325
 
@@ -1,3 +1,3 @@
1
1
  module Ridley
2
- VERSION = "0.10.0.rc1"
2
+ VERSION = "0.10.0.rc2"
3
3
  end
@@ -104,12 +104,6 @@ describe Ridley::WindowsTemplateBinding do
104
104
  end
105
105
  end
106
106
 
107
- describe "#windows_wget_vb" do
108
- it "returns a string" do
109
- subject.windows_wget_vb.should be_a(String)
110
- end
111
- end
112
-
113
107
  describe "#windows_wget_powershell" do
114
108
  it "returns a string" do
115
109
  subject.windows_wget_powershell.should be_a(String)
@@ -64,9 +64,29 @@ describe Ridley::Bootstrapper do
64
64
  end
65
65
 
66
66
  describe "#contexts" do
67
- it "returns an array of Bootstrapper::Contexts" do
68
- subject.contexts.should be_a(Array)
69
- subject.contexts.should each be_a(Ridley::Bootstrapper::Context)
67
+ before do
68
+ Ridley::Bootstrapper::Context.stub(:create).and_return(double)
69
+ end
70
+
71
+ it "creates a new context for each host" do
72
+ Ridley::Bootstrapper::Context.should_receive(:create).exactly(nodes.length).times
73
+ subject.contexts
74
+ end
75
+
76
+ it "contains a item for each host" do
77
+ subject.contexts.should have(nodes.length).items
78
+ end
79
+
80
+ context "when a host is unreachable" do
81
+ before do
82
+ Ridley::Bootstrapper::Context.stub(:create).and_raise(Ridley::Errors::HostConnectionError)
83
+ end
84
+
85
+ it "raises a HostConnectionError" do
86
+ expect {
87
+ subject.contexts
88
+ }.to raise_error(Ridley::Errors::HostConnectionError)
89
+ end
70
90
  end
71
91
  end
72
92
 
@@ -1,15 +1,57 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Ridley::HostConnector::SSH::Worker do
4
- describe "ClassMethods" do
5
- let(:host) { 'reset.riotgames.com' }
4
+ subject { ssh_worker }
5
+ let(:ssh_worker) { described_class.new(host, options) }
6
+
7
+ let(:host) { 'reset.riotgames.com' }
8
+ let(:options) { {} }
9
+
10
+ describe "#sudo" do
11
+ subject { ssh_worker.sudo }
12
+
13
+ it { should be_false }
14
+
15
+ context "with sudo" do
16
+ let(:options) { { ssh: { sudo: true } } }
17
+
18
+ it { should be_true }
19
+ end
20
+ end
21
+
22
+ describe "#chef_client" do
23
+ subject(:chef_client) { ssh_worker.chef_client }
24
+
25
+ it { should be_a(Array) }
26
+
27
+ context "with sudo" do
28
+ let(:options) { { ssh: { sudo: true } } }
29
+
30
+ it "sends a run command with sudo" do
31
+ ssh_worker.should_receive(:run).with("sudo chef-client")
32
+ chef_client
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "#put_secret" do
38
+ subject(:put_secret) { ssh_worker.put_secret(encrypted_data_bag_secret_path) }
39
+ let(:encrypted_data_bag_secret_path) { fixtures_path.join("encrypted_data_bag_secret").to_s }
40
+ let(:secret) { File.read(encrypted_data_bag_secret_path).chomp }
6
41
 
7
- subject { described_class }
42
+ it "receives a run command with echo" do
43
+ ssh_worker.should_receive(:run).with("echo '#{secret}' > /etc/chef/encrypted_data_bag_secret; chmod 0600 /etc/chef/encrypted_data_bag_secret")
44
+ put_secret
45
+ end
46
+ end
47
+
48
+ describe "#ruby_script" do
49
+ subject(:ruby_script) { ssh_worker.ruby_script(command_lines) }
50
+ let(:command_lines) { ["puts 'hello'", "puts 'there'"] }
8
51
 
9
- describe "::new" do
10
- it { subject.new(host, ssh: {sudo: true}).sudo.should be_true }
11
- it { subject.new(host, ssh: {sudo: false}).sudo.should be_false }
12
- it { subject.new(host).sudo.should be_false }
52
+ it "receives a ruby call with the command" do
53
+ ssh_worker.should_receive(:run).with("#{described_class::EMBEDDED_RUBY_PATH} -e \"puts 'hello';puts 'there'\"")
54
+ ruby_script
13
55
  end
14
56
  end
15
57
  end
@@ -18,7 +18,8 @@ describe Ridley::HostConnector::SSH do
18
18
  let(:options) do
19
19
  {
20
20
  user: "vagrant",
21
- password: "vagrant"
21
+ password: "vagrant",
22
+ timeout: 1
22
23
  }
23
24
  end
24
25
 
@@ -38,10 +39,10 @@ describe Ridley::HostConnector::SSH do
38
39
  end
39
40
  end
40
41
 
41
- subject { Ridley::HostConnector::SSH.new([node_one, node_two], user: "vagrant", password: "vagrant") }
42
+ subject { Ridley::HostConnector::SSH.new([node_one, node_two], ssh: {user: "vagrant", password: "vagrant", timeout: 1}) }
42
43
 
43
44
  describe "#run" do
44
- it "returns an SSH::ResponseSet" do
45
+ it "returns an HostConnector::ResponseSet" do
45
46
  subject.run("ls").should be_a(Ridley::HostConnector::ResponseSet)
46
47
  end
47
48
  end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::HostConnector::WinRM::CommandUploader do
4
+ let(:winrm_stub) {
5
+ double('WinRM',
6
+ run_cmd: run_cmd_data,
7
+ powershell: nil
8
+ )
9
+ }
10
+
11
+ subject { command_uploader }
12
+
13
+ let(:command_uploader) { described_class.new(winrm_stub) }
14
+ let(:command_string) { "a" * 2048 }
15
+ let(:run_cmd_data) { { data: [{ stdout: "abc123" }] } }
16
+ let(:command_file_name) { "my_command.bat" }
17
+
18
+ its(:winrm) { should eq(winrm_stub) }
19
+
20
+ before do
21
+ command_uploader.stub(:get_file_path).and_return("")
22
+ end
23
+
24
+ describe "#upload" do
25
+ let(:upload) { command_uploader.upload(command_string) }
26
+
27
+ it "calls winrm to upload and convert the command" do
28
+ winrm_stub.should_receive(:run_cmd).and_return(
29
+ run_cmd_data,
30
+ nil,
31
+ run_cmd_data
32
+ )
33
+ winrm_stub.should_receive(:powershell)
34
+
35
+ upload
36
+ end
37
+ end
38
+
39
+ describe "#command" do
40
+ subject { command }
41
+ let(:command) { command_uploader.command }
42
+
43
+ before do
44
+ command_uploader.stub command_file_name: command_file_name
45
+ end
46
+
47
+ it { should eq("cmd.exe /C #{command_file_name}") }
48
+ end
49
+
50
+ describe "#cleanup" do
51
+ subject { cleanup }
52
+
53
+ let(:cleanup) { command_uploader.cleanup }
54
+ let(:base64_file_name) { "my_base64_file" }
55
+
56
+ before do
57
+ command_uploader.stub command_file_name: command_file_name
58
+ command_uploader.stub base64_file_name: base64_file_name
59
+ end
60
+
61
+ it "cleans up the windows temp dir" do
62
+ winrm_stub.should_receive(:run_cmd).with("del #{base64_file_name} /F /Q")
63
+ winrm_stub.should_receive(:run_cmd).with("del #{command_file_name} /F /Q")
64
+ cleanup
65
+ end
66
+ end
67
+ end
@@ -1,41 +1,87 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Ridley::HostConnector::WinRM::Worker do
4
+ subject { winrm_worker }
5
+ let(:winrm_worker) { described_class.new(host, options) }
4
6
  let(:host) { 'reset.riotgames.com' }
5
-
6
- let(:options) do
7
- {
8
- winrm: {
9
- port: 1234
10
- }
11
- }
12
- end
7
+ let(:options) { {} }
13
8
 
14
- subject { Ridley::HostConnector::WinRM::Worker.new(host, options) }
9
+ before do
10
+ Ridley::HostConnector::WinRM::CommandUploader.stub(:new).and_return(double('command_uploader'))
11
+ end
15
12
 
16
13
  describe "#winrm_port" do
17
- it "can be overridden if options contains :winrm_port" do
18
- subject.winrm_port.should eq(1234)
19
- end
14
+ subject(:winrm_port) { winrm_worker.winrm_port }
15
+
16
+ it { should eq(Ridley::HostConnector::DEFAULT_WINRM_PORT) }
20
17
 
21
- it "defaults to Ridley::HostConnector::DEFAULT_WINRM_PORT when not overridden" do
22
- options.delete(:winrm)
23
- subject.winrm_port.should eq(Ridley::HostConnector::DEFAULT_WINRM_PORT)
18
+ context "when overridden" do
19
+ let(:options) { { winrm: { port: 1234 } } }
20
+
21
+ it { should eq(1234) }
24
22
  end
25
23
  end
26
24
 
27
25
  describe "#winrm" do
28
- it "returns a WinRM::WinRMWebService" do
29
- subject.winrm.should be_a(WinRM::WinRMWebService)
30
- end
26
+ subject { winrm_worker.winrm }
27
+
28
+ it { should be_a(WinRM::WinRMWebService) }
31
29
  end
32
30
 
33
31
  describe "#get_command" do
32
+ subject(:get_command) { winrm_worker.get_command(command, command_uploader_stub) }
33
+
34
34
  let(:command) { "echo %TEMP%" }
35
- context "when a command is less than 2047 characters" do
36
- it "returns the command" do
37
- subject.get_command(command).should eq(command)
35
+ let(:command_uploader_stub) { double('CommandUploader') }
36
+
37
+ it { should eq(command) }
38
+
39
+ context "when a command is more than 2047 characters" do
40
+ let(:command) { "a" * 2048 }
41
+
42
+ it "uploads and returns a command" do
43
+ Ridley::HostConnector::WinRM::CommandUploader.stub new: command_uploader_stub
44
+
45
+ command_uploader_stub.should_receive :upload
46
+ command_uploader_stub.stub command: "my command"
47
+ command_uploader_stub.stub(:cleanup)
48
+
49
+ get_command.should eq("my command")
38
50
  end
39
51
  end
40
52
  end
53
+
54
+ describe "#chef_client" do
55
+ subject(:chef_client) { winrm_worker.chef_client }
56
+
57
+ it "receives a command to run chef-client" do
58
+ winrm_worker.should_receive(:run).with("chef-client")
59
+
60
+ chef_client
61
+ end
62
+ end
63
+
64
+ describe "#put_secret" do
65
+ subject(:put_secret) { winrm_worker.put_secret(encrypted_data_bag_secret_path) }
66
+
67
+ let(:encrypted_data_bag_secret_path) { fixtures_path.join("encrypted_data_bag_secret").to_s }
68
+ let(:secret) { File.read(encrypted_data_bag_secret_path).chomp }
69
+
70
+ it "receives a command to copy the secret" do
71
+ winrm_worker.should_receive(:run).with("echo #{secret} > C:\\chef\\encrypted_data_bag_secret")
72
+
73
+ put_secret
74
+ end
75
+ end
76
+
77
+ describe "#ruby_script" do
78
+ subject(:ruby_script) { winrm_worker.ruby_script(command_lines) }
79
+ let(:command_lines) { ["puts 'hello'", "puts 'there'"] }
80
+
81
+ it "receives a ruby call with the command" do
82
+ winrm_worker.should_receive(:run).with("#{described_class::EMBEDDED_RUBY_PATH} -e \"puts 'hello';puts 'there'\"")
83
+
84
+ ruby_script
85
+ end
86
+ end
41
87
  end