silver_spurs 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.simplecov +4 -0
- data/.travis.yml +6 -0
- data/README.md +55 -8
- data/Rakefile +18 -0
- data/lib/silver_spurs/asyncifier.rb +3 -3
- data/lib/silver_spurs/client.rb +10 -8
- data/lib/silver_spurs/client/bootstrap_run.rb +1 -4
- data/lib/silver_spurs/knife_interface.rb +1 -1
- data/lib/silver_spurs/version.rb +1 -1
- data/silver_spurs.gemspec +1 -0
- data/spec/lib/silver_spurs/app_spec.rb +192 -9
- data/spec/lib/silver_spurs/asyncifier_spec.rb +509 -0
- data/spec/lib/silver_spurs/client/bootstrap_run_spec.rb +162 -0
- data/spec/lib/silver_spurs/client_spec.rb +113 -0
- data/spec/lib/silver_spurs/knife_interface_spec.rb +26 -1
- data/spec/spec_helper.rb +2 -0
- metadata +33 -3
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.simplecov
ADDED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,29 +1,76 @@
|
|
1
|
-
# SilverSpurs
|
1
|
+
# SilverSpurs [![Build Status](https://travis-ci.org/christian-blades-cb/silver_spurs.png?branch=master)](https://travis-ci.org/christian-blades-cb/silver_spurs)
|
2
2
|
|
3
|
-
|
3
|
+
RESTful Chef bootstrapping
|
4
|
+
|
5
|
+
Instead of using a CLI to kick off bootstrapping, hit an API endpoint and let the service do it for you.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
9
|
+
Install chef and set up knife on the machine (refer to the [chef docs](http://docs.opscode.com))
|
10
|
+
|
7
11
|
Add this line to your application's Gemfile:
|
8
12
|
|
9
13
|
gem 'silver_spurs'
|
10
14
|
|
15
|
+
Create a config.ru:
|
16
|
+
|
17
|
+
require 'bundler'
|
18
|
+
Bundler.setup
|
19
|
+
|
20
|
+
require 'silver_spurs'
|
21
|
+
|
22
|
+
# SilverSpurs::App.deployment_key = '/opt/deployment_key.pem'
|
23
|
+
# SilverSpurs::App.deployment_user = 'deployer'
|
24
|
+
# SilverSpurs::App.node_name_filter = /\w{3,10}/
|
25
|
+
# SilverSpurs::Asyncifier.base_path = '/opt/silver_spurs'
|
26
|
+
|
27
|
+
run SilverSpurs::App
|
28
|
+
|
11
29
|
And then execute:
|
12
30
|
|
13
31
|
$ bundle
|
32
|
+
$ rackup
|
14
33
|
|
15
|
-
|
34
|
+
## Usage
|
16
35
|
|
17
|
-
|
36
|
+
### Kick off a bootstrap
|
18
37
|
|
19
|
-
|
38
|
+
$ curl -X PUT -i -d node_name=machineotron http://localhost/bootstrap/10.0.1.2
|
39
|
+
|
40
|
+
The redirect will point to the query URL for the bootstrap run. Since these runs tend to take several minutes, this is preferable to a long-running HTTP request.
|
41
|
+
|
42
|
+
Further GET requests to this endpoint will return a log of the run in progress. HEAD requests will return just the status code.
|
43
|
+
|
44
|
+
### Status codes
|
45
|
+
|
46
|
+
* 406 - Missing a required parameter
|
47
|
+
* 404 - Unknown endpoint or resource
|
48
|
+
* 201 - Run completed successfully
|
49
|
+
* 202 - Run in progress
|
50
|
+
* 550 - Run ended in failure
|
51
|
+
|
52
|
+
## Using the client
|
53
|
+
|
54
|
+
Add this line to the application's Gemfile:
|
55
|
+
|
56
|
+
gem 'silver_spurs'
|
57
|
+
|
58
|
+
Sample application
|
20
59
|
|
21
|
-
|
60
|
+
require 'silver_spurs/client'
|
61
|
+
|
62
|
+
silver = SilverSpurs::Client.new('http://localhost')
|
63
|
+
bootstrap = silver.start_bootstrap('10.0.1.2', 'machineotron')
|
64
|
+
while bootstrap.status == :pending do
|
65
|
+
sleep 10
|
66
|
+
end
|
67
|
+
puts bootstrap.log
|
22
68
|
|
23
69
|
## Contributing
|
24
70
|
|
25
71
|
1. Fork it
|
26
72
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
73
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
-
4.
|
29
|
-
5.
|
74
|
+
4. Add you some tests
|
75
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
76
|
+
6. Create new Pull Request
|
data/Rakefile
CHANGED
@@ -1,2 +1,20 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
desc 'Run all tests'
|
6
|
+
task :test => [:spec]
|
7
|
+
task :default => [:coverage]
|
8
|
+
|
9
|
+
desc "Run all rspec tests"
|
10
|
+
RSpec::Core::RakeTask.new 'spec' do |t|
|
11
|
+
t.pattern = 'spec/**/*_spec.rb'
|
12
|
+
t.rspec_opts = '-fd -c'
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Run all rspec tests and generate a coverage report"
|
16
|
+
task :coverage do
|
17
|
+
ENV['COVERAGE'] = 'true'
|
18
|
+
Rake::Task['spec'].invoke
|
19
|
+
`open coverage/index.html` if RUBY_PLATFORM.downcase.include? 'darwin'
|
20
|
+
end
|
@@ -28,7 +28,7 @@ module SilverSpurs
|
|
28
28
|
def spawn_process(process_name, command)
|
29
29
|
create_directory_tree
|
30
30
|
logged_command = "#{command} 1> #{log_file_path process_name} 2>&1 && touch #{success_file_path process_name}"
|
31
|
-
logger.debug "Executing: #{
|
31
|
+
logger.debug "Executing: #{logged_command}"
|
32
32
|
pid = Process.spawn logged_command
|
33
33
|
File.open(pid_file_path(process_name), 'wb') { |f| f.write pid }
|
34
34
|
Process.detach pid
|
@@ -38,7 +38,7 @@ module SilverSpurs
|
|
38
38
|
return false unless File.exists? pid_file_path(process_name)
|
39
39
|
|
40
40
|
pid = File.read(pid_file_path(process_name)).to_i
|
41
|
-
|
41
|
+
IO.popen("ps -o command -p #{pid}").read.split("\n").count == 2
|
42
42
|
end
|
43
43
|
|
44
44
|
def success?(process_name)
|
@@ -63,7 +63,7 @@ module SilverSpurs
|
|
63
63
|
sleep 1
|
64
64
|
Process.kill('TERM', pid) if has_lock? process_name
|
65
65
|
end
|
66
|
-
|
66
|
+
reap_orphaned_lock process_name
|
67
67
|
end
|
68
68
|
|
69
69
|
def reap_old_process(process_name)
|
data/lib/silver_spurs/client.rb
CHANGED
@@ -17,13 +17,7 @@ module SilverSpurs
|
|
17
17
|
payload = parameterize_hash params
|
18
18
|
headers = {:accept => :json, :content_type=> 'application/x-www-form-urlencoded'}
|
19
19
|
|
20
|
-
response = spur_host["bootstrap/#{ip}"].put(payload, headers
|
21
|
-
if response.code == 303
|
22
|
-
response
|
23
|
-
else
|
24
|
-
response.return! &block
|
25
|
-
end
|
26
|
-
end
|
20
|
+
response = spur_host["bootstrap/#{ip}"].put(payload, headers, &method(:dont_redirect_for_303))
|
27
21
|
|
28
22
|
throw ClientException.new("unexpected response", response) unless response.code == 303
|
29
23
|
|
@@ -46,6 +40,14 @@ module SilverSpurs
|
|
46
40
|
def spur_host
|
47
41
|
RestClient::Resource.new(@host_url, :timeout => @timeout)
|
48
42
|
end
|
49
|
-
|
43
|
+
|
44
|
+
def dont_redirect_for_303(response, origin, orig_result, &block)
|
45
|
+
if response.code == 303
|
46
|
+
response
|
47
|
+
else
|
48
|
+
response.return! &block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
50
52
|
end
|
51
53
|
end
|
data/lib/silver_spurs/version.rb
CHANGED
data/silver_spurs.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
describe SilverSpurs::App do
|
4
5
|
include Rack::Test::Methods
|
@@ -6,11 +7,54 @@ describe SilverSpurs::App do
|
|
6
7
|
def app
|
7
8
|
SilverSpurs::App
|
8
9
|
end
|
10
|
+
|
11
|
+
describe '/' do
|
12
|
+
it "should return 200" do
|
13
|
+
get '/'
|
14
|
+
last_response.status.should be 200
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '/settings' do
|
19
|
+
it "should tell us about the deployment_key, deployment_user and node_name_filter" do
|
20
|
+
get '/settings'
|
21
|
+
settings_hash = JSON.parse last_response.body
|
22
|
+
[:deployment_key, :deployment_user, :node_name_filter].each do |setting|
|
23
|
+
settings_hash.keys.should include setting.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
9
27
|
|
10
28
|
describe "/bootstrap/:ip" do
|
29
|
+
|
30
|
+
context "when the node is not already being bootstrapped" do
|
31
|
+
before :each do
|
32
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return false
|
33
|
+
end
|
34
|
+
|
35
|
+
it "builds a bootstrap command" do
|
36
|
+
SilverSpurs::Asyncifier.stub(:spawn_process)
|
37
|
+
SilverSpurs::KnifeInterface.should_receive(:bootstrap_command).with('10.0.0.0', 'yourmom', kind_of(String), kind_of(String), kind_of(Hash))
|
38
|
+
|
39
|
+
put '/bootstrap/10.0.0.0', :node_name => 'yourmom'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "spawns a knife run" do
|
43
|
+
SilverSpurs::Asyncifier.should_receive(:spawn_process).with(kind_of(String), kind_of(String))
|
44
|
+
SilverSpurs::KnifeInterface.stub(:bootstrap_command).and_return 'knife bootstrap'
|
45
|
+
|
46
|
+
put '/bootstrap/10.0.0.0', :node_name => 'yourmom'
|
47
|
+
end
|
48
|
+
end
|
11
49
|
|
50
|
+
context "when a node name is not passed in" do
|
51
|
+
it "should return a 406 status" do
|
52
|
+
put '/bootstrap/10.0.0.0'
|
53
|
+
last_response.status.should be 406
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
12
57
|
context "with bad node name" do
|
13
|
-
|
14
58
|
it "should reject node names with spaces" do
|
15
59
|
node_name = "your mom"
|
16
60
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
@@ -46,47 +90,186 @@ describe SilverSpurs::App do
|
|
46
90
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
47
91
|
last_response.status.should eq 406
|
48
92
|
end
|
49
|
-
|
50
93
|
end
|
51
94
|
|
52
95
|
context "with good node name" do
|
53
96
|
before(:each) do
|
54
|
-
SilverSpurs::
|
97
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return true
|
55
98
|
end
|
56
99
|
|
57
100
|
it "should accept node names with dashes" do
|
58
101
|
node_name = "your-mom"
|
59
102
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
60
|
-
last_response.status.should eq
|
103
|
+
last_response.status.should eq 303
|
61
104
|
end
|
62
105
|
|
63
106
|
it "should accept one-word node names" do
|
64
107
|
node_name = "mom"
|
65
108
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
66
|
-
last_response.status.should eq
|
109
|
+
last_response.status.should eq 303
|
67
110
|
end
|
68
111
|
|
69
112
|
it "should accept node names with numbers" do
|
70
113
|
node_name = "mombot-3000"
|
71
114
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
72
|
-
last_response.status.should eq
|
115
|
+
last_response.status.should eq 303
|
73
116
|
end
|
74
117
|
|
75
118
|
it "should accept node names with 15 chars" do
|
76
119
|
node_name = "123456789012345"
|
77
120
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
78
|
-
last_response.status.should eq
|
121
|
+
last_response.status.should eq 303
|
79
122
|
end
|
80
123
|
|
81
124
|
it "should accept node names with 3 chars" do
|
82
125
|
node_name = "123"
|
83
126
|
put "/bootstrap/10.0.0.0", :node_name => node_name
|
84
|
-
last_response.status.should eq
|
127
|
+
last_response.status.should eq 303
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '/bootstrap/query/:process_id' do
|
134
|
+
|
135
|
+
describe 'HEAD' do
|
136
|
+
|
137
|
+
context 'when process does not exist' do
|
138
|
+
before :each do
|
139
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return false
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should return a 404' do
|
143
|
+
head '/bootstrap/query/fake_process'
|
144
|
+
last_response.status.should eq 404
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when process is still running' do
|
149
|
+
before :each do
|
150
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return true
|
151
|
+
SilverSpurs::Asyncifier.stub(:reap_old_process)
|
152
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return true
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should return a 202' do
|
156
|
+
head '/bootstrap/query/fake_process'
|
157
|
+
last_response.status.should eq 202
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'when process finished successfully' do
|
162
|
+
before :each do
|
163
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return true
|
164
|
+
SilverSpurs::Asyncifier.stub(:reap_old_process)
|
165
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return false
|
166
|
+
SilverSpurs::Asyncifier.stub(:success?).and_return true
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should return a 201' do
|
170
|
+
head '/bootstrap/query/fake_process'
|
171
|
+
last_response.status.should eq 201
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'when process finished in failure' do
|
176
|
+
before :each do
|
177
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return true
|
178
|
+
SilverSpurs::Asyncifier.stub(:reap_old_process)
|
179
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return false
|
180
|
+
SilverSpurs::Asyncifier.stub(:success?).and_return false
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should return a 550' do
|
184
|
+
head '/bootstrap/query/fake_process'
|
185
|
+
last_response.status.should eq 550
|
186
|
+
end
|
85
187
|
end
|
86
|
-
|
188
|
+
|
87
189
|
end
|
190
|
+
|
191
|
+
describe 'GET' do
|
192
|
+
before :each do
|
193
|
+
SilverSpurs::Asyncifier.stub(:get_log).and_return "Loggylog"
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'when process does not exist' do
|
197
|
+
before :each do
|
198
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return false
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should return a 404' do
|
202
|
+
get '/bootstrap/query/fake_process'
|
203
|
+
last_response.status.should eq 404
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'should not spit out a log' do
|
207
|
+
get '/bootstrap/query/fake_process'
|
208
|
+
last_response.body.should_not eq 'Loggylog'
|
209
|
+
end
|
88
210
|
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'when process is still running' do
|
214
|
+
before :each do
|
215
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return true
|
216
|
+
SilverSpurs::Asyncifier.stub(:reap_old_process)
|
217
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return true
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'should return a 202' do
|
221
|
+
get '/bootstrap/query/fake_process'
|
222
|
+
last_response.status.should eq 202
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'should spit out a log' do
|
226
|
+
get '/bootstrap/query/fake_process'
|
227
|
+
last_response.body.should eq 'Loggylog'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'when process finished successfully' do
|
232
|
+
before :each do
|
233
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return true
|
234
|
+
SilverSpurs::Asyncifier.stub(:reap_old_process)
|
235
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return false
|
236
|
+
SilverSpurs::Asyncifier.stub(:success?).and_return true
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'should return a 201' do
|
240
|
+
get '/bootstrap/query/fake_process'
|
241
|
+
last_response.status.should eq 201
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'should spit out a log' do
|
245
|
+
get '/bootstrap/query/fake_process'
|
246
|
+
last_response.body.should eq 'Loggylog'
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'when process finished in failure' do
|
251
|
+
before :each do
|
252
|
+
SilverSpurs::Asyncifier.stub(:exists?).and_return true
|
253
|
+
SilverSpurs::Asyncifier.stub(:reap_old_process)
|
254
|
+
SilverSpurs::Asyncifier.stub(:has_lock?).and_return false
|
255
|
+
SilverSpurs::Asyncifier.stub(:success?).and_return false
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'should return a 550' do
|
259
|
+
get '/bootstrap/query/fake_process'
|
260
|
+
last_response.status.should eq 550
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'should spit out a log' do
|
264
|
+
get '/bootstrap/query/fake_process'
|
265
|
+
last_response.body.should eq 'Loggylog'
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
89
271
|
end
|
272
|
+
|
90
273
|
end
|
91
274
|
|
92
275
|
|