bixby-agent 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.testguardrc +1 -0
- data/.travis.yml +25 -0
- data/Gemfile +61 -0
- data/Gemfile.lock +237 -0
- data/LICENSE +21 -0
- data/Rakefile +13 -0
- data/VERSION +1 -0
- data/bin/bixby-agent +15 -0
- data/bixby-agent.gemspec +186 -0
- data/etc/bixby-god.initd +70 -0
- data/etc/bixby.god +20 -0
- data/etc/god.d/bixby-agent.god +71 -0
- data/lib/bixby-agent.rb +16 -0
- data/lib/bixby-agent/agent.rb +98 -0
- data/lib/bixby-agent/agent/config.rb +109 -0
- data/lib/bixby-agent/agent/crypto.rb +73 -0
- data/lib/bixby-agent/agent/handshake.rb +81 -0
- data/lib/bixby-agent/agent/shell_exec.rb +111 -0
- data/lib/bixby-agent/agent_handler.rb +38 -0
- data/lib/bixby-agent/app.rb +208 -0
- data/lib/bixby-agent/app/cli.rb +112 -0
- data/lib/bixby-agent/config_exception.rb +5 -0
- data/lib/bixby-agent/help/system_time.rb +41 -0
- data/lib/bixby-agent/version.rb +8 -0
- data/lib/bixby-agent/websocket/client.rb +186 -0
- data/tasks/cane.rake +14 -0
- data/tasks/coverage.rake +2 -0
- data/tasks/coveralls.rake +11 -0
- data/tasks/jeweler.rake +21 -0
- data/tasks/test.rake +5 -0
- data/tasks/yard.rake +6 -0
- data/test/base.rb +92 -0
- data/test/helper.rb +29 -0
- data/test/stub_eventmachine.rb +38 -0
- data/test/support/root_dir/bixby.yml +8 -0
- data/test/support/root_dir/id_rsa +27 -0
- data/test/support/root_dir/server +27 -0
- data/test/support/root_dir/server.pub +9 -0
- data/test/support/test_bundle/bin/cat +2 -0
- data/test/support/test_bundle/bin/echo +2 -0
- data/test/support/test_bundle/digest +17 -0
- data/test/support/test_bundle/manifest.json +0 -0
- data/test/test_agent.rb +110 -0
- data/test/test_agent_exec.rb +53 -0
- data/test/test_agent_handler.rb +72 -0
- data/test/test_app.rb +138 -0
- data/test/test_bixby_common.rb +18 -0
- data/test/test_crypto.rb +78 -0
- data/test/websocket/test_client.rb +110 -0
- metadata +557 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module Bixby
|
3
|
+
module Help
|
4
|
+
class SystemTime
|
5
|
+
|
6
|
+
extend Bixby::Script::Distro
|
7
|
+
extend Bixby::Script::Platform
|
8
|
+
|
9
|
+
def self.print
|
10
|
+
$stderr.puts self.message
|
11
|
+
end
|
12
|
+
|
13
|
+
# Registration may fail if the system clock is too far in the past (more than 15 minutes)
|
14
|
+
# Show the user how to fix the issue and try again
|
15
|
+
def self.message
|
16
|
+
s = " it appears your system clock is out of sync"
|
17
|
+
|
18
|
+
res = HTTPI.get("http://google.com")
|
19
|
+
if not res.error? then
|
20
|
+
current_time = Time.parse(res.headers["Date"]).utc
|
21
|
+
s += " > current time: #{current_time}"
|
22
|
+
s += " > system time: #{Time.new.utc}"
|
23
|
+
end
|
24
|
+
|
25
|
+
$stderr.puts
|
26
|
+
if linux? && ubuntu? then
|
27
|
+
s += " to fix:"
|
28
|
+
s += " sudo apt-get install ntpdate && sudo ntpdate ntp.ubuntu.com"
|
29
|
+
elsif linux? && centos? then
|
30
|
+
s += " to fix:"
|
31
|
+
s += " sudo yum install ntpdate && sudo ntpdate ntp.ubuntu.com"
|
32
|
+
else
|
33
|
+
s += " you can fix this on most unix systems by running 'sudo ntpdate ntp.ubuntu.com'"
|
34
|
+
end
|
35
|
+
|
36
|
+
s
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
|
2
|
+
require 'bixby-agent/help/system_time'
|
3
|
+
|
4
|
+
require 'faye/websocket'
|
5
|
+
require 'eventmachine'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
module Bixby
|
9
|
+
module WebSocket
|
10
|
+
|
11
|
+
# WebSocket Client
|
12
|
+
class Client
|
13
|
+
|
14
|
+
include Bixby::Log
|
15
|
+
|
16
|
+
attr_reader :ws, :api
|
17
|
+
|
18
|
+
def initialize(url, handler)
|
19
|
+
@url = url
|
20
|
+
@handler = handler
|
21
|
+
@tries = 0
|
22
|
+
@exiting = false
|
23
|
+
end
|
24
|
+
|
25
|
+
# Start the Client thread
|
26
|
+
#
|
27
|
+
# NOTE: This call never returns!
|
28
|
+
def start
|
29
|
+
|
30
|
+
@exiting = false
|
31
|
+
|
32
|
+
Kernel.trap("EXIT") do
|
33
|
+
# :nocov:
|
34
|
+
@exiting = true
|
35
|
+
# :nocov:
|
36
|
+
end
|
37
|
+
|
38
|
+
logger.debug "connecting to #{@url}"
|
39
|
+
EM.run {
|
40
|
+
connect()
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
@exiting = true
|
46
|
+
EM.stop_event_loop if EM.reactor_running?
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Connect to the WebSocket endpoint given by @url. Will attempt to keep
|
53
|
+
# the connection open forever, reconnecting as needed.
|
54
|
+
def connect
|
55
|
+
|
56
|
+
# Ping is set to 55 sec to workaround issues with certain gateway devices which have a hard
|
57
|
+
# 60 sec timeout, like the AWS ELB:
|
58
|
+
# http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/ts-elb-healthcheck.html
|
59
|
+
@ws = Faye::WebSocket::Client.new(@url, nil, :ping => 55)
|
60
|
+
@api = Bixby::WebSocket::APIChannel.new(@ws, @handler)
|
61
|
+
|
62
|
+
ws.on :open do |e|
|
63
|
+
begin
|
64
|
+
authenticate(e)
|
65
|
+
rescue Exception => ex
|
66
|
+
logger.error ex
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ws.on :message do |e|
|
71
|
+
begin
|
72
|
+
api.message(e)
|
73
|
+
rescue Exception => ex
|
74
|
+
raise ex if ex.kind_of? SystemExit # possible when message is a connect() response
|
75
|
+
logger.error ex
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
ws.on(:close, &lambda { |e|
|
80
|
+
begin
|
81
|
+
was_connected = api.connected?
|
82
|
+
api.close(e)
|
83
|
+
return if @exiting or not EM.reactor_running?
|
84
|
+
if was_connected then
|
85
|
+
logger.info "lost connection to manager"
|
86
|
+
else
|
87
|
+
logger.debug "failed to connect"
|
88
|
+
end
|
89
|
+
reconnect()
|
90
|
+
rescue Exception => ex
|
91
|
+
logger.error ex
|
92
|
+
end
|
93
|
+
})
|
94
|
+
end
|
95
|
+
|
96
|
+
# Send a connection request to authenticate with the manager
|
97
|
+
def authenticate(e)
|
98
|
+
logger.debug "connected to manager, authenticating"
|
99
|
+
|
100
|
+
json_req = JsonRequest.new("", "")
|
101
|
+
signed_req = SignedJsonRequest.new(json_req, Bixby.agent.access_key, Bixby.agent.secret_key)
|
102
|
+
auth_req = Request.new(signed_req, SecureRandom.uuid, "connect")
|
103
|
+
|
104
|
+
id = api.execute_async(auth_req) do |ret|
|
105
|
+
if ret.success? then
|
106
|
+
logger.info "Successfully connected to manager at #{@url}"
|
107
|
+
api.open(e)
|
108
|
+
@tries = 0
|
109
|
+
|
110
|
+
else
|
111
|
+
if ret.message =~ /900 seconds old/ then
|
112
|
+
logger.error "error authenticating with manager:\n" + Help::SystemTime.message
|
113
|
+
else
|
114
|
+
logger.error "error authenticating with manager: #{ret.code} #{ret.message}"
|
115
|
+
end
|
116
|
+
logger.error "exiting since we failed to auth"
|
117
|
+
@exiting = true
|
118
|
+
exit 1 # bail out since we failed to connect, nothing to do
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Start a thread to watch for auth timeout
|
123
|
+
Thread.new do
|
124
|
+
begin
|
125
|
+
sec = 60
|
126
|
+
Timeout.timeout(sec) do
|
127
|
+
api.fetch_response(id) # blocks until request is completed
|
128
|
+
end
|
129
|
+
|
130
|
+
rescue Timeout::Error => ex
|
131
|
+
logger.warn("Authentication timed out after #{sec} seconds; trying again")
|
132
|
+
reconnect()
|
133
|
+
|
134
|
+
rescue Exception => ex
|
135
|
+
logger.error(ex)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
def cleanup
|
142
|
+
@api.close(nil) if @api
|
143
|
+
@ws.close if @ws
|
144
|
+
end
|
145
|
+
|
146
|
+
def reconnect
|
147
|
+
if backoff() then
|
148
|
+
cleanup()
|
149
|
+
connect()
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Delay reconnection by a slowly increasing interval
|
154
|
+
def backoff
|
155
|
+
|
156
|
+
if @exiting or not EM.reactor_running? then
|
157
|
+
# shutting down, don't try to reconnect
|
158
|
+
logger.debug "not retrying since we are shutting down"
|
159
|
+
return false
|
160
|
+
end
|
161
|
+
|
162
|
+
@tries += 1
|
163
|
+
if @tries == 1 then
|
164
|
+
logger.debug "retrying immediately"
|
165
|
+
|
166
|
+
# :nocov:
|
167
|
+
elsif @tries == 2 then
|
168
|
+
logger.debug "retrying every 1 sec"
|
169
|
+
sleep 1
|
170
|
+
elsif @tries <= 30 then
|
171
|
+
sleep 1
|
172
|
+
elsif @tries == 31 then
|
173
|
+
logger.debug "retrying every 5 sec"
|
174
|
+
sleep 5
|
175
|
+
else
|
176
|
+
sleep 5
|
177
|
+
end
|
178
|
+
# :nocov:
|
179
|
+
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
data/tasks/cane.rake
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'cane/rake_task'
|
4
|
+
|
5
|
+
desc "Run cane to check quality metrics"
|
6
|
+
Cane::RakeTask.new(:quality) do |cane|
|
7
|
+
cane.abc_max = 10
|
8
|
+
cane.add_threshold 'coverage/covered_percent', :>=, 99
|
9
|
+
cane.no_style = true
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :quality
|
13
|
+
rescue LoadError
|
14
|
+
end
|
data/tasks/coverage.rake
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
desc "Report coverage to coveralls"
|
3
|
+
task :coveralls do
|
4
|
+
require "easycov"
|
5
|
+
require "coveralls"
|
6
|
+
|
7
|
+
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
8
|
+
if File.exists? File.join(EasyCov.path, ".resultset.json") then
|
9
|
+
SimpleCov::ResultMerger.merged_result.format!
|
10
|
+
end
|
11
|
+
end
|
data/tasks/jeweler.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
require 'jeweler'
|
3
|
+
|
4
|
+
Jeweler::Tasks.new do |gemspec|
|
5
|
+
gemspec.name = "bixby-agent"
|
6
|
+
gemspec.summary = "Bixby Agent"
|
7
|
+
gemspec.description = "Bixby Agent"
|
8
|
+
gemspec.email = "chetan@pixelcop.net"
|
9
|
+
gemspec.homepage = "http://github.com/chetan/bixby-agent"
|
10
|
+
gemspec.authors = ["Chetan Sarva"]
|
11
|
+
gemspec.license = "MIT"
|
12
|
+
|
13
|
+
gemspec.executables = %w{ bixby-agent }
|
14
|
+
|
15
|
+
# exclude these bin scripts for now
|
16
|
+
%w{ ci.sh ci_setup.sh install.sh }.each do |f|
|
17
|
+
gemspec.files.exclude "bin/#{f}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/tasks/test.rake
ADDED
data/tasks/yard.rake
ADDED
data/test/base.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
|
2
|
+
require 'helper'
|
3
|
+
require 'micron/test_case/redir_logging'
|
4
|
+
|
5
|
+
module Bixby
|
6
|
+
module Test
|
7
|
+
|
8
|
+
class TestCase < Micron::TestCase
|
9
|
+
|
10
|
+
include Micron::TestCase::RedirLogging
|
11
|
+
self.redir_logger = Logging.logger[Bixby]
|
12
|
+
|
13
|
+
def setup
|
14
|
+
super
|
15
|
+
WebMock.reset!
|
16
|
+
|
17
|
+
@git_path = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
18
|
+
@support_path = File.join(@git_path, "test", "support")
|
19
|
+
|
20
|
+
@manager_uri = "http://localhost:3000"
|
21
|
+
@tenant = "pixelcop"
|
22
|
+
@password = "foobar"
|
23
|
+
@root_dir = Dir.mktmpdir("bixby-agent-test-")
|
24
|
+
@port = 9999
|
25
|
+
|
26
|
+
Bixby.manager_uri = @manager_uri
|
27
|
+
@api_url = @manager_uri + "/api"
|
28
|
+
`rm -rf #{@root_dir}`
|
29
|
+
|
30
|
+
vendor_path = File.join(@root_dir, "repo", "vendor")
|
31
|
+
`mkdir -p #{vendor_path}`
|
32
|
+
`cp -a #{File.join(@git_path, "repo/vendor/*")} #{vendor_path}`
|
33
|
+
|
34
|
+
ENV["BIXBY_NOCRYPTO"] = "1"
|
35
|
+
ENV["BIXBY_HOME"] = @root_dir
|
36
|
+
ARGV.clear
|
37
|
+
end
|
38
|
+
|
39
|
+
def teardown
|
40
|
+
`rm -rf #{@root_dir}`
|
41
|
+
@agent = nil
|
42
|
+
ENV["BIXBY_HOME"] = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def setup_existing_agent
|
46
|
+
ENV["BIXBY_HOME"] = @root_dir
|
47
|
+
src = File.join(@support_path, "root_dir")
|
48
|
+
dest = File.join(@root_dir, "etc")
|
49
|
+
FileUtils.mkdir_p(dest)
|
50
|
+
FileUtils.copy_entry(src, dest)
|
51
|
+
@agent = Agent.create
|
52
|
+
end
|
53
|
+
|
54
|
+
def setup_root
|
55
|
+
# copy repo to path
|
56
|
+
`mkdir -p #{@root_dir}/repo/support`
|
57
|
+
`cp -a #{@bundle_path} #{@root_dir}/repo/support/`
|
58
|
+
end
|
59
|
+
|
60
|
+
def setup_test_bundle(repo, bundle, command, digest=nil)
|
61
|
+
@bundle_path = File.join(@support_path, "test_bundle")
|
62
|
+
@c = CommandSpec.new({ :repo => repo, :bundle => bundle,
|
63
|
+
:command => command, :digest => digest })
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_new_agent
|
67
|
+
ENV["BIXBY_HOME"] = nil
|
68
|
+
@agent = Agent.create(@root_dir)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# common routines for crypto tests
|
73
|
+
|
74
|
+
def server_private_key
|
75
|
+
s = File.join(@root_dir, "etc", "server")
|
76
|
+
OpenSSL::PKey::RSA.new(File.read(s))
|
77
|
+
end
|
78
|
+
|
79
|
+
def encrypt_for_agent(msg)
|
80
|
+
Bixby::CryptoUtil.encrypt(msg, "server_uuid", @agent.private_key, server_private_key)
|
81
|
+
end
|
82
|
+
|
83
|
+
def decrypt_from_agent(data)
|
84
|
+
data = StringIO.new(data, 'rb')
|
85
|
+
uuid = data.readline.strip
|
86
|
+
Bixby::CryptoUtil.decrypt(data, server_private_key, @agent.private_key)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development, :test)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'micron/minitest'
|
12
|
+
|
13
|
+
# load curb first so webmock can stub it out as necessary
|
14
|
+
require 'curb'
|
15
|
+
require 'webmock'
|
16
|
+
include WebMock::API
|
17
|
+
require 'mocha/setup'
|
18
|
+
|
19
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
|
20
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
21
|
+
ENV["RUBYLIB"] = $:.first
|
22
|
+
require 'bixby-agent'
|
23
|
+
|
24
|
+
require "base"
|
25
|
+
Dir.glob(File.dirname(__FILE__) + "/../lib/**/*.rb").each{ |f| require f }
|
26
|
+
|
27
|
+
EasyCov.path = "coverage"
|
28
|
+
EasyCov.filters << EasyCov::IGNORE_GEMS << EasyCov::IGNORE_STDLIB
|
29
|
+
EasyCov.start
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module EventMachine
|
3
|
+
class << self
|
4
|
+
|
5
|
+
alias_method :_orig_run, :run
|
6
|
+
alias_method :_orig_next_tick, :next_tick
|
7
|
+
alias_method :_orig_stop, :stop
|
8
|
+
alias_method :_orig_stop_event_loop, :stop_event_loop
|
9
|
+
|
10
|
+
def run_immediately(&block)
|
11
|
+
@reactor_running = true
|
12
|
+
block.call() if block
|
13
|
+
end
|
14
|
+
|
15
|
+
def tick_immediately(&block)
|
16
|
+
block.call() if block
|
17
|
+
end
|
18
|
+
|
19
|
+
def noop
|
20
|
+
end
|
21
|
+
|
22
|
+
def stub!
|
23
|
+
define_singleton_method :run, method(:run_immediately)
|
24
|
+
define_singleton_method :next_tick, method(:tick_immediately)
|
25
|
+
define_singleton_method :stop, method(:noop)
|
26
|
+
define_singleton_method :stop_event_loop, method(:noop)
|
27
|
+
end
|
28
|
+
|
29
|
+
def disable_stub!
|
30
|
+
define_singleton_method :run, method(:_orig_run)
|
31
|
+
define_singleton_method :next_tick, method(:_orig_next_tick)
|
32
|
+
define_singleton_method :stop, method(:_orig_stop)
|
33
|
+
define_singleton_method :stop_event_loop, method(:_orig_stop_event_loop)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|