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.
- data/bootstrappers/windows_omnibus.erb +1 -22
- data/lib/ridley/bootstrap_bindings/windows_template_binding.rb +0 -53
- data/lib/ridley/bootstrapper.rb +13 -10
- data/lib/ridley/bootstrapper/context.rb +2 -1
- data/lib/ridley/errors.rb +3 -0
- data/lib/ridley/host_connector.rb +21 -16
- data/lib/ridley/host_connector/ssh/worker.rb +40 -2
- data/lib/ridley/host_connector/winrm.rb +1 -0
- data/lib/ridley/host_connector/winrm/command_uploader.rb +90 -0
- data/lib/ridley/host_connector/winrm/worker.rb +67 -47
- data/lib/ridley/resources/environment_resource.rb +1 -1
- data/lib/ridley/resources/node_resource.rb +106 -19
- data/lib/ridley/version.rb +1 -1
- data/spec/unit/ridley/bootstrap_bindings/windows_template_binding_spec.rb +0 -6
- data/spec/unit/ridley/bootstrapper_spec.rb +23 -3
- data/spec/unit/ridley/host_connector/ssh/worker_spec.rb +49 -7
- data/spec/unit/ridley/host_connector/ssh_spec.rb +4 -3
- data/spec/unit/ridley/host_connector/winrm/command_uploader_spec.rb +67 -0
- data/spec/unit/ridley/host_connector/winrm/worker_spec.rb +67 -21
- data/spec/unit/ridley/host_connector/winrm_spec.rb +5 -0
- data/spec/unit/ridley/host_connector_spec.rb +39 -10
- data/spec/unit/ridley/resources/node_resource_spec.rb +171 -4
- metadata +5 -2
@@ -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.
|
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::
|
278
|
+
# a hash of options to pass to the best {Ridley::HostConnector}
|
203
279
|
#
|
204
|
-
# @return [
|
280
|
+
# @return [HostConnector::Response]
|
205
281
|
def chef_client(options = {})
|
206
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
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::
|
302
|
+
# a hash of options to pass to the best {Ridley::HostConnector}
|
220
303
|
#
|
221
|
-
# @return [
|
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
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
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
|
|
data/lib/ridley/version.rb
CHANGED
@@ -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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
5
|
-
|
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
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
14
|
+
subject(:winrm_port) { winrm_worker.winrm_port }
|
15
|
+
|
16
|
+
it { should eq(Ridley::HostConnector::DEFAULT_WINRM_PORT) }
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|