cloud66 0.0.26
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of cloud66 might be problematic. Click here for more details.
- data/bin/c66-agent +632 -0
- data/lib/client_auth.rb +32 -0
- data/lib/cloud-quartz.rb +64 -0
- data/lib/plugins/backup.rb +74 -0
- data/lib/plugins/broken.rb +19 -0
- data/lib/plugins/file_rotate.rb +66 -0
- data/lib/plugins/log_rotate.rb +65 -0
- data/lib/plugins/mysql_backup.rb +92 -0
- data/lib/plugins/quartz_plugin.rb +44 -0
- data/lib/plugins/rackspace_backup.rb +207 -0
- data/lib/plugins/rake.rb +29 -0
- data/lib/plugins/redis_backup.rb +95 -0
- data/lib/plugins/s3_backup.rb +200 -0
- data/lib/plugins/shell.rb +26 -0
- data/lib/plugins/tester.rb +22 -0
- data/lib/plugins/webget.rb +39 -0
- data/lib/version.rb +52 -0
- data/lib/vital_signs_utils.rb +161 -0
- metadata +207 -0
data/bin/c66-agent
ADDED
@@ -0,0 +1,632 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require File.expand_path("../../lib/cloud-quartz", __FILE__)
|
5
|
+
require File.expand_path('../../lib/version', __FILE__)
|
6
|
+
require File.expand_path("../../lib/client_auth", __FILE__)
|
7
|
+
require File.expand_path("../../lib/vital_signs_utils", __FILE__)
|
8
|
+
require 'optparse'
|
9
|
+
require 'socket'
|
10
|
+
require 'logger'
|
11
|
+
require 'highline/import'
|
12
|
+
|
13
|
+
begin
|
14
|
+
gem 'eventmachine', '~>1.0.0.beta.4'
|
15
|
+
gem 'faye', '~>0.8.3'
|
16
|
+
gem 'highline', '~>1.6.11'
|
17
|
+
|
18
|
+
require 'eventmachine'
|
19
|
+
require 'faye'
|
20
|
+
rescue LoadError => exc
|
21
|
+
warn "Cannot find required ruby gems needed to run this agent. Please install the cloud66 gem by running 'gem install cloud66'"
|
22
|
+
warn exc
|
23
|
+
exit -1
|
24
|
+
end
|
25
|
+
|
26
|
+
#statuses
|
27
|
+
ST_UNREGISTERED = 0
|
28
|
+
ST_STOPPED = 1
|
29
|
+
ST_STARTED = 2
|
30
|
+
|
31
|
+
#number of pulses between pulses that check for network settings
|
32
|
+
NETWORK_CHECK_PULSE_FREQUENCY = 5
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def safefork
|
37
|
+
tryagain = true
|
38
|
+
|
39
|
+
while tryagain
|
40
|
+
tryagain = false
|
41
|
+
begin
|
42
|
+
if pid = fork
|
43
|
+
return pid
|
44
|
+
end
|
45
|
+
rescue Errno::EWOULDBLOCK
|
46
|
+
sleep 5
|
47
|
+
tryagain = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def daemonize(oldmode=0, closefd=false)
|
53
|
+
srand # Split rand streams between spawning and daemonized process
|
54
|
+
safefork and exit # Fork and exit from the parent
|
55
|
+
|
56
|
+
# Detach from the controlling terminal
|
57
|
+
unless sess_id = Process.setsid
|
58
|
+
raise 'Cannot detach from controlled terminal'
|
59
|
+
end
|
60
|
+
|
61
|
+
# Prevent the possibility of acquiring a controlling terminal
|
62
|
+
if oldmode.zero?
|
63
|
+
trap 'SIGHUP', 'IGNORE'
|
64
|
+
exit if pid = safefork
|
65
|
+
end
|
66
|
+
|
67
|
+
Dir.chdir "/" # Release old working directory
|
68
|
+
File.umask 0000 # Insure sensible umask
|
69
|
+
|
70
|
+
if closefd
|
71
|
+
# Make sure all file descriptors are closed
|
72
|
+
ObjectSpace.each_object(IO) do |io|
|
73
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
74
|
+
io.close rescue nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
STDIN.reopen "/dev/null" # Free file descriptors and
|
80
|
+
STDOUT.reopen "/dev/null", "a" # point them somewhere sensible
|
81
|
+
STDERR.reopen STDOUT # STDOUT/STDERR should go to a logfile
|
82
|
+
return oldmode ? sess_id : 0 # Return value is mostly irrelevant
|
83
|
+
end
|
84
|
+
|
85
|
+
def save_config
|
86
|
+
Dir.mkdir(@config_dir) if !FileTest::directory?(@config_dir)
|
87
|
+
File.open(@config_full, 'w+') do |out|
|
88
|
+
data = {
|
89
|
+
'api_key' => @api_key,
|
90
|
+
'agent_id' => @agent_id,
|
91
|
+
'secret_key' => @secret_key
|
92
|
+
}
|
93
|
+
# store the url if it is different
|
94
|
+
data['url'] = @url if @url != 'https://api.cloud66.com'
|
95
|
+
# store the faye url if it is different
|
96
|
+
data['faye_url'] = @faye_url if @faye_url != 'https://sockets.cloud66.com/push'
|
97
|
+
YAML::dump(data, out)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def load_config
|
102
|
+
if File.exists?(@config_full)
|
103
|
+
# config file present
|
104
|
+
config = YAML::load(File.open(@config_full))
|
105
|
+
@api_key = config['api_key']
|
106
|
+
@agent_id = config['agent_id']
|
107
|
+
@secret_key = config['secret_key']
|
108
|
+
|
109
|
+
# set if it exists in the config
|
110
|
+
config_url = config['url']
|
111
|
+
@url = config_url if !config_url.nil? && !config_url.strip.empty?
|
112
|
+
|
113
|
+
# set if it exists in the config
|
114
|
+
config_faye_url = config['faye_url']
|
115
|
+
@faye_url = config_faye_url if !config_faye_url.nil? && !config_faye_url.strip.empty?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete_config
|
120
|
+
File.delete(@config_full) if File.exists?(@config_full)
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_pid
|
124
|
+
if File.exists?(@pid_full)
|
125
|
+
file = File.new(@pid_full, "r")
|
126
|
+
pid = file.read
|
127
|
+
file.close
|
128
|
+
pid
|
129
|
+
else
|
130
|
+
0
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def delete_pid
|
135
|
+
File.delete(@pid_full) if get_pid != 0
|
136
|
+
end
|
137
|
+
|
138
|
+
def pid_process_running?(pid)
|
139
|
+
begin
|
140
|
+
pid_number = pid.to_i
|
141
|
+
Process.getpgid(pid_number)
|
142
|
+
true
|
143
|
+
rescue Errno::ESRCH
|
144
|
+
false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_status
|
149
|
+
pid = get_pid
|
150
|
+
#check if the process is actually running
|
151
|
+
return ST_STARTED if pid != 0 && pid_process_running?(pid)
|
152
|
+
return ST_STOPPED if File.exists?(@config_full)
|
153
|
+
return ST_UNREGISTERED
|
154
|
+
end
|
155
|
+
|
156
|
+
public
|
157
|
+
|
158
|
+
def register
|
159
|
+
|
160
|
+
if get_status != ST_UNREGISTERED
|
161
|
+
begin
|
162
|
+
unregister
|
163
|
+
rescue
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
puts 'Cloud 66 Agent Registration:' if @api_key.empty? || @secret_key.empty?
|
168
|
+
# still no api key, we need to get it
|
169
|
+
if @api_key.empty?
|
170
|
+
@api_key = ask('Please enter your API key. (you can find it at https://cloud66.com/me): ')
|
171
|
+
if @api_key.length != 32
|
172
|
+
puts 'Invalid API key'
|
173
|
+
exit -1
|
174
|
+
end
|
175
|
+
end
|
176
|
+
if @secret_key.empty?
|
177
|
+
@secret_key = ask('Please enter your Secret Key (you can find it at https://cloud66.com/me): ')
|
178
|
+
if @secret_key.length != 32
|
179
|
+
puts 'Invalid Secret key'
|
180
|
+
exit -1
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url, :secret_key => @secret_key)
|
185
|
+
puts "Registering the Cloud 66 Agent..."
|
186
|
+
|
187
|
+
os_name = RUBY_PLATFORM
|
188
|
+
os_id = os_name.include?('darwin') ? 5 : 1
|
189
|
+
|
190
|
+
timezone = Time.new.zone
|
191
|
+
agent = { :agent_type_id => os_id, :agent_name => @name, :agent_timezone => timezone, :extra => os_name, :server_uid => @server_uid }
|
192
|
+
result = @quartz.register(agent)
|
193
|
+
|
194
|
+
if result['ok']
|
195
|
+
@agent_id = result['uid']
|
196
|
+
puts "Registered successfully (and now ready to be started)!"
|
197
|
+
save_config
|
198
|
+
else
|
199
|
+
puts "Failed to register due to #{result['error']}"
|
200
|
+
exit -1
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def unregister
|
205
|
+
begin
|
206
|
+
stop if get_status == ST_STARTED
|
207
|
+
|
208
|
+
load_config
|
209
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url, :agent_id => @agent_id, :secret_key => @secret_key)
|
210
|
+
|
211
|
+
puts "Unregistering the Cloud 66 Agent..."
|
212
|
+
@agent_id = ""
|
213
|
+
@quartz.unregister(@agent_id)
|
214
|
+
|
215
|
+
rescue
|
216
|
+
ensure
|
217
|
+
delete_config
|
218
|
+
puts "Unregistered successfully!"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def stop(signalCatch = false)
|
223
|
+
|
224
|
+
#signalCatch indicates a TERM or INT trap (where the app is already stopped, but server not told)
|
225
|
+
if !signalCatch && get_status == ST_STOPPED
|
226
|
+
puts "This agent was already stopped."
|
227
|
+
|
228
|
+
#delete the pid file just in case its hanging around
|
229
|
+
delete_pid
|
230
|
+
|
231
|
+
exit -1
|
232
|
+
end
|
233
|
+
|
234
|
+
#unregister the agent on the server
|
235
|
+
begin
|
236
|
+
load_config
|
237
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url, :agent_id => @agent_id, :secret_key => @secret_key)
|
238
|
+
puts "Stopping the Cloud 66 Agent..."
|
239
|
+
@log.debug "Stopping the Cloud 66 Agent..."
|
240
|
+
@quartz.status(2)
|
241
|
+
rescue
|
242
|
+
end
|
243
|
+
|
244
|
+
begin
|
245
|
+
EM.stop
|
246
|
+
rescue
|
247
|
+
end
|
248
|
+
|
249
|
+
pid = get_pid
|
250
|
+
if pid != 0
|
251
|
+
begin
|
252
|
+
Process.kill('TERM', pid.to_i)
|
253
|
+
rescue
|
254
|
+
end
|
255
|
+
end
|
256
|
+
delete_pid
|
257
|
+
|
258
|
+
puts "Stopped successfully!"
|
259
|
+
@log.debug "Stopped successfully!"
|
260
|
+
end
|
261
|
+
|
262
|
+
def start
|
263
|
+
|
264
|
+
if get_status == ST_STARTED
|
265
|
+
puts "This agent is already started. To stop it, please use the 'stop' command."
|
266
|
+
exit -1
|
267
|
+
end
|
268
|
+
|
269
|
+
#we know it isn't running, so delete leftover pid file if it exists
|
270
|
+
delete_pid
|
271
|
+
|
272
|
+
load_config
|
273
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url, :agent_id => @agent_id, :secret_key => @secret_key)
|
274
|
+
load_plugins
|
275
|
+
|
276
|
+
begin
|
277
|
+
begin
|
278
|
+
facter_data = VitalSignsUtils.get_facter_info
|
279
|
+
rescue => exc
|
280
|
+
facter_data = {}
|
281
|
+
end
|
282
|
+
@log.info @quartz.init({ :version => Agent::Version.current, plugins: plugin_meta_data, facter: facter_data })
|
283
|
+
rescue => exc
|
284
|
+
message = exc.message
|
285
|
+
if message =~ /Couldn't find Agent with uid =/
|
286
|
+
@log.warn "This agent is no longer registered at the server. The old registration details have been removed from this agent. Please re-run the agent to re-register it."
|
287
|
+
puts "This agent is no longer registered at the server. The old registration details have been removed from this agent. Please re-run the agent to re-register it."
|
288
|
+
puts @config_full
|
289
|
+
File.delete(@config_full)
|
290
|
+
else
|
291
|
+
@log.error exc.message
|
292
|
+
end
|
293
|
+
exit -1
|
294
|
+
end
|
295
|
+
|
296
|
+
puts "Starting the Cloud 66 Agent..."
|
297
|
+
if @daemon_mode
|
298
|
+
daemonize
|
299
|
+
pid = Process.pid
|
300
|
+
begin
|
301
|
+
file = File.new(@pid_full, "w")
|
302
|
+
file.write(pid)
|
303
|
+
file.close
|
304
|
+
rescue => exc
|
305
|
+
Process.kill('TERM', pid)
|
306
|
+
warn "Cannot start the Cloud 66 Agent: #{exc.message}"
|
307
|
+
end
|
308
|
+
|
309
|
+
run
|
310
|
+
else
|
311
|
+
run
|
312
|
+
end
|
313
|
+
exit 0
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
def handle(result)
|
319
|
+
# if the server sends a shutdown signal
|
320
|
+
if !result.nil? && result.is_a?(Hash) && result['shut_down']
|
321
|
+
puts "Agent shutting down (server sent shut_down command)"
|
322
|
+
@log.debug "Agent shutting down (server sent shut_down command)"
|
323
|
+
# ensure bluepill doesn't bring this up again
|
324
|
+
`sudo bluepill cloud66_agent unmonitor` rescue nil
|
325
|
+
# stop the agent
|
326
|
+
stop(true)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def load_plugins
|
331
|
+
@load_path = File.expand_path(File.join(File.dirname(__FILE__), '../lib/plugins'))
|
332
|
+
@log.info "Loading plugins from #{@load_path}"
|
333
|
+
|
334
|
+
files = Dir.glob("#{@load_path}/*.rb")
|
335
|
+
files.each do |file|
|
336
|
+
unless file =~ /quartz_plugin/
|
337
|
+
|
338
|
+
# is it a valid plugin?
|
339
|
+
require "#{file}"
|
340
|
+
classname = File.basename(file, '.rb').split('_').collect { |part| part.capitalize }.join
|
341
|
+
begin
|
342
|
+
clazz = Kernel.const_get(classname)
|
343
|
+
if clazz.ancestors[1].name == 'QuartzPlugin'
|
344
|
+
instance = clazz.new(@log, { :api_key => @api_key, :agent_id => @agent_id })
|
345
|
+
guid = instance.info[:uid]
|
346
|
+
@plugins = @plugins.merge({ guid => instance })
|
347
|
+
@log.info "Found plugin #{instance.info[:name]}/#{instance.info[:version]} with uid #{guid}"
|
348
|
+
else
|
349
|
+
@log.error "Invalid plugin found #{clazz}"
|
350
|
+
end
|
351
|
+
rescue
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
@log.debug "All plugins #{plugin_meta_data}"
|
357
|
+
end
|
358
|
+
|
359
|
+
def plugin_meta_data
|
360
|
+
result = []
|
361
|
+
@plugins.each do |k, v|
|
362
|
+
result << v.info
|
363
|
+
end
|
364
|
+
result
|
365
|
+
end
|
366
|
+
|
367
|
+
def get_job
|
368
|
+
begin
|
369
|
+
result = @quartz.get_job
|
370
|
+
if result['ok']
|
371
|
+
if !result['empty']
|
372
|
+
message = JSON.parse(result['message'])
|
373
|
+
guid = message['plugin_uid']
|
374
|
+
name = message['template_name']
|
375
|
+
drt = message['desired_run_time']
|
376
|
+
|
377
|
+
@log.info "Going to run #{name} (uid:#{guid})"
|
378
|
+
|
379
|
+
# get the plugin
|
380
|
+
if @plugins.include?(guid)
|
381
|
+
plugin = @plugins[guid]
|
382
|
+
|
383
|
+
#run the job (new thread)
|
384
|
+
operation = proc { run_plugin(plugin, message) }
|
385
|
+
EM.defer(operation)
|
386
|
+
|
387
|
+
#drain the queue until it is empty
|
388
|
+
get_job
|
389
|
+
else
|
390
|
+
@log.error "No plugin found with uid #{guid}"
|
391
|
+
job_id = message['job_id']
|
392
|
+
data = { :run_start => Time.now.utc.to_i, :run_end => Time.now.utc.to_i, :agent_uid => @agent_id, :ok => false, :fail_reason => "Requested plugin not found. Does this agent support this job type?" }
|
393
|
+
@quartz.post_results(job_id, data)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
else
|
397
|
+
@log.error "Failed to retrieve job due to #{result['error']}"
|
398
|
+
end
|
399
|
+
rescue => exc
|
400
|
+
@log.error "Failed to retrieve job due to #{exc}"
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def run_plugin(plugin, message)
|
405
|
+
run_start = Time.now.utc.to_i
|
406
|
+
begin
|
407
|
+
job_id = message['job_id']
|
408
|
+
result = plugin.run(message)
|
409
|
+
@log.debug "Run returned for job #{job_id} with #{result}"
|
410
|
+
@log.debug result
|
411
|
+
ok = result[:ok]
|
412
|
+
to_return = result[:message]
|
413
|
+
rescue => exc
|
414
|
+
@log.error "Failure during execution of plugin #{plugin} due to #{exc}"
|
415
|
+
ok = false
|
416
|
+
if result.nil?
|
417
|
+
to_return = exc.message
|
418
|
+
else
|
419
|
+
to_return = result[:message]
|
420
|
+
end
|
421
|
+
ensure
|
422
|
+
data = { :run_start => run_start, :run_end => Time.now.utc.to_i, :agent_uid => @agent_id, :ok => ok }
|
423
|
+
data = ok ? data.merge({ :run_result => to_return }) : data.merge({ :fail_reason => to_return })
|
424
|
+
begin
|
425
|
+
@log.debug "Posting results for job #{job_id} back to the server #{data}"
|
426
|
+
@quartz.post_results(job_id, data)
|
427
|
+
rescue => e
|
428
|
+
@log.error "Failed to post results back to server due to #{e}"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def pulsate
|
434
|
+
begin
|
435
|
+
if @pulse_count == NETWORK_CHECK_PULSE_FREQUENCY
|
436
|
+
|
437
|
+
#reset pulse count
|
438
|
+
@pulse_count = 0
|
439
|
+
|
440
|
+
begin
|
441
|
+
handle(@quartz.pulse_with_ip_address(VitalSignsUtils.get_ip_address_info))
|
442
|
+
rescue => exc
|
443
|
+
#do a normal pulse if we have any detection issues
|
444
|
+
handle(@quartz.pulse_without_ip_address)
|
445
|
+
end
|
446
|
+
else
|
447
|
+
handle(@quartz.pulse_without_ip_address)
|
448
|
+
|
449
|
+
end
|
450
|
+
@pulse_count += 1
|
451
|
+
rescue => exc
|
452
|
+
@log.error "Failed to pulsate due to #{exc.message}"
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def update_vital_signs
|
457
|
+
begin
|
458
|
+
data = {
|
459
|
+
:disk => VitalSignsUtils.get_disk_usage_info,
|
460
|
+
:cpu => VitalSignsUtils.get_cpu_usage_info,
|
461
|
+
:memory => VitalSignsUtils.get_memory_usage_info
|
462
|
+
}
|
463
|
+
handle(@quartz.send_vital_signs(data))
|
464
|
+
rescue => exc
|
465
|
+
@log.error "Failed to update vital signs due to #{exc.message}"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def run
|
470
|
+
EM.run {
|
471
|
+
Signal.trap('INT') { @log.debug("trapped INT signal"); stop(true) }
|
472
|
+
Signal.trap('TERM') { @log.debug("trapped TERM signal"); stop(true) }
|
473
|
+
|
474
|
+
# pulse
|
475
|
+
@pulse_count = NETWORK_CHECK_PULSE_FREQUENCY
|
476
|
+
pulsate
|
477
|
+
update_vital_signs
|
478
|
+
begin
|
479
|
+
|
480
|
+
EM.add_periodic_timer 60 do
|
481
|
+
pulsate
|
482
|
+
end
|
483
|
+
EM.add_periodic_timer 1800 do
|
484
|
+
update_vital_signs
|
485
|
+
end
|
486
|
+
rescue => exc
|
487
|
+
@log.error "Unable to add EM timer due to: #{exc.message}"
|
488
|
+
@log.error "#{exc.backtrace}"
|
489
|
+
exit -1
|
490
|
+
end
|
491
|
+
|
492
|
+
if @realtime
|
493
|
+
channel = "/agent_user/#{@api_key}/agent/#{@agent_id}/newjob"
|
494
|
+
@log.info "Listening to realtime notifications from '#{channel}' on '#{@faye_url}'"
|
495
|
+
client = Faye::Client.new(@faye_url)
|
496
|
+
client.subscribe(channel) do |message|
|
497
|
+
@log.info "Got realtime notice for a new job #{message}"
|
498
|
+
get_job
|
499
|
+
end
|
500
|
+
else
|
501
|
+
@log.info "Checking for new jobs every 60 seconds"
|
502
|
+
#reduced job check frequency (for stale checker)
|
503
|
+
EM.add_periodic_timer 45 do
|
504
|
+
get_job
|
505
|
+
end
|
506
|
+
end
|
507
|
+
}
|
508
|
+
end
|
509
|
+
|
510
|
+
public
|
511
|
+
|
512
|
+
config_file = 'agent.yml'
|
513
|
+
@pid_file = 'c66-agent.pid'
|
514
|
+
@log_file = 'c66-agent.log'
|
515
|
+
@config_dir = '/etc/cloud66/'
|
516
|
+
@config_full = File.join(@config_dir, config_file)
|
517
|
+
@cb_tmp_dir = '/tmp/cloud66'
|
518
|
+
Dir.mkdir(@cb_tmp_dir) if !File.exists?(@cb_tmp_dir)
|
519
|
+
|
520
|
+
@pid_full = File.join(@cb_tmp_dir, @pid_file)
|
521
|
+
@log_full = File.join(@cb_tmp_dir, @log_file)
|
522
|
+
commands = %w[register unregister start stop]
|
523
|
+
@plugins = {}
|
524
|
+
command = nil
|
525
|
+
|
526
|
+
|
527
|
+
OptionParser.new do |opts|
|
528
|
+
opts.banner = <<-EOF
|
529
|
+
Cloud 66 Agent. v#{Agent::Version.current} (c) 2012 Cloud 66
|
530
|
+
For more information please visit http://cloud66.com
|
531
|
+
|
532
|
+
Usage: c66-agent [register|unregister|start|stop] [options]
|
533
|
+
|
534
|
+
Options:
|
535
|
+
EOF
|
536
|
+
|
537
|
+
opts.on('--url URL', 'Server URL') do |server_url|
|
538
|
+
@url = server_url
|
539
|
+
end
|
540
|
+
@url ||= 'https://api.cloud66.com'
|
541
|
+
|
542
|
+
opts.on('-d', '--no-daemon', 'Not in daemon mode') do |v|
|
543
|
+
@daemon_mode = false
|
544
|
+
end
|
545
|
+
@daemon_mode ||= true
|
546
|
+
|
547
|
+
opts.on('-p', '--pid PID', 'PID file path') do |v|
|
548
|
+
@pid_full = v
|
549
|
+
end
|
550
|
+
|
551
|
+
opts.on('-l', '--log LOG', 'Full log file path') do |v|
|
552
|
+
@log_full = v
|
553
|
+
end
|
554
|
+
|
555
|
+
opts.on('-n', '--name NAME', 'Name of this agent') do |v|
|
556
|
+
@name = v
|
557
|
+
end
|
558
|
+
@name = Socket.gethostname if @name.nil? || @name.empty?
|
559
|
+
|
560
|
+
opts.on('-c', '--config CONFIG', 'Config file path') do |v|
|
561
|
+
@config_full = v
|
562
|
+
end
|
563
|
+
|
564
|
+
opts.on('--sockets SOCKETS', 'Sockets URL') do |v|
|
565
|
+
@faye_url = v
|
566
|
+
end
|
567
|
+
@faye_url ||= 'https://sockets.cloud66.com/push'
|
568
|
+
|
569
|
+
opts.on('--api-key APIKEY', 'API key') do |v|
|
570
|
+
@api_key = v
|
571
|
+
end
|
572
|
+
@api_key ||= ''
|
573
|
+
|
574
|
+
opts.on('--secret-key SECRETKET', 'Secret Key') do |v|
|
575
|
+
@secret_key = v
|
576
|
+
end
|
577
|
+
@secret_key ||= ''
|
578
|
+
|
579
|
+
opts.on('--server SERVERUID', 'Server id') do |v|
|
580
|
+
@server_uid = v
|
581
|
+
end
|
582
|
+
|
583
|
+
opts.on('-r', '--realtime', 'Enable realtime notifications (default)') do |v|
|
584
|
+
@realtime = true
|
585
|
+
end
|
586
|
+
opts.on('-R', '--no-realtime', 'Disable realtime notifications') do |v|
|
587
|
+
@realtime = false
|
588
|
+
end
|
589
|
+
@realtime ||= true
|
590
|
+
|
591
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
592
|
+
puts opts
|
593
|
+
puts <<-EOF
|
594
|
+
|
595
|
+
Commands:
|
596
|
+
register Register the Cloud 66 Agent
|
597
|
+
start Starts the Cloud 66 Agent as a deamon
|
598
|
+
stop Stops the Cloud 66 Agent daemon
|
599
|
+
unregister Unregisters the Cloud 66 Agent
|
600
|
+
|
601
|
+
EOF
|
602
|
+
exit 0
|
603
|
+
end
|
604
|
+
end.parse!
|
605
|
+
|
606
|
+
#set logging output
|
607
|
+
@log = @daemon_mode ? Logger.new(@log_full) : Logger.new(STDOUT)
|
608
|
+
@log.level = Logger::DEBUG
|
609
|
+
|
610
|
+
#return status information
|
611
|
+
command = ARGV[0].downcase unless ARGV[0].nil?
|
612
|
+
if command.nil? || command.empty?
|
613
|
+
status = get_status
|
614
|
+
puts "v#{Agent::Version.current} Started (use --help for commands)" if status == ST_STARTED
|
615
|
+
puts "v#{Agent::Version.current} Stopped (use --help for commands)" if status == ST_STOPPED
|
616
|
+
puts "v#{Agent::Version.current} Unregistered (use --help for commands)" if status == ST_UNREGISTERED
|
617
|
+
exit -1
|
618
|
+
end
|
619
|
+
|
620
|
+
unless commands.include?(command)
|
621
|
+
puts 'Invalid command. Use --help for more information'
|
622
|
+
exit -1
|
623
|
+
end
|
624
|
+
|
625
|
+
begin
|
626
|
+
send(command)
|
627
|
+
rescue => exc
|
628
|
+
@log.error exc.message
|
629
|
+
puts "An error has occurred: #{exc.message}"
|
630
|
+
exit -1
|
631
|
+
end
|
632
|
+
|
data/lib/client_auth.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class ClientAuth
|
2
|
+
|
3
|
+
def initialize api_key, secret_key
|
4
|
+
@headers = self.class.build_headers api_key, secret_key
|
5
|
+
end
|
6
|
+
|
7
|
+
def outgoing(message, callback)
|
8
|
+
|
9
|
+
# Again, leave non-subscribe messages alone
|
10
|
+
if message['channel'] != '/meta/subscribe'
|
11
|
+
return callback.call(message)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add ext field if it's not present
|
15
|
+
message['ext'] ||= {}
|
16
|
+
|
17
|
+
# Set the tokens
|
18
|
+
message['ext']['api_key'] = @headers['api_key']
|
19
|
+
message['ext']['hash'] = @headers['hash']
|
20
|
+
message['ext']['time'] = @headers['time']
|
21
|
+
|
22
|
+
# Carry on and send the message to the server
|
23
|
+
callback.call(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.build_headers api_key, secret_key
|
27
|
+
time = Time.now.utc.to_i
|
28
|
+
hash = Digest::SHA1.hexdigest("#{api_key}#{secret_key}#{time}").downcase
|
29
|
+
{ 'api_key' => api_key, 'hash' => hash, 'time' => time.to_s }
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|