silver_spurs 0.0.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/silver_spurs.rb +1 -0
- data/lib/silver_spurs/app.rb +49 -11
- data/lib/silver_spurs/asyncifier.rb +100 -0
- data/lib/silver_spurs/client.rb +13 -5
- data/lib/silver_spurs/client/bootstrap_run.rb +55 -0
- data/lib/silver_spurs/knife_interface.rb +2 -24
- data/lib/silver_spurs/version.rb +1 -1
- metadata +4 -2
data/lib/silver_spurs.rb
CHANGED
data/lib/silver_spurs/app.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'sinatra/base'
|
2
2
|
require 'silver_spurs/knife_interface'
|
3
3
|
require 'json'
|
4
|
-
|
4
|
+
require 'silver_spurs/asyncifier'
|
5
5
|
|
6
6
|
module SilverSpurs
|
7
7
|
class App < Sinatra::Base
|
@@ -9,8 +9,8 @@ module SilverSpurs
|
|
9
9
|
set :deployment_key, "/etc/chef/deployment_key.pem"
|
10
10
|
set :deployment_user, "silverspurs"
|
11
11
|
# sane setting for AD subdomain
|
12
|
-
set :node_name_filter, /^[-A-Za-z0-9]{3,15}$/
|
13
|
-
|
12
|
+
set :node_name_filter, /^[-A-Za-z0-9]{3,15}$/
|
13
|
+
|
14
14
|
get '/' do
|
15
15
|
%q| Ride 'em, cowboy |
|
16
16
|
end
|
@@ -35,18 +35,56 @@ module SilverSpurs
|
|
35
35
|
node_name = params[:node_name].strip
|
36
36
|
return 406, {:bad_params => :node_name}.to_json unless node_name =~ settings.node_name_filter
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
process_name = "knife_bootstrap_#{params[:ip].strip.gsub '.', '_'}"
|
39
|
+
|
40
|
+
unless Asyncifier.has_lock? process_name
|
41
|
+
logger.info "Asynchronously spawning knife command. process_name = [#{process_name}]"
|
42
|
+
bootstrap_options = Hash[KnifeInterface.supported_arguments.map do |arg|
|
43
|
+
value = params[arg]
|
44
|
+
next if value.nil?
|
45
|
+
[arg, params[arg]]
|
46
|
+
end]
|
43
47
|
|
44
|
-
|
45
|
-
|
48
|
+
command = KnifeInterface.bootstrap_command(
|
49
|
+
params[:ip],
|
50
|
+
node_name,
|
51
|
+
settings.deployment_user,
|
52
|
+
settings.deployment_key,
|
53
|
+
bootstrap_options)
|
54
|
+
logger.debug "knife command: #{command}"
|
55
|
+
Asyncifier.spawn_process process_name, command
|
56
|
+
end
|
46
57
|
|
47
|
-
|
58
|
+
redirect to("/bootstrap/query/#{process_name}"), 303
|
48
59
|
end
|
49
60
|
|
61
|
+
head '/bootstrap/query/:process_id' do
|
62
|
+
return 404 unless Asyncifier.exists? params[:process_id]
|
63
|
+
Asyncifier.reap_old_process params[:process_id]
|
64
|
+
return 202 if Asyncifier.has_lock? params[:process_id]
|
65
|
+
if Asyncifier.success? params[:process_id]
|
66
|
+
return 201
|
67
|
+
else
|
68
|
+
return 550
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
get '/bootstrap/query/:process_id' do
|
73
|
+
return 404 unless Asyncifier.exists? params[:process_id]
|
74
|
+
Asyncifier.reap_old_process params[:process_id]
|
75
|
+
|
76
|
+
headers 'Content-Type' => 'text/plain'
|
77
|
+
body Asyncifier.get_log params[:process_id]
|
78
|
+
if Asyncifier.success? params[:process_id]
|
79
|
+
status 201
|
80
|
+
elsif Asyncifier.has_lock? params[:process_id]
|
81
|
+
status 202
|
82
|
+
else
|
83
|
+
status 550
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
50
88
|
def required_vars?(params, requirement_list)
|
51
89
|
requirement_list.none? { |required_param| params[required_param].nil? }
|
52
90
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module SilverSpurs
|
5
|
+
class Asyncifier
|
6
|
+
include Singleton
|
7
|
+
extend SingleForwardable
|
8
|
+
|
9
|
+
attr_writer :base_path, :timeout
|
10
|
+
def_delegators :instance, :has_lock?, :base_path, :base_path=, :timeout=, :timeout, :spawn_process, :has_lock?, :success?, :get_log, :reap_orphaned_lock, :reap_process, :reap_old_process, :exists?
|
11
|
+
|
12
|
+
def timeout
|
13
|
+
@timeout ||= 60 * 60
|
14
|
+
end
|
15
|
+
|
16
|
+
def base_path
|
17
|
+
@base_path ||= './silver_spurs_async'
|
18
|
+
end
|
19
|
+
|
20
|
+
def spawn_process(process_name, command)
|
21
|
+
create_directory_tree
|
22
|
+
logged_command = "#{command} &> #{log_file_path process_name} && touch #{success_file_path process_name}"
|
23
|
+
pid = Process.spawn logged_command
|
24
|
+
File.open(pid_file_path(process_name), 'wb') { |f| f.write pid }
|
25
|
+
Process.detach pid
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_lock?(process_name)
|
29
|
+
return false unless File.exists? pid_file_path(process_name)
|
30
|
+
|
31
|
+
pid = File.read(pid_file_path(process_name)).to_i
|
32
|
+
`ps -o command -p #{pid}`.split("\n").count == 2
|
33
|
+
end
|
34
|
+
|
35
|
+
def success?(process_name)
|
36
|
+
return false unless File.exists? success_file_path(process_name)
|
37
|
+
return true unless File.exists? pid_file_path(process_name)
|
38
|
+
File.mtime(success_file_path(process_name)) >= File.mtime(pid_file_path(process_name))
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_log(process_name)
|
42
|
+
return nil unless File.exists? log_file_path(process_name)
|
43
|
+
File.read log_file_path(process_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def reap_orphaned_lock(process_name)
|
47
|
+
File.delete pid_file_path(process_name) unless has_lock? process_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def reap_process(process_name)
|
51
|
+
if has_lock? process_name
|
52
|
+
pid = File.read(pid_file_path(process_name)).to_i
|
53
|
+
Process.kill 'KILL', pid
|
54
|
+
sleep 1
|
55
|
+
Process.kill('TERM', pid) if has_lock? process_name
|
56
|
+
end
|
57
|
+
reap_lock_if_done process_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def reap_old_process(process_name)
|
61
|
+
if has_lock? process_name
|
62
|
+
launch_time = File.mtime pid_file_path(process_name)
|
63
|
+
reap_process process_name if Time.now - launch_time > timeout
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def exists?(process_name)
|
68
|
+
return true if has_lock? process_name
|
69
|
+
File.exists? log_file_path(process_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def log_file_path(process_name)
|
75
|
+
filename = "#{process_name}.log"
|
76
|
+
File.join base_path, 'logs', filename
|
77
|
+
end
|
78
|
+
|
79
|
+
def success_file_path(process_name)
|
80
|
+
filename = "#{process_name}.success"
|
81
|
+
File.join base_path, 'status', filename
|
82
|
+
end
|
83
|
+
|
84
|
+
def pid_file_path(process_name)
|
85
|
+
filename = "#{process_name}.pid"
|
86
|
+
File.join base_path, 'lockfiles', filename
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_directory_tree
|
90
|
+
Dir.mkdir base_path unless Dir.exists? base_path
|
91
|
+
|
92
|
+
['logs', 'lockfiles', 'status'].each do |directory|
|
93
|
+
path = File.join(base_path, directory)
|
94
|
+
Dir.mkdir path unless Dir.exists? path
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
data/lib/silver_spurs/client.rb
CHANGED
@@ -3,23 +3,31 @@ require 'addressable/uri'
|
|
3
3
|
require 'json'
|
4
4
|
require 'silver_spurs/knife_interface'
|
5
5
|
require 'silver_spurs/client/exceptions'
|
6
|
+
require 'silver_spurs/client/bootstrap_run'
|
6
7
|
|
7
8
|
module SilverSpurs
|
8
9
|
class Client
|
9
10
|
def initialize(host_url, options={})
|
10
11
|
@host_url = host_url
|
11
|
-
@timeout = options[:timeout] ||
|
12
|
+
@timeout = options[:timeout] || 2 * 60
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
+
def start_bootstrap(ip, node_name, options = {})
|
15
16
|
params = extract_extra_params(options).merge({:node_name => node_name})
|
16
17
|
payload = parameterize_hash params
|
17
18
|
headers = {:accept => :json, :content_type=> 'application/x-www-form-urlencoded'}
|
18
19
|
|
19
|
-
response = spur_host["bootstrap/#{ip}"].put(payload, headers)
|
20
|
-
|
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
|
27
|
+
|
28
|
+
throw ClientException.new("unexpected response", response) unless response.code == 303
|
21
29
|
|
22
|
-
|
30
|
+
BootstrapRun.new(response.headers[:location], :timeout => @timeout)
|
23
31
|
end
|
24
32
|
|
25
33
|
private
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'silver_spurs/client/exceptions'
|
3
|
+
|
4
|
+
module SilverSpurs
|
5
|
+
class BootstrapRun
|
6
|
+
def initialize(async_url, options={})
|
7
|
+
@async_url = async_url
|
8
|
+
@timeout = options[:timeout] || 2 * 60
|
9
|
+
end
|
10
|
+
|
11
|
+
def status
|
12
|
+
response = RestClient.head @async_url, &method(:no_exception_for_550)
|
13
|
+
|
14
|
+
case response.code
|
15
|
+
when 201
|
16
|
+
:success
|
17
|
+
when 202
|
18
|
+
:processing
|
19
|
+
when 550
|
20
|
+
:failed
|
21
|
+
when 404
|
22
|
+
throw ClientException.new("the server doesn't know anything about this knife run", response)
|
23
|
+
else
|
24
|
+
throw ClientException.new("unexpected response", response)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def log
|
29
|
+
response = RestClient.get @async_url, &method(:no_exception_for_550)
|
30
|
+
|
31
|
+
case response.code
|
32
|
+
when 201, 202, 550
|
33
|
+
response.body
|
34
|
+
when 404
|
35
|
+
throw ClientException.new("the server doesn't know anything about this knife run", response)
|
36
|
+
else
|
37
|
+
throw ClientException.new("unexpected response", response)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def no_exception_for_550(response, origin, orig_result, &block)
|
43
|
+
if response.code == 550
|
44
|
+
response
|
45
|
+
else
|
46
|
+
response.return! &block
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
@@ -28,7 +28,7 @@ module SilverSpurs
|
|
28
28
|
end.reject {|arg| arg.nil?}
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.
|
31
|
+
def self.bootstrap_command(ip, node_name, deployment_user, deployment_key, options = {})
|
32
32
|
bootstrap_options = {
|
33
33
|
:identity_file => deployment_key,
|
34
34
|
:ssh_user => deployment_user,
|
@@ -36,30 +36,8 @@ module SilverSpurs
|
|
36
36
|
}.merge options
|
37
37
|
|
38
38
|
arguments = expand_bootstrap_args bootstrap_options
|
39
|
-
logger.debug "Knife arguments: #{arguments.join ', '}"
|
40
|
-
|
41
|
-
strap_r, strap_w = IO.pipe
|
42
39
|
|
43
|
-
command = ['knife', 'bootstrap', *arguments, ip].join ' '
|
44
|
-
logger.debug "Knife command line: #{command}"
|
45
|
-
knife_pid = spawn(command, :err => :out, :out => strap_w)
|
46
|
-
|
47
|
-
Process.waitpid(knife_pid)
|
48
|
-
exitcode = $?.exitstatus
|
49
|
-
|
50
|
-
strap_w.close
|
51
|
-
loglines = strap_r.read
|
52
|
-
logger.debug "Knife log lines: #{loglines}"
|
53
|
-
strap_r.close
|
54
|
-
|
55
|
-
{
|
56
|
-
:exit_code => exitcode,
|
57
|
-
:log_lines => loglines
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.logger
|
62
|
-
@logger ||= Logger.new(STDERR)
|
40
|
+
command = ['knife', 'bootstrap', *arguments, ip].join ' '
|
63
41
|
end
|
64
42
|
|
65
43
|
end
|
data/lib/silver_spurs/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: silver_spurs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
@@ -187,7 +187,9 @@ files:
|
|
187
187
|
- Rakefile
|
188
188
|
- lib/silver_spurs.rb
|
189
189
|
- lib/silver_spurs/app.rb
|
190
|
+
- lib/silver_spurs/asyncifier.rb
|
190
191
|
- lib/silver_spurs/client.rb
|
192
|
+
- lib/silver_spurs/client/bootstrap_run.rb
|
191
193
|
- lib/silver_spurs/client/exceptions.rb
|
192
194
|
- lib/silver_spurs/knife_interface.rb
|
193
195
|
- lib/silver_spurs/version.rb
|