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 CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  \#*#
19
+ /silver_spurs_async
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ -fd
2
+ -c
@@ -0,0 +1,4 @@
1
+ SimpleCov.start do
2
+ add_filter '/spec/'
3
+ minimum_coverage 100
4
+ end
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.9.2
6
+ - ruby-head
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
- TODO: Write a gem description
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
- Or install it yourself as:
34
+ ## Usage
16
35
 
17
- $ gem install silver_spurs
36
+ ### Kick off a bootstrap
18
37
 
19
- ## Usage
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
- TODO: Write usage instructions here
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. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
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: #{logger_command}"
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
- `ps -o command -p #{pid}`.split("\n").count == 2
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
- reap_lock_if_done process_name
66
+ reap_orphaned_lock process_name
67
67
  end
68
68
 
69
69
  def reap_old_process(process_name)
@@ -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) do |response, &block|
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
@@ -39,6 +39,7 @@ module SilverSpurs
39
39
  end
40
40
 
41
41
  private
42
+
42
43
  def no_exception_for_550(response, origin, orig_result, &block)
43
44
  if response.code == 550
44
45
  response
@@ -49,7 +50,3 @@ module SilverSpurs
49
50
 
50
51
  end
51
52
  end
52
-
53
-
54
-
55
-
@@ -37,7 +37,7 @@ module SilverSpurs
37
37
 
38
38
  arguments = expand_bootstrap_args bootstrap_options
39
39
 
40
- command = ['knife', 'bootstrap', *arguments, ip].join ' '
40
+ command = ['knife', 'bootstrap', '--no-host-key-verify', *arguments, ip].join ' '
41
41
  end
42
42
 
43
43
  end
@@ -1,3 +1,3 @@
1
1
  module SilverSpurs
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
@@ -26,5 +26,6 @@ Gem::Specification.new do |gem|
26
26
  gem.add_development_dependency 'growl'
27
27
  gem.add_development_dependency 'rb-fsevent', '~> 0.9'
28
28
  gem.add_development_dependency 'rack-test'
29
+ gem.add_development_dependency 'rake'
29
30
 
30
31
  end
@@ -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::KnifeInterface.stub(:bootstrap).and_return({:exit_code => 0, :log_lines => "I'm a test!"})
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 201
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 201
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 201
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 201
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 201
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