silver_spurs 0.0.8 → 1.0.0
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/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
|