bixby-agent 0.3.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.
- 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
|
+
|