amiral 0.1.7 → 0.1.8
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/amiral.gemspec +0 -1
- data/bin/amiral.rb +5 -29
- data/lib/amiral/agent.rb +119 -0
- data/lib/amiral/matcher.rb +65 -0
- data/lib/amiral/message.rb +39 -0
- data/lib/amiral/providers/apt-update.rb +22 -0
- data/lib/amiral/providers/facts.rb +16 -0
- data/lib/amiral/providers/invalid.rb +9 -0
- data/lib/amiral/providers/ping.rb +9 -0
- data/lib/amiral/providers/provider-list.rb +9 -0
- data/lib/amiral/providers/puppet-agent.rb +34 -0
- data/lib/amiral/providers/service.rb +31 -0
- data/lib/amiral/providers/uptime.rb +28 -0
- data/lib/amiral/providers.rb +22 -0
- data/lib/amiral/version.rb +1 -1
- data/lib/amiral.rb +4 -351
- metadata +14 -18
data/amiral.gemspec
CHANGED
data/bin/amiral.rb
CHANGED
@@ -4,7 +4,6 @@ lib = File.expand_path('../../lib', __FILE__)
|
|
4
4
|
$:.unshift(lib) unless $:.include?(lib)
|
5
5
|
|
6
6
|
require 'rubygems'
|
7
|
-
require 'redis'
|
8
7
|
require 'amiral'
|
9
8
|
require 'optparse'
|
10
9
|
require 'ostruct'
|
@@ -14,8 +13,8 @@ options = OpenStruct.new
|
|
14
13
|
options.logfile = "amiral.log"
|
15
14
|
options.host = "localhost"
|
16
15
|
options.port = 6379
|
17
|
-
options.match_spec = {:all => true}
|
18
16
|
options.loglevel = Logger::INFO
|
17
|
+
options.prefix = "amiral:"
|
19
18
|
|
20
19
|
opts = OptionParser.new do |opts|
|
21
20
|
opts.banner = "Usage: amiral-agent [options]"
|
@@ -55,40 +54,17 @@ opts = OptionParser.new do |opts|
|
|
55
54
|
options.port = port
|
56
55
|
end
|
57
56
|
|
58
|
-
opts.on("-
|
59
|
-
"redis
|
60
|
-
options.
|
61
|
-
end
|
62
|
-
|
63
|
-
opts.on("-q", "--response-queue [queue]", String,
|
64
|
-
"redis response queue name") do |rqueue|
|
65
|
-
options.queue_response = rqueue
|
66
|
-
end
|
67
|
-
|
68
|
-
opts.on("-a", "--ack-queue [queue]", String,
|
69
|
-
"redis acknowledgement queue name") do |aqueue|
|
70
|
-
options.queue_ack = aqueue
|
57
|
+
opts.on("-P", "--prefix [prefix]", String,
|
58
|
+
"redis key prefixes") do |prefix|
|
59
|
+
options.prefix = prefix
|
71
60
|
end
|
72
61
|
|
73
62
|
opts.on("-l", "--log [logfile]", String, "log file path") do |logfile|
|
74
63
|
options.logfile = logfile
|
75
64
|
end
|
76
65
|
|
77
|
-
opts.on("-m", "--match [filter]", String, "node match filter") do |matcher|
|
78
|
-
options.match_spec = Amiral::Matcher.new(matcher).spec
|
79
|
-
end
|
80
|
-
|
81
|
-
opts.on("-T", "--timeouts [timeouts]", String,
|
82
|
-
"acknowledgement and response timeouts") do |timeouts|
|
83
|
-
tab = timeouts.split(',')
|
84
|
-
raise "timeouts need two values" unless tab.length == 2
|
85
|
-
options.ack_timeout = tab[0].to_i
|
86
|
-
options.reponse_timeout = tab[1].to_i
|
87
|
-
end
|
88
66
|
end.parse!
|
89
67
|
|
90
|
-
options.redis_in = Redis.new :host => options.host, :port => options.port, :driver => :hiredis
|
91
|
-
options.redis_out = Redis.new :host => options.host, :port => options.port, :driver => :hiredis
|
92
68
|
|
93
69
|
classes = {
|
94
70
|
'agent' => Amiral::Agent
|
@@ -98,4 +74,4 @@ role = ARGV.shift
|
|
98
74
|
|
99
75
|
raise "no such role: #{role}" unless klass = classes[role]
|
100
76
|
|
101
|
-
klass.new(options.marshal_dump).tap{|o| o.run(ARGV)}
|
77
|
+
klass.new(options.marshal_dump).tap{|o| o.run(ARGV)}
|
data/lib/amiral/agent.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'openssl'
|
3
|
+
require 'logger'
|
4
|
+
require 'redis'
|
5
|
+
|
6
|
+
module Amiral
|
7
|
+
KEYTYPES = {
|
8
|
+
:dss => OpenSSL::PKey::DSA,
|
9
|
+
:rsa => OpenSSL::PKey::RSA
|
10
|
+
}
|
11
|
+
|
12
|
+
class Agent
|
13
|
+
include Message
|
14
|
+
|
15
|
+
def initialize settings
|
16
|
+
@settings = {
|
17
|
+
:prefix => "amiral:",
|
18
|
+
:keytype => :dss,
|
19
|
+
:hostname => hostname,
|
20
|
+
:loglevel => Logger::INFO,
|
21
|
+
:logfile => "amiral.log",
|
22
|
+
:host => "localhost",
|
23
|
+
:port => 6379
|
24
|
+
}.merge settings
|
25
|
+
|
26
|
+
@logger = Logger.new(@settings[:logfile])
|
27
|
+
@logger.level = @settings[:loglevel]
|
28
|
+
|
29
|
+
raise "need key path" unless @settings[:keypath]
|
30
|
+
|
31
|
+
@privkey = File.open @settings[:keypath] do |file|
|
32
|
+
KEYTYPES[@settings[:keytype]].new file
|
33
|
+
end
|
34
|
+
|
35
|
+
@redis_in = Redis.new :host => @settings[:host],
|
36
|
+
:port => @settings[:port],
|
37
|
+
:driver => :hiredis,
|
38
|
+
:logger => @logger
|
39
|
+
@redis_out = Redis.new :host => @settings[:host],
|
40
|
+
:port => @settings[:port],
|
41
|
+
:driver => :hiredis,
|
42
|
+
:logger => @logger
|
43
|
+
end
|
44
|
+
|
45
|
+
def run args
|
46
|
+
begin
|
47
|
+
listen
|
48
|
+
rescue Exception => err
|
49
|
+
@logger.fatal("caught exception, exiting")
|
50
|
+
@logger.fatal(err)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def listen
|
55
|
+
begin
|
56
|
+
prefix = @settings[:prefix]
|
57
|
+
@redis_in.subscribe "#{prefix}req" do |on|
|
58
|
+
|
59
|
+
|
60
|
+
on.message do |x, payload|
|
61
|
+
|
62
|
+
@logger.debug "incoming message"
|
63
|
+
message = deserialize payload
|
64
|
+
|
65
|
+
if agent_matches? message['match']
|
66
|
+
@logger.info "valid request type: #{message['command']['provider']}"
|
67
|
+
uuid = uuidgen
|
68
|
+
send_ack(:in_reply_to => message['reply_to'],
|
69
|
+
:uuid => uuid,
|
70
|
+
:status => "start")
|
71
|
+
begin
|
72
|
+
out = execute message
|
73
|
+
rescue Exception => e
|
74
|
+
@logger.error "provider crashed: #{e}"
|
75
|
+
@logger.error e
|
76
|
+
end
|
77
|
+
send_response(:in_reply_to => message['reply_to'],
|
78
|
+
:uuid => uuid,
|
79
|
+
:status => "complete",
|
80
|
+
:output => out)
|
81
|
+
else
|
82
|
+
send_ack(:in_reply_to => message['reply_to'],
|
83
|
+
:status => "noop")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
rescue Redis::BaseConnectionError => error
|
88
|
+
@logger.error "connection hung up: #{error}"
|
89
|
+
sleep 1
|
90
|
+
retry
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def agent_matches? matcher
|
95
|
+
m = Matcher.new matcher
|
96
|
+
m.match
|
97
|
+
end
|
98
|
+
|
99
|
+
def execute message
|
100
|
+
(PROVIDERS[message['command']['provider']] ||
|
101
|
+
Amiral::Providers::Invalid).new.execute message
|
102
|
+
end
|
103
|
+
|
104
|
+
def send_ack response
|
105
|
+
send_payload "ack", response
|
106
|
+
end
|
107
|
+
|
108
|
+
def send_response response
|
109
|
+
send_payload "res", response
|
110
|
+
end
|
111
|
+
|
112
|
+
def send_payload type, response
|
113
|
+
uuid = response[:in_reply_to]
|
114
|
+
response[:hostname] = @settings[:hostname]
|
115
|
+
prefix = @settings[:prefix]
|
116
|
+
@redis_out.publish "#{prefix}#{type}:#{uuid}", serialize(response)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'facter'
|
3
|
+
|
4
|
+
module Amiral
|
5
|
+
class Matcher
|
6
|
+
|
7
|
+
attr_accessor :spec
|
8
|
+
|
9
|
+
def match
|
10
|
+
return true if (@spec.has_key? :all && @spec[:all])
|
11
|
+
|
12
|
+
if @spec.has_key? :hostname
|
13
|
+
return false unless `hostname`.chomp =~ Regexp.new(@spec[:hostname])
|
14
|
+
end
|
15
|
+
|
16
|
+
## XXX: platform
|
17
|
+
|
18
|
+
if @spec.has_key? :provider
|
19
|
+
return false unless PROVIDERS.has_key? @spec[:provider]
|
20
|
+
end
|
21
|
+
|
22
|
+
if @spec.has_key? :facts
|
23
|
+
@spec[:facts].map do |k,v|
|
24
|
+
return false unless Facter.value(k).to_s =~ Regexp.new(v)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize match_spec
|
31
|
+
case match_spec.class.to_s
|
32
|
+
when "String"
|
33
|
+
@spec = match_spec.split(',').map do |expr|
|
34
|
+
(lval, rval) = expr.split('=', 2)
|
35
|
+
case lval
|
36
|
+
when 'all'
|
37
|
+
{ :all => true }
|
38
|
+
when /^fact:/
|
39
|
+
{ :facts => { lval.split(':', 2)[1] => rval } }
|
40
|
+
when /^hostname/
|
41
|
+
{ :hostname => rval }
|
42
|
+
when /^provider/
|
43
|
+
{ :provider => rval }
|
44
|
+
when /^platform/
|
45
|
+
{ :platform => rval }
|
46
|
+
else
|
47
|
+
raise "unsupported match filter type: #{lval}"
|
48
|
+
end
|
49
|
+
end.reduce do |e1,e2|
|
50
|
+
e1.merge(e2) do |e,c1,c2|
|
51
|
+
c1.merge c2
|
52
|
+
end
|
53
|
+
end
|
54
|
+
when "Hash"
|
55
|
+
@spec = match_spec.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
56
|
+
else
|
57
|
+
raise "don't know how to create match from #{match_spec} (#{match_spec.class})"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def serialize
|
62
|
+
@spec.to_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
require 'uuidtools'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Amiral
|
8
|
+
module Message
|
9
|
+
|
10
|
+
def serialize data
|
11
|
+
json = data.to_json
|
12
|
+
digest = OpenSSL::Digest::SHA1.digest(json)
|
13
|
+
sig = Base64.encode64(@privkey.syssign(digest)).chomp
|
14
|
+
"#{sig}:#{json}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize data
|
18
|
+
(sig, json) = data.split(':', 2)
|
19
|
+
raise "wrong format" unless (sig && json)
|
20
|
+
sig = Base64.decode64(sig)
|
21
|
+
unless @privkey.sysverify(OpenSSL::Digest::SHA1.digest(json), sig)
|
22
|
+
raise "invalid signature"
|
23
|
+
end
|
24
|
+
JSON.parse json
|
25
|
+
end
|
26
|
+
|
27
|
+
def hostname
|
28
|
+
`hostname`.strip
|
29
|
+
end
|
30
|
+
|
31
|
+
def uuidgen
|
32
|
+
UUIDTools::UUID.random_create.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def timestamp
|
36
|
+
Time.new.to_i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'popen4'
|
2
|
+
|
3
|
+
module Amiral
|
4
|
+
module Providers
|
5
|
+
class AptGetUpdate
|
6
|
+
def execute message
|
7
|
+
out = err = nil
|
8
|
+
status = POpen4::popen4("apt-get update"){|stdout,stderr,stdin,pid|
|
9
|
+
out = stdout.read
|
10
|
+
err = stderr.read
|
11
|
+
}
|
12
|
+
{
|
13
|
+
:exit => status.exitstatus,
|
14
|
+
:short => (status.exitstatus == 0) ? "apt-get update ran fine" :
|
15
|
+
"apt-get cannot perform update!",
|
16
|
+
:out => out,
|
17
|
+
:err => err
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'popen4'
|
2
|
+
|
3
|
+
module Amiral
|
4
|
+
module Providers
|
5
|
+
class PuppetAgent
|
6
|
+
def execute message
|
7
|
+
out = err = short = nil
|
8
|
+
status = POpen4::popen4("puppet agent -t"){|stdout,stderr,stdin,pid|
|
9
|
+
out = stdout.read
|
10
|
+
err = stderr.read
|
11
|
+
}
|
12
|
+
short = "unknown status"
|
13
|
+
case status.exitstatus
|
14
|
+
when 0
|
15
|
+
short = "nothing to do, i'm all good"
|
16
|
+
when 2
|
17
|
+
short = "successfully applied changes"
|
18
|
+
when 4
|
19
|
+
short = "encountered catalog application errors"
|
20
|
+
when 6
|
21
|
+
short = "errors encountered, managed to apply some changes"
|
22
|
+
end
|
23
|
+
|
24
|
+
{
|
25
|
+
:exit => ((status.exitstatus == 0)||(status.exitstatus == 2)) ? 0 : 1,
|
26
|
+
:status => status,
|
27
|
+
:short => short,
|
28
|
+
:out => out,
|
29
|
+
:err => err
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'popen4'
|
2
|
+
|
3
|
+
module Amiral
|
4
|
+
module Providers
|
5
|
+
class Service
|
6
|
+
def execute message
|
7
|
+
action = "reload"
|
8
|
+
service = message['command']['args'][0]
|
9
|
+
if message['command']['args'].length > 1
|
10
|
+
action = message['command']['args'][1]
|
11
|
+
end
|
12
|
+
|
13
|
+
raise "unknown service command" unless ["stop", "start", "restart", "status", "reload"].include? action
|
14
|
+
|
15
|
+
out = err = nil
|
16
|
+
status = POpen4::popen4("service #{service} #{action}"){|stdout, stderr, stdin, pid|
|
17
|
+
out = stdout.read
|
18
|
+
err = stderr.read
|
19
|
+
}
|
20
|
+
{
|
21
|
+
:exit => status.exitstatus,
|
22
|
+
:short => (status.exitstatus == 0) ?
|
23
|
+
"service #{service} #{action} successful" :
|
24
|
+
"service #{service} #{action} failed!",
|
25
|
+
:out => out,
|
26
|
+
:err => err
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Amiral
|
2
|
+
module Providers
|
3
|
+
class Uptime
|
4
|
+
|
5
|
+
PATTERN = /^([0-9:]+) up (.*),[ \t]+([0-9]+) users?,[ \t]+load average: ([0-9.]+), ([0-9.]+), ([0-9.]+)/
|
6
|
+
def execute message
|
7
|
+
uptime = `uptime`.strip
|
8
|
+
|
9
|
+
if uptime =~ PATTERN
|
10
|
+
{
|
11
|
+
:exit => 0,
|
12
|
+
:time => $1,
|
13
|
+
:since => $2,
|
14
|
+
:users => $3,
|
15
|
+
:averages => [$4, $5, $6],
|
16
|
+
:short => uptime,
|
17
|
+
}
|
18
|
+
else
|
19
|
+
{
|
20
|
+
:exit => 1,
|
21
|
+
:short => "could not parse uptime",
|
22
|
+
:data => {:uptime => uptime}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'amiral/providers/uptime'
|
2
|
+
require 'amiral/providers/puppet-agent'
|
3
|
+
require 'amiral/providers/service'
|
4
|
+
require 'amiral/providers/apt-update'
|
5
|
+
require 'amiral/providers/ping'
|
6
|
+
require 'amiral/providers/facts'
|
7
|
+
require 'amiral/providers/provider-list'
|
8
|
+
require 'amiral/providers/invalid'
|
9
|
+
|
10
|
+
|
11
|
+
module Amiral
|
12
|
+
PROVIDERS = {
|
13
|
+
"uptime" => Amiral::Providers::Uptime,
|
14
|
+
"facts" => Amiral::Providers::Facts,
|
15
|
+
"provider-list" => Amiral::Providers::ProviderList,
|
16
|
+
"ping" => Amiral::Providers::Ping,
|
17
|
+
"puppet-agent" => Amiral::Providers::PuppetAgent,
|
18
|
+
"apt-update" => Amiral::Providers::AptGetUpdate,
|
19
|
+
"service" => Amiral::Providers::Service,
|
20
|
+
"invalid" => Amiral::Providers::Invalid
|
21
|
+
}
|
22
|
+
end
|
data/lib/amiral/version.rb
CHANGED
data/lib/amiral.rb
CHANGED
@@ -1,354 +1,7 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require 'json'
|
6
|
-
require 'facter'
|
7
|
-
require 'uuidtools'
|
8
|
-
require 'awesome_print'
|
9
|
-
require 'logger'
|
10
|
-
require 'popen4'
|
1
|
+
require 'amiral/providers'
|
2
|
+
require 'amiral/matcher'
|
3
|
+
require 'amiral/message'
|
4
|
+
require 'amiral/agent'
|
11
5
|
|
12
6
|
module Amiral
|
13
|
-
KEYTYPES = {
|
14
|
-
:dss => OpenSSL::PKey::DSA,
|
15
|
-
:rsa => OpenSSL::PKey::RSA
|
16
|
-
}
|
17
|
-
|
18
|
-
module Providers
|
19
|
-
class Uptime
|
20
|
-
|
21
|
-
PATTERN = /^([0-9:]+) up (.*),[ \t]+([0-9]+) users?,[ \t]+load average: ([0-9.]+), ([0-9.]+), ([0-9.]+)/
|
22
|
-
def execute message
|
23
|
-
uptime = `uptime`.strip
|
24
|
-
|
25
|
-
if uptime =~ PATTERN
|
26
|
-
{
|
27
|
-
:exit => 0,
|
28
|
-
:time => $1,
|
29
|
-
:since => $2,
|
30
|
-
:users => $3,
|
31
|
-
:averages => [$4, $5, $6],
|
32
|
-
:short => uptime,
|
33
|
-
}
|
34
|
-
else
|
35
|
-
{
|
36
|
-
:exit => 1,
|
37
|
-
:short => "could not parse uptime",
|
38
|
-
:data => {:uptime => uptime}
|
39
|
-
}
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class PuppetAgent
|
45
|
-
def execute message
|
46
|
-
out = err = short = nil
|
47
|
-
status = POpen4::popen4("puppet agent -t"){|stdout,stderr,stdin,pid|
|
48
|
-
out = stdout.read
|
49
|
-
err = stderr.read
|
50
|
-
}
|
51
|
-
short = "unknown status"
|
52
|
-
case status.exitstatus
|
53
|
-
when 0
|
54
|
-
short = "nothing to do, i'm all good"
|
55
|
-
when 2
|
56
|
-
short = "successfully applied changes"
|
57
|
-
when 4
|
58
|
-
short = "encountered catalog application errors"
|
59
|
-
when 6
|
60
|
-
short = "errors encountered, managed to apply some changes"
|
61
|
-
end
|
62
|
-
|
63
|
-
{
|
64
|
-
:exit => ((status.exitstatus == 0)||(status.exitstatus == 2)) ? 0 : 1,
|
65
|
-
:status => status,
|
66
|
-
:short => short,
|
67
|
-
:out => out,
|
68
|
-
:err => err
|
69
|
-
}
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class AptGetUpdate
|
74
|
-
def execute message
|
75
|
-
out = err = nil
|
76
|
-
status = POpen4::popen4("apt-get update"){|stdout,stderr,stdin,pid|
|
77
|
-
out = stdout.read
|
78
|
-
err = stderr.read
|
79
|
-
}
|
80
|
-
{
|
81
|
-
:exit => status.exitstatus,
|
82
|
-
:short => (status.exitstatus == 0) ? "apt-get update ran fine" :
|
83
|
-
"apt-get cannot perform update!",
|
84
|
-
:out => out,
|
85
|
-
:err => err
|
86
|
-
}
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class Service
|
91
|
-
def execute message
|
92
|
-
action = "reload"
|
93
|
-
service = message['command']['args'][0]
|
94
|
-
if message['command']['args'].length > 1
|
95
|
-
action = message['command']['args'][1]
|
96
|
-
end
|
97
|
-
|
98
|
-
raise "unknown service command" unless ["stop", "start", "restart", "status", "reload"].include? action
|
99
|
-
|
100
|
-
out = err = nil
|
101
|
-
status = POpen4::popen4("service #{service} #{action}"){|stdout, stderr, stdin, pid|
|
102
|
-
out = stdout.read
|
103
|
-
err = stderr.read
|
104
|
-
}
|
105
|
-
{
|
106
|
-
:exit => status.exitstatus,
|
107
|
-
:short => (status.exitstatus == 0) ?
|
108
|
-
"service #{service} #{action} successful" :
|
109
|
-
"service #{service} #{action} failed!",
|
110
|
-
:out => out,
|
111
|
-
:err => err
|
112
|
-
}
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class Facts
|
117
|
-
def execute message
|
118
|
-
facts = Facter.to_hash
|
119
|
-
{
|
120
|
-
:exit => 0,
|
121
|
-
:facts => facts,
|
122
|
-
:short => "returned #{facts.length} facts"
|
123
|
-
}
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class Ping
|
128
|
-
def execute message
|
129
|
-
{:exit => 0, :short => "alive"}
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
class ProviderList
|
134
|
-
def execute message
|
135
|
-
{:exit => 0, :short => PROVIDERS.keys}
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
class Invalid
|
140
|
-
def execute message
|
141
|
-
{:exit => 1, :short => "invalid provider requested"}
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
PROVIDERS = {
|
147
|
-
"uptime" => Amiral::Providers::Uptime,
|
148
|
-
"facts" => Amiral::Providers::Facts,
|
149
|
-
"provider_list" => Amiral::Providers::ProviderList,
|
150
|
-
"ping" => Amiral::Providers::Ping,
|
151
|
-
"puppet-agent" => Amiral::Providers::PuppetAgent,
|
152
|
-
"apt-update" => Amiral::Providers::AptGetUpdate,
|
153
|
-
"service" => Amiral::Providers::Service,
|
154
|
-
"invalid" => Amiral::Providers::Invalid
|
155
|
-
}
|
156
|
-
|
157
|
-
module Message
|
158
|
-
|
159
|
-
def serialize data
|
160
|
-
json = data.to_json
|
161
|
-
digest = OpenSSL::Digest::SHA1.digest(json)
|
162
|
-
sig = Base64.encode64(@privkey.syssign(digest)).chomp
|
163
|
-
"#{sig}:#{json}"
|
164
|
-
end
|
165
|
-
|
166
|
-
def deserialize data
|
167
|
-
(sig, json) = data.split(':', 2)
|
168
|
-
raise "wrong format" unless (sig && json)
|
169
|
-
sig = Base64.decode64(sig)
|
170
|
-
unless @privkey.sysverify(OpenSSL::Digest::SHA1.digest(json), sig)
|
171
|
-
raise "invalid signature"
|
172
|
-
end
|
173
|
-
JSON.parse json
|
174
|
-
end
|
175
|
-
|
176
|
-
def hostname
|
177
|
-
`hostname`.strip
|
178
|
-
end
|
179
|
-
|
180
|
-
def uuidgen
|
181
|
-
UUIDTools::UUID.random_create.to_s
|
182
|
-
end
|
183
|
-
|
184
|
-
def timestamp
|
185
|
-
Time.new.to_i
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
class Matcher
|
190
|
-
|
191
|
-
attr_accessor :spec
|
192
|
-
|
193
|
-
def match
|
194
|
-
return true if (@spec.has_key? :all && @spec[:all])
|
195
|
-
|
196
|
-
if @spec.has_key? :hostname
|
197
|
-
return false unless `hostname`.chomp =~ Regexp.new(@spec[:hostname])
|
198
|
-
end
|
199
|
-
|
200
|
-
## XXX: platform
|
201
|
-
|
202
|
-
if @spec.has_key? :provider
|
203
|
-
return false unless PROVIDERS.has_key? @spec[:provider]
|
204
|
-
end
|
205
|
-
|
206
|
-
if @spec.has_key? :facts
|
207
|
-
@spec[:facts].map do |k,v|
|
208
|
-
return false unless Facter.value(k).to_s =~ Regexp.new(v)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
true
|
212
|
-
end
|
213
|
-
|
214
|
-
def initialize match_spec
|
215
|
-
case match_spec.class.to_s
|
216
|
-
when "String"
|
217
|
-
@spec = match_spec.split(',').map do |expr|
|
218
|
-
(lval, rval) = expr.split('=', 2)
|
219
|
-
case lval
|
220
|
-
when 'all'
|
221
|
-
{ :all => true }
|
222
|
-
when /^fact:/
|
223
|
-
{ :facts => { lval.split(':', 2)[1] => rval } }
|
224
|
-
when /^hostname/
|
225
|
-
{ :hostname => rval }
|
226
|
-
when /^provider/
|
227
|
-
{ :provider => rval }
|
228
|
-
when /^platform/
|
229
|
-
{ :platform => rval }
|
230
|
-
else
|
231
|
-
raise "unsupported match filter type: #{lval}"
|
232
|
-
end
|
233
|
-
end.reduce do |e1,e2|
|
234
|
-
e1.merge(e2) do |e,c1,c2|
|
235
|
-
c1.merge c2
|
236
|
-
end
|
237
|
-
end
|
238
|
-
when "Hash"
|
239
|
-
@spec = match_spec.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
240
|
-
else
|
241
|
-
raise "don't know how to create match from #{match_spec} (#{match_spec.class})"
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
def serialize
|
246
|
-
@spec.to_json
|
247
|
-
end
|
248
|
-
|
249
|
-
end
|
250
|
-
|
251
|
-
class Agent
|
252
|
-
include Message
|
253
|
-
|
254
|
-
def initialize settings
|
255
|
-
@settings = {
|
256
|
-
:prefix => "amiral:",
|
257
|
-
:keytype => :dss,
|
258
|
-
:hostname => hostname,
|
259
|
-
:loglevel => Logger::INFO,
|
260
|
-
:logfile => "amiral.log",
|
261
|
-
}.merge settings
|
262
|
-
|
263
|
-
@logger = Logger.new(@settings[:logfile])
|
264
|
-
@logger.level = @settings[:loglevel]
|
265
|
-
|
266
|
-
raise "need key path" unless @settings[:keypath]
|
267
|
-
|
268
|
-
@privkey = File.open @settings[:keypath] do |file|
|
269
|
-
KEYTYPES[@settings[:keytype]].new file
|
270
|
-
end
|
271
|
-
|
272
|
-
@redis_in = @settings[:redis_in]
|
273
|
-
@redis_out = @settings[:redis_out]
|
274
|
-
end
|
275
|
-
|
276
|
-
def run args
|
277
|
-
begin
|
278
|
-
listen
|
279
|
-
rescue Exception => err
|
280
|
-
@logger.fatal("caught exception, exiting")
|
281
|
-
@logger.fatal(err)
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
def show
|
286
|
-
end
|
287
|
-
|
288
|
-
def listen
|
289
|
-
begin
|
290
|
-
prefix = @settings[:prefix]
|
291
|
-
@redis_in.subscribe "#{prefix}req" do |on|
|
292
|
-
|
293
|
-
|
294
|
-
on.message do |x, payload|
|
295
|
-
|
296
|
-
@logger.debug "incoming message"
|
297
|
-
message = deserialize payload
|
298
|
-
|
299
|
-
if agent_matches? message['match']
|
300
|
-
@logger.info "valid request type: #{message['command']['provider']}"
|
301
|
-
uuid = uuidgen
|
302
|
-
send_ack(:in_reply_to => message['reply_to'],
|
303
|
-
:uuid => uuid,
|
304
|
-
:status => "start")
|
305
|
-
begin
|
306
|
-
out = execute message
|
307
|
-
rescue Exception => e
|
308
|
-
@logger.error "provider crashed: #{e}"
|
309
|
-
@logger.error e
|
310
|
-
end
|
311
|
-
send_response(:in_reply_to => message['reply_to'],
|
312
|
-
:uuid => uuid,
|
313
|
-
:status => "complete",
|
314
|
-
:output => out)
|
315
|
-
else
|
316
|
-
send_ack(:in_reply_to => message['reply_to'],
|
317
|
-
:status => "noop")
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
rescue Redis::BaseConnectionError => error
|
322
|
-
@logger.error "connection hung up: #{error}"
|
323
|
-
sleep 1
|
324
|
-
retry
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
def agent_matches? matcher
|
329
|
-
m = Matcher.new matcher
|
330
|
-
m.match
|
331
|
-
end
|
332
|
-
|
333
|
-
def execute message
|
334
|
-
(PROVIDERS[message['command']['provider']] ||
|
335
|
-
Amiral::Providers::Invalid).new.execute message
|
336
|
-
end
|
337
|
-
|
338
|
-
def send_ack response
|
339
|
-
send_payload "ack", response
|
340
|
-
end
|
341
|
-
|
342
|
-
def send_response response
|
343
|
-
send_payload "res", response
|
344
|
-
end
|
345
|
-
|
346
|
-
def send_payload type, response
|
347
|
-
uuid = response[:in_reply_to]
|
348
|
-
response[:hostname] = @settings[:hostname]
|
349
|
-
prefix = @settings[:prefix]
|
350
|
-
@redis_out.publish "#{prefix}#{type}:#{uuid}", serialize(response)
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
7
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amiral
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
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: 2012-11-
|
12
|
+
date: 2012-11-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -59,22 +59,6 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: awesome_print
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ! '>='
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
type: :runtime
|
71
|
-
prerelease: false
|
72
|
-
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ! '>='
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '0'
|
78
62
|
- !ruby/object:Gem::Dependency
|
79
63
|
name: uuidtools
|
80
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,6 +123,18 @@ files:
|
|
139
123
|
- amiral.gemspec
|
140
124
|
- bin/amiral.rb
|
141
125
|
- lib/amiral.rb
|
126
|
+
- lib/amiral/agent.rb
|
127
|
+
- lib/amiral/matcher.rb
|
128
|
+
- lib/amiral/message.rb
|
129
|
+
- lib/amiral/providers.rb
|
130
|
+
- lib/amiral/providers/apt-update.rb
|
131
|
+
- lib/amiral/providers/facts.rb
|
132
|
+
- lib/amiral/providers/invalid.rb
|
133
|
+
- lib/amiral/providers/ping.rb
|
134
|
+
- lib/amiral/providers/provider-list.rb
|
135
|
+
- lib/amiral/providers/puppet-agent.rb
|
136
|
+
- lib/amiral/providers/service.rb
|
137
|
+
- lib/amiral/providers/uptime.rb
|
142
138
|
- lib/amiral/version.rb
|
143
139
|
homepage: ''
|
144
140
|
licenses: []
|