silver_spurs 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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