cloudblocks 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/chief +143 -0
- data/bin/quartz +414 -0
- data/lib/cloud-quartz.rb +55 -0
- data/lib/config-chief.rb +100 -0
- data/lib/plugins/broken.rb +14 -0
- data/lib/plugins/mysql.rb +85 -0
- data/lib/plugins/quartz_plugin.rb +49 -0
- data/lib/plugins/rake.rb +23 -0
- data/lib/plugins/shell.rb +22 -0
- data/lib/plugins/tester.rb +17 -0
- data/lib/plugins/webget.rb +34 -0
- metadata +112 -0
data/bin/chief
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require File.expand_path("../../lib/config-chief", __FILE__)
|
5
|
+
require 'optparse'
|
6
|
+
begin
|
7
|
+
gem 'eventmachine', '~>1.0.0.beta.4'
|
8
|
+
gem 'faye', '~>0.8.0'
|
9
|
+
|
10
|
+
require 'eventmachine'
|
11
|
+
require 'faye'
|
12
|
+
rescue LoadError => exc
|
13
|
+
warn "Cannot find required ruby gems needed to run this agent. Please install cloudblocks gem by running 'gem install cloudblocks'"
|
14
|
+
warn exc
|
15
|
+
exit -1
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
puts "Stopping"
|
20
|
+
EM.stop
|
21
|
+
exit 0
|
22
|
+
end
|
23
|
+
|
24
|
+
config_file = 'cloudblocks.yaml'
|
25
|
+
config_dir = File.join(Dir.home, '.cloudblocks')
|
26
|
+
config_full = File.join(config_dir, config_file)
|
27
|
+
|
28
|
+
api_key = ''
|
29
|
+
faye_url = 'https://socket.thecloudblocks.com:8443/'
|
30
|
+
|
31
|
+
if File.exists?(config_full)
|
32
|
+
# config file present
|
33
|
+
config = YAML::load(File.open(config_full))
|
34
|
+
api_key = config['api_key']
|
35
|
+
else
|
36
|
+
# no config file
|
37
|
+
puts 'CloudBlocks ConfigChief'
|
38
|
+
puts 'Please enter your API key. (you can find it at https://www.thecloudblocks.com/me):'
|
39
|
+
api_key = gets
|
40
|
+
api_key = api_key.chomp
|
41
|
+
if api_key.length != 32
|
42
|
+
puts 'Invalid API key'
|
43
|
+
exit -1
|
44
|
+
else
|
45
|
+
if !FileTest::directory?(config_dir)
|
46
|
+
Dir.mkdir(config_dir)
|
47
|
+
end
|
48
|
+
File.open(config_full, 'w+') { |out| YAML::dump({ 'api_key' => api_key }, out) }
|
49
|
+
puts 'Configuration Saved'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@version = 1
|
54
|
+
str_version = "0.0.#{@version}"
|
55
|
+
workspace = nil
|
56
|
+
key = nil
|
57
|
+
is_test = false
|
58
|
+
parameters = {}
|
59
|
+
url = nil
|
60
|
+
id = nil
|
61
|
+
OptionParser.new do |opts|
|
62
|
+
opts.banner = <<-EOF
|
63
|
+
ConfigChief. v#{str_version} (c) 2012 CloudBlocks
|
64
|
+
For more information please visit http://www.thecloudblocks.com
|
65
|
+
|
66
|
+
Usage: chief [options]
|
67
|
+
|
68
|
+
Options:
|
69
|
+
EOF
|
70
|
+
|
71
|
+
opts.on("-w", "--workspace WORKSPACE", "Workspace id") do |v|
|
72
|
+
workspace = v
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on('-u', '--url URL', 'Server URL') do |v|
|
76
|
+
url = v
|
77
|
+
end
|
78
|
+
url = url || 'https://api.thecloudblocks.com'
|
79
|
+
|
80
|
+
opts.on('-k', '--key KEY', 'Config Key') do |v|
|
81
|
+
key = v
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on('--api-key APIKEY', 'API key') do |v|
|
85
|
+
api_key = v
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on('-i', '--id ID', 'Config Id') do |v|
|
89
|
+
id = v
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on('-p', '--parameters P1=V1,P2=V2,...', Array, 'Optional Parameters') do |v|
|
93
|
+
v.each do |p|
|
94
|
+
pv = p.split('=')
|
95
|
+
if pv.count != 2 || pv[0].nil? || pv[1].nil?
|
96
|
+
warn 'Invalid Parameters. Use -h for help'
|
97
|
+
exit -1
|
98
|
+
end
|
99
|
+
|
100
|
+
parameters = parameters.merge({pv[0].to_sym => pv[1]})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.on('-t', '--test', 'Test mode. Shows updates received from the server') do |v|
|
105
|
+
is_test = true
|
106
|
+
end
|
107
|
+
|
108
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
109
|
+
puts opts
|
110
|
+
exit 0
|
111
|
+
end
|
112
|
+
end.parse!
|
113
|
+
|
114
|
+
if workspace.nil?
|
115
|
+
warn 'No workspace specified. Use --help for more info'
|
116
|
+
exit -1
|
117
|
+
end
|
118
|
+
|
119
|
+
if key.nil? && id.nil? && !is_test
|
120
|
+
warn 'Either id (-i) or key (-k) have to be present'
|
121
|
+
exit -1
|
122
|
+
end
|
123
|
+
|
124
|
+
if is_test
|
125
|
+
EM.run {
|
126
|
+
Signal.trap('INT') { stop }
|
127
|
+
Signal.trap('TERM'){ stop }
|
128
|
+
|
129
|
+
puts "Listening for /chief/keys/#{workspace} now on #{faye_url}"
|
130
|
+
client = Faye::Client.new(faye_url)
|
131
|
+
client.subscribe("/chief/keys/#{workspace}") do |message|
|
132
|
+
puts message.inspect
|
133
|
+
end
|
134
|
+
}
|
135
|
+
else
|
136
|
+
chief = ConfigChief.new(:api_key => api_key, :workspace => workspace, :url => url)
|
137
|
+
if (key.nil?)
|
138
|
+
puts chief.get_by_id(id, {}, parameters)
|
139
|
+
else
|
140
|
+
# let's get to bussiness
|
141
|
+
puts chief.get_by_key(key, {}, parameters)
|
142
|
+
end
|
143
|
+
end
|
data/bin/quartz
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require File.expand_path("../../lib/cloud-quartz", __FILE__)
|
5
|
+
require 'optparse'
|
6
|
+
require 'socket'
|
7
|
+
require 'logger'
|
8
|
+
begin
|
9
|
+
gem 'eventmachine', '~>1.0.0.beta.4'
|
10
|
+
gem 'faye', '~>0.8.0'
|
11
|
+
|
12
|
+
require 'eventmachine'
|
13
|
+
require 'faye'
|
14
|
+
rescue LoadError => exc
|
15
|
+
warn "Cannot find required ruby gems needed to run this agent. Please install cloudblocks gem by running 'gem install cloudblocks'"
|
16
|
+
warn exc
|
17
|
+
exit -1
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def save_config
|
23
|
+
if !FileTest::directory?(@config_dir)
|
24
|
+
Dir.mkdir(@config_dir)
|
25
|
+
end
|
26
|
+
File.open(@config_full, 'w+') { |out| YAML::dump({ 'api_key' => @api_key, 'agent_id' => @agent_id }, out) }
|
27
|
+
end
|
28
|
+
|
29
|
+
public
|
30
|
+
|
31
|
+
@load_path = File.expand_path(File.join(File.dirname(__FILE__), '../lib/plugins'))
|
32
|
+
|
33
|
+
def start
|
34
|
+
begin
|
35
|
+
@log.info "Starting CloudQuartz agent"
|
36
|
+
puts "Starting CloudQuartz agent"
|
37
|
+
|
38
|
+
pid = get_pid
|
39
|
+
if pid != 0
|
40
|
+
warn "Quartz is already running. Use stop command to stop it or --help for more info"
|
41
|
+
exit -1
|
42
|
+
end
|
43
|
+
|
44
|
+
check_version
|
45
|
+
load_plugins
|
46
|
+
|
47
|
+
@log.info @quartz.status(1, @version, plugin_meta_data)
|
48
|
+
rescue => exc
|
49
|
+
@log.error exc.message
|
50
|
+
exit -1
|
51
|
+
end
|
52
|
+
|
53
|
+
if @daemon_mode
|
54
|
+
pid = fork {
|
55
|
+
run
|
56
|
+
}
|
57
|
+
|
58
|
+
begin
|
59
|
+
file = File.new(@pid_full, "w")
|
60
|
+
file.write(pid)
|
61
|
+
file.close
|
62
|
+
Process.detach(pid)
|
63
|
+
rescue => exc
|
64
|
+
Process.kill('TERM', pid)
|
65
|
+
warn "Cannot start CloudQuartz agent: #{exc.message}"
|
66
|
+
end
|
67
|
+
else
|
68
|
+
run
|
69
|
+
end
|
70
|
+
|
71
|
+
exit 0
|
72
|
+
end
|
73
|
+
|
74
|
+
def stop
|
75
|
+
pid = get_pid
|
76
|
+
@log.info "Stopping CloudQuartz agent"
|
77
|
+
begin
|
78
|
+
@quartz.status(2, @version, plugin_meta_data)
|
79
|
+
EM.stop
|
80
|
+
rescue
|
81
|
+
end
|
82
|
+
|
83
|
+
if pid != 0
|
84
|
+
begin
|
85
|
+
Process.kill('HUP', pid.to_i)
|
86
|
+
rescue
|
87
|
+
end
|
88
|
+
File.delete(@pid_full)
|
89
|
+
puts "Stopped"
|
90
|
+
else
|
91
|
+
warn "Quartz is not running"
|
92
|
+
exit -1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def register
|
97
|
+
puts "Registering agent with #{@url} and API Key #{@api_key}"
|
98
|
+
os_name = RUBY_PLATFORM
|
99
|
+
os_id = os_name.include?('darwin') ? 5 : 1
|
100
|
+
agent = { :agent_type_id => os_id, :agent_name => Socket.gethostname, :agent_timezone => Time.new.zone, :extra => os_name}
|
101
|
+
|
102
|
+
result = @quartz.register(agent)
|
103
|
+
if result['ok']
|
104
|
+
@agent_id = result['uid']
|
105
|
+
puts "Registered with id #{@agent_id}"
|
106
|
+
save_config
|
107
|
+
else
|
108
|
+
puts "Failed to register due to #{result['error']}"
|
109
|
+
exit -1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def unregister
|
114
|
+
puts "Unregister agent #{@agent_id} with #{@url}"
|
115
|
+
@agent_id = ""
|
116
|
+
@quartz.unregister(@agent_id)
|
117
|
+
save_config
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def load_plugins
|
123
|
+
@log.info "Loading plugins from #{@load_path}"
|
124
|
+
|
125
|
+
files = Dir.glob("#{@load_path}/*.rb")
|
126
|
+
files.each do |file|
|
127
|
+
if file != 'quartz_plugin'
|
128
|
+
# is it a valid plugin?
|
129
|
+
require "#{file}"
|
130
|
+
classname = File.basename(file, '.rb').capitalize
|
131
|
+
begin
|
132
|
+
clazz = Kernel.const_get(classname)
|
133
|
+
if clazz.ancestors[1].name == 'QuartzPlugin'
|
134
|
+
instance = clazz.new(@log, { :api_key => @api_key, :agent_id => @agent_id })
|
135
|
+
guid = instance.info[:uid]
|
136
|
+
@plugins = @plugins.merge({ guid => instance })
|
137
|
+
@log.info "Found plugin #{instance.info[:name]}/#{instance.info[:version]} with uid #{guid}"
|
138
|
+
end
|
139
|
+
rescue
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
@log.debug "All plugins #{plugin_meta_data}"
|
145
|
+
end
|
146
|
+
|
147
|
+
def plugin_meta_data
|
148
|
+
result = []
|
149
|
+
@plugins.each do |k, v|
|
150
|
+
result << v.info
|
151
|
+
end
|
152
|
+
|
153
|
+
result
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_job
|
157
|
+
result = @quartz.get_job
|
158
|
+
if result['ok']
|
159
|
+
if result['empty']
|
160
|
+
@log.debug 'No jobs to run'
|
161
|
+
else
|
162
|
+
message = JSON.parse(result['message'])
|
163
|
+
guid = message['plugin_uid']
|
164
|
+
name = message['template_name']
|
165
|
+
drt = message['desired_run_time']
|
166
|
+
|
167
|
+
@log.info "Going to run #{name} (uid:#{guid})"
|
168
|
+
|
169
|
+
# get the plugin
|
170
|
+
if @plugins.include?(guid)
|
171
|
+
plugin = @plugins[guid]
|
172
|
+
# run it
|
173
|
+
operation = proc { run_plugin(plugin, message) }
|
174
|
+
EM.defer(operation)
|
175
|
+
else
|
176
|
+
@log.error "No plugin found with uid #{guid}"
|
177
|
+
# TODO: Send this back to server as run error
|
178
|
+
end
|
179
|
+
end
|
180
|
+
else
|
181
|
+
@log.error "Failed to retrieve job due to #{result['error']}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def run_plugin(plugin, message)
|
186
|
+
run_start = Time.now.utc.to_i
|
187
|
+
begin
|
188
|
+
job_id = message['job_id']
|
189
|
+
result = plugin.run(message)
|
190
|
+
@log.debug "Run returned for job #{job_id} with #{result}"
|
191
|
+
@log.debug result
|
192
|
+
ok = result[:ok]
|
193
|
+
to_return = result[:message]
|
194
|
+
rescue => exc
|
195
|
+
@log.error "Failure during running plugin #{plugin} due to #{exc}"
|
196
|
+
ok = false
|
197
|
+
if result.nil?
|
198
|
+
to_return = exc.message
|
199
|
+
else
|
200
|
+
to_return = result[:message]
|
201
|
+
end
|
202
|
+
ensure
|
203
|
+
data = { :run_start => run_start, :run_end => Time.now.utc.to_i, :agent_uid => @agent_id, :ok => ok }
|
204
|
+
data = ok ? data.merge({ :run_result => to_return }) : data.merge({ :fail_reason => to_return })
|
205
|
+
begin
|
206
|
+
@log.debug "Posting results for job #{job_id} back to the server #{data}"
|
207
|
+
@quartz.post_results(job_id, data)
|
208
|
+
rescue => e
|
209
|
+
@log.error "Failed to post results back to server due to #{e}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def check_version
|
215
|
+
begin
|
216
|
+
result = @quartz.check_version
|
217
|
+
if result['ok']
|
218
|
+
latest = result['latest']
|
219
|
+
@log.warn 'A newer version of CloudQuartz agent is available. Update the cloudblocks gem. See http://help.thecloudblocks.com for more info' if latest > @version
|
220
|
+
end
|
221
|
+
rescue => exc
|
222
|
+
warn "Cannot connect to the server"
|
223
|
+
exit -1
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def run
|
228
|
+
EM.run{
|
229
|
+
Signal.trap('INT') { stop }
|
230
|
+
Signal.trap('TERM'){ stop }
|
231
|
+
|
232
|
+
if @realtime
|
233
|
+
@log.info "Listening to realtime nofitifications from /quartz/jobs/#{@agent_id} on #{@faye_url}"
|
234
|
+
client = Faye::Client.new(@faye_url)
|
235
|
+
client.subscribe("/quartz/jobs/#{@agent_id}") do |message|
|
236
|
+
@log.info "Got realtime notice for a new job #{message}"
|
237
|
+
get_job
|
238
|
+
end
|
239
|
+
else
|
240
|
+
@log.info "Checking for new jobs every 5 seconds"
|
241
|
+
EM.add_periodic_timer 5 do
|
242
|
+
get_job
|
243
|
+
end
|
244
|
+
end
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
def get_pid
|
249
|
+
if File.exists?(@pid_full)
|
250
|
+
file = File.new(@pid_full, "r")
|
251
|
+
pid = file.read
|
252
|
+
file.close
|
253
|
+
|
254
|
+
pid
|
255
|
+
else
|
256
|
+
0
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
public
|
261
|
+
|
262
|
+
@version = 1
|
263
|
+
str_version = "0.0.#{@version}"
|
264
|
+
|
265
|
+
config_file = 'cloudblocks.yaml'
|
266
|
+
@pid_file = 'quartz.pid'
|
267
|
+
@log_file = 'quartz.log'
|
268
|
+
@config_dir = File.join(Dir.home, '.cloudblocks')
|
269
|
+
@config_full = File.join(@config_dir, config_file)
|
270
|
+
@pid_full = File.join('/tmp', @pid_file)
|
271
|
+
@log_full = File.join('/tmp', @log_file)
|
272
|
+
|
273
|
+
@api_key = ''
|
274
|
+
@faye_url = 'https://socket.thecloudblocks.com:8443/'
|
275
|
+
commands = %w[register unregister start stop]
|
276
|
+
|
277
|
+
@plugins = {}
|
278
|
+
|
279
|
+
@url = nil
|
280
|
+
@daemon_mode = true
|
281
|
+
command = nil
|
282
|
+
@agent_id = ''
|
283
|
+
@realtime = true
|
284
|
+
OptionParser.new do |opts|
|
285
|
+
opts.banner = <<-EOF
|
286
|
+
CloudQuartz Agent. v#{str_version} (c) 2012 CloudBlocks
|
287
|
+
For more information please visit http://www.thecloudblocks.com
|
288
|
+
|
289
|
+
Usage: quartz [register|unregister|start|stop] [options]
|
290
|
+
|
291
|
+
Options:
|
292
|
+
EOF
|
293
|
+
|
294
|
+
opts.on('--url URL', 'Server URL') do |server_url|
|
295
|
+
@url = server_url
|
296
|
+
end
|
297
|
+
@url = @url || 'https://api.thecloudblocks.com'
|
298
|
+
|
299
|
+
opts.on('--agent-id AGENTID', 'Agent id') do |v|
|
300
|
+
@agent_id = v
|
301
|
+
end
|
302
|
+
|
303
|
+
opts.on('-d', '--no-daemon', 'Not in daemon mode') do |v|
|
304
|
+
@daemon_mode = false
|
305
|
+
end
|
306
|
+
|
307
|
+
opts.on('-p', '--pid PID', 'PID file path') do |v|
|
308
|
+
@pid_full = v
|
309
|
+
end
|
310
|
+
|
311
|
+
opts.on('-l', '--log LOG', 'Log file path') do |v|
|
312
|
+
@log_full = v
|
313
|
+
end
|
314
|
+
|
315
|
+
opts.on('-c', '--config CONFIG', 'Config file path') do |v|
|
316
|
+
@config_full = v
|
317
|
+
end
|
318
|
+
|
319
|
+
opts.on('--sockets SOCKETS', 'Sockets URL') do |v|
|
320
|
+
@faye_url = v
|
321
|
+
@faye_url = "#{@faye_url}/" if @faye_url[-1] != '/'
|
322
|
+
end
|
323
|
+
|
324
|
+
opts.on('--api-key APIKEY', 'API key') do |v|
|
325
|
+
@api_key = v
|
326
|
+
end
|
327
|
+
|
328
|
+
opts.on('-n', '--no-realtime', 'Disable realtime notifications') do |v|
|
329
|
+
@realtime = false
|
330
|
+
end
|
331
|
+
|
332
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
333
|
+
puts opts
|
334
|
+
puts <<-EOF
|
335
|
+
|
336
|
+
Commands:
|
337
|
+
register Register the agent
|
338
|
+
start Starts agent as deamon
|
339
|
+
stop Stops agent daemon
|
340
|
+
unregister Unregister the agent
|
341
|
+
|
342
|
+
EOF
|
343
|
+
exit 0
|
344
|
+
end
|
345
|
+
end.parse!
|
346
|
+
|
347
|
+
command = ARGV[0].downcase unless ARGV[0].nil?
|
348
|
+
|
349
|
+
if @daemon_mode
|
350
|
+
@log = Logger.new(@log_full)
|
351
|
+
else
|
352
|
+
@log = Logger.new(STDOUT)
|
353
|
+
end
|
354
|
+
|
355
|
+
@log.level = Logger::DEBUG
|
356
|
+
|
357
|
+
if (@api_key.empty? || @agent_id.empty?) && File.exists?(@config_full)
|
358
|
+
# config file present
|
359
|
+
config = YAML::load(File.open(@config_full))
|
360
|
+
@api_key = config['api_key']
|
361
|
+
@agent_id = config['agent_id']
|
362
|
+
end
|
363
|
+
|
364
|
+
# still no api key, we need to get it
|
365
|
+
if @api_key.empty?
|
366
|
+
puts 'CloudBlocks CloudQuartz'
|
367
|
+
print 'Please enter your API key. (you can find it at https://www.thecloudblocks.com/me):'
|
368
|
+
@api_key = gets.chomp
|
369
|
+
if @api_key.length != 32
|
370
|
+
puts 'Invalid API key'
|
371
|
+
exit -1
|
372
|
+
end
|
373
|
+
|
374
|
+
save_config
|
375
|
+
puts 'Configuration Saved'
|
376
|
+
end
|
377
|
+
|
378
|
+
# no agent id?
|
379
|
+
if @agent_id.empty?
|
380
|
+
print 'Register the agent? [Y/n]'
|
381
|
+
if gets.chomp.downcase != 'n'
|
382
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url)
|
383
|
+
register
|
384
|
+
|
385
|
+
print 'Start the agent as daemon? [Yn]'
|
386
|
+
if gets.chomp.downcase != 'n'
|
387
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url, :agent_id => @agent_id)
|
388
|
+
start
|
389
|
+
else
|
390
|
+
exit 0
|
391
|
+
end
|
392
|
+
else
|
393
|
+
exit 0
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
if command.nil? || command.empty?
|
398
|
+
puts 'No command found. Use --help for more information'
|
399
|
+
exit -1
|
400
|
+
end
|
401
|
+
|
402
|
+
unless commands.include?(command)
|
403
|
+
puts 'Invalid command. Use --help for more information'
|
404
|
+
exit -1
|
405
|
+
end
|
406
|
+
|
407
|
+
if (@agent_id.nil? || @agent_id.empty? || @agent_id.empty?) && command != 'register'
|
408
|
+
puts 'No Agent id found. Have you registered it yet? Use --help for more information'
|
409
|
+
exit -1
|
410
|
+
end
|
411
|
+
|
412
|
+
@quartz = CloudQuartz.new(:api_key => @api_key, :url => @url, :agent_id => @agent_id)
|
413
|
+
|
414
|
+
send(command)
|
data/lib/cloud-quartz.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class CloudQuartz
|
5
|
+
include HTTParty
|
6
|
+
@api_key = ""
|
7
|
+
@agent_id = ""
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@api_key = options[:api_key]
|
11
|
+
@agent_id = options[:agent_id]
|
12
|
+
self.class.base_uri options[:url] || 'https://api.thecloudblocks.com'
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_job
|
16
|
+
process(self.class.get("/queue/#{@agent_id}.json", { :headers => http_headers } ))
|
17
|
+
end
|
18
|
+
|
19
|
+
def register(agent)
|
20
|
+
process(self.class.post('/agent.json', { :headers => http_headers.merge({'Content-Type' => 'application/json'}), :body => agent.to_json }))
|
21
|
+
end
|
22
|
+
|
23
|
+
def unregister(agent)
|
24
|
+
process(self.class.delete("/agent/#{agent}", :headers => http_headers))
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_version
|
28
|
+
self.class.get("/agent/version", :headers => http_headers)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post_results(job_id, data)
|
32
|
+
process(self.class.post("/job/#{job_id}/complete.json", { :headers => http_headers.merge({'Content-Type' => 'application/json'}), :body => data.to_json } ))
|
33
|
+
end
|
34
|
+
|
35
|
+
def status(stat, version, plugins)
|
36
|
+
data = { :status => stat, :version => version, :plugins => plugins }
|
37
|
+
process(self.class.post("/agent/#{@agent_id}/status.json", { :headers => http_headers.merge({'Content-Type' => 'application/json'}), :body => data.to_json }))
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def http_headers
|
43
|
+
{ 'api_key' => @api_key }
|
44
|
+
end
|
45
|
+
|
46
|
+
def process(response)
|
47
|
+
if response.code != 200
|
48
|
+
raise response.body
|
49
|
+
else
|
50
|
+
response.parsed_response
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
data/lib/config-chief.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'HTTParty'
|
2
|
+
require 'socket'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class ConfigChief
|
6
|
+
include HTTParty
|
7
|
+
|
8
|
+
@api_key = ""
|
9
|
+
@workspace = ""
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@api_key = options[:api_key]
|
13
|
+
@workspace = options[:workspace]
|
14
|
+
self.class.base_uri options[:url] || 'https://api.thecloudblocks.com'
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_by_key(key, options = {}, params = {})
|
18
|
+
opts = {
|
19
|
+
:default_value => nil,
|
20
|
+
:full_obj => false
|
21
|
+
}.merge(options)
|
22
|
+
|
23
|
+
optionals = {}
|
24
|
+
params.each do |k, v|
|
25
|
+
optionals["cc-#{k}"] = v
|
26
|
+
end
|
27
|
+
|
28
|
+
headers = { :headers => http_headers.merge(optionals) }
|
29
|
+
|
30
|
+
result = self.class.get("/workspaces/#{@workspace}/value.json", headers.merge(:query => { :query => key } ))
|
31
|
+
|
32
|
+
if result.response.code.to_i == 200
|
33
|
+
retrieved = result.parsed_response
|
34
|
+
return retrieved if opts[:full_obj]
|
35
|
+
return retrieved['parsed_value']
|
36
|
+
end
|
37
|
+
|
38
|
+
return opts[:default_value]
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_by_id(id, options = {}, params = {})
|
42
|
+
opts = {
|
43
|
+
:default_value => nil,
|
44
|
+
:full_obj => false
|
45
|
+
}.merge(options)
|
46
|
+
|
47
|
+
optionals = {}
|
48
|
+
params.each do |k, v|
|
49
|
+
optionals["cc-#{k}"] = v
|
50
|
+
end
|
51
|
+
|
52
|
+
headers = { :headers => http_headers.merge(optionals) }
|
53
|
+
|
54
|
+
result = self.class.get("/workspaces/#{@workspace}/config_keys/#{id}.json", headers )
|
55
|
+
|
56
|
+
if result.response.code.to_i == 200
|
57
|
+
retrieved = result.parsed_response
|
58
|
+
return retrieved if opts[:full_obj]
|
59
|
+
return retrieved['parsed_value']
|
60
|
+
end
|
61
|
+
|
62
|
+
return opts[:default_value]
|
63
|
+
end
|
64
|
+
|
65
|
+
def workspaces
|
66
|
+
self.class.get("/workspaces.json", { :headers => http_headers } )
|
67
|
+
end
|
68
|
+
|
69
|
+
def config_items(query = '*')
|
70
|
+
headers = { :headers => http_headers }
|
71
|
+
self.class.get("/workspaces/#{@workspace}/config_keys.json", headers.merge(:query => { :query => query } ))
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_value(key, value)
|
75
|
+
headers = { :headers => http_headers.merge({'Content-Type' => 'application/json'}) }
|
76
|
+
self.class.post("/workspaces/#{@workspace}/config_keys.json", headers.merge(:body => { :key => key, :value => value }.to_json))
|
77
|
+
end
|
78
|
+
|
79
|
+
def register_node(options = {})
|
80
|
+
headers = { :headers => http_headers.merge({'Content-Type' => 'application/json'}) }
|
81
|
+
self.class.post("/workspaces/#{@workspace}/node.json", headers.merge(:body => options.to_json))
|
82
|
+
end
|
83
|
+
|
84
|
+
def unregister_node(node_uid)
|
85
|
+
headers = { :headers => http_headers }
|
86
|
+
self.class.delete("/workspaces/#{@workspace}/node/#{node_uid}.json", headers)
|
87
|
+
end
|
88
|
+
|
89
|
+
def update_node_status(node_uid, status)
|
90
|
+
headers = { :headers => http_headers.merge({'Content-Type' => 'application/json'}) }
|
91
|
+
self.class.post("/workspaces/#{@workspace}/node/#{node_uid}/status.json", headers.merge({ :body => { :status => status}.to_json}))
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def http_headers
|
97
|
+
{ 'api_key' => @api_key, 'ConfigChief-Node' => Socket.gethostname }
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
2
|
+
|
3
|
+
class Broken < QuartzPlugin
|
4
|
+
def info
|
5
|
+
{ :uid => "04165a45fde840a9a17b41f019b3dca3", :name => "Broken", :version => "0.0.0" }
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(message)
|
9
|
+
@log.info "Running with #{message}"
|
10
|
+
@log.info "This is a failure"
|
11
|
+
|
12
|
+
run_result(false, "Boo! It's broken")
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Mysql < QuartzPlugin
|
5
|
+
|
6
|
+
def info
|
7
|
+
{ :uid => "67deb35a555344c8a7651c656e6c8e2e", :name => "MySQL Backup", :version => "0.0.0" }
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(message)
|
11
|
+
pl = payload(message)
|
12
|
+
pl = pl.select { |k,v| !v.nil? && !v.empty? }
|
13
|
+
|
14
|
+
@log.debug "Pruned payload #{pl}"
|
15
|
+
|
16
|
+
@job_name = pl['job_name'].gsub(/[^\w\s_-]+/, '').gsub(/(^|\b\s)\s+($|\s?\b)/, '\\1\\2').gsub(/\s/, '_')
|
17
|
+
@mysqldump_utility = pl['dump utility'] || '/usr/bin/mysqldump'
|
18
|
+
@name = pl['db name'] || :all
|
19
|
+
@username = pl['username']
|
20
|
+
@password = pl['password']
|
21
|
+
@socket = pl['socket']
|
22
|
+
@host = pl['host']
|
23
|
+
@port = pl['port']
|
24
|
+
@skip_tables = pl['skip tables']
|
25
|
+
@only_tables = pl['only tables']
|
26
|
+
@additional_options = pl['additional options'] || ['--single-transaction', '--quick']
|
27
|
+
@path = pl['backup folder']
|
28
|
+
|
29
|
+
dump_cmd = "#{mysqldump} | gzip > '#{ File.join(@path, @job_name.downcase) }.sql.gz'"
|
30
|
+
@log.debug "Running #{dump_cmd}"
|
31
|
+
|
32
|
+
FileUtils.mkdir_p(@path)
|
33
|
+
|
34
|
+
result = run_shell dump_cmd
|
35
|
+
if result[:ok]
|
36
|
+
run_result(true, "MySQL Backup finished successfully")
|
37
|
+
else
|
38
|
+
run_result(false, result[:message])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# copied from backup gem with little mods
|
45
|
+
def mysqldump
|
46
|
+
"#{ @mysqldump_utility } #{ credential_options } #{ connectivity_options } " +
|
47
|
+
"#{ user_options } #{ @name } #{ tables_to_dump } #{ tables_to_skip }"
|
48
|
+
end
|
49
|
+
|
50
|
+
def credential_options
|
51
|
+
%w[username password].map do |option|
|
52
|
+
value = instance_variable_get("@#{option}")
|
53
|
+
next if value.to_s.empty?
|
54
|
+
"--#{option}='#{value}'".gsub('--username', '--user')
|
55
|
+
end.compact.join(' ')
|
56
|
+
end
|
57
|
+
|
58
|
+
def connectivity_options
|
59
|
+
%w[host port socket].map do |option|
|
60
|
+
value = instance_variable_get("@#{option}")
|
61
|
+
next if value.to_s.empty?
|
62
|
+
"--#{option}='#{value}'"
|
63
|
+
end.compact.join(' ')
|
64
|
+
end
|
65
|
+
|
66
|
+
def user_options
|
67
|
+
@additional_options.join(' ') unless @additional_options.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def tables_to_dump
|
71
|
+
@only_tables.join(' ') unless @only_tables.nil? || dump_all?
|
72
|
+
end
|
73
|
+
|
74
|
+
def tables_to_skip
|
75
|
+
return '' if @skip_tables.nil?
|
76
|
+
@skip_tables.map do |table|
|
77
|
+
"--ignore-table='#{@name}.#{table}'"
|
78
|
+
end.join(' ') unless dump_all?
|
79
|
+
end
|
80
|
+
|
81
|
+
def dump_all?
|
82
|
+
@name == :all
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'open4'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class QuartzPlugin
|
5
|
+
|
6
|
+
def initialize(log, options)
|
7
|
+
@log = log
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def run_result(success, message)
|
12
|
+
result = { :ok => success, :message => message }
|
13
|
+
@log.debug "Job finished with result #{result}"
|
14
|
+
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
def payload(message)
|
19
|
+
@log.debug "Message #{message}"
|
20
|
+
raw_payload = message['payload']
|
21
|
+
@log.debug "Payload #{raw_payload}"
|
22
|
+
parsed_payload = JSON.parse(raw_payload) unless raw_payload.nil?
|
23
|
+
@log.debug "Parsed payload #{parsed_payload}"
|
24
|
+
|
25
|
+
v = {}
|
26
|
+
unless parsed_payload.nil?
|
27
|
+
parsed_payload.each do |p|
|
28
|
+
v = v.merge({ p['name'] => p['value']})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
v = v.merge({'job_name' => message['job_name']})
|
33
|
+
|
34
|
+
@log.debug "Payload #{v}"
|
35
|
+
|
36
|
+
v
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_shell(command)
|
40
|
+
pid, stdin, stdout, stderr = Open4::popen4("#{command}")
|
41
|
+
ignored, status = Process::waitpid2 pid
|
42
|
+
|
43
|
+
if status.exitstatus == 0
|
44
|
+
{ :ok => true, :message => stdout.read.strip }
|
45
|
+
else
|
46
|
+
{ :ok => false, :message => stderr.read.strip}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/plugins/rake.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
2
|
+
|
3
|
+
class Rake < QuartzPlugin
|
4
|
+
def info
|
5
|
+
{ :uid => "62e3583abfc24f209916c4ff97661fa0", :name => "Rake", :version => "0.0.0" }
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(message)
|
9
|
+
@log.debug "Running with #{message}"
|
10
|
+
payload = payload(message)
|
11
|
+
task = payload['task']
|
12
|
+
location = payload['location']
|
13
|
+
params = payload['params']
|
14
|
+
@log.info "Rake #{task} in #{location} with params:#{params}"
|
15
|
+
|
16
|
+
begin
|
17
|
+
result = run_shell("bundle exec rake #{task} #{params}")
|
18
|
+
run_result(result[:ok], result[:message])
|
19
|
+
rescue => ex
|
20
|
+
run_result(false, "Failed to run rake due to #{ex}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
2
|
+
|
3
|
+
class Rake < QuartzPlugin
|
4
|
+
def info
|
5
|
+
{ :uid => "20e07c656e2f477d969e9561e13229fb", :name => "Shell", :version => "0.0.0" }
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(message)
|
9
|
+
@log.debug "Running with #{message}"
|
10
|
+
payload = payload(message)
|
11
|
+
command = payload['command']
|
12
|
+
params = payload['params']
|
13
|
+
@log.info "Shell command '#{command}' with '#{params}'"
|
14
|
+
|
15
|
+
begin
|
16
|
+
result = run_shell("#{command} #{params}")
|
17
|
+
run_result(result[:ok], result[:message])
|
18
|
+
rescue => ex
|
19
|
+
run_result(false, "Failed to run shell command due to #{ex}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
2
|
+
|
3
|
+
class Tester < QuartzPlugin
|
4
|
+
def info
|
5
|
+
{ :uid => "c0bb6ed7950b489f9abba8071ff0e0ab", :name => "Tester", :version => "0.0.0" }
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(message)
|
9
|
+
@log.info "Running with #{message}"
|
10
|
+
i = Random.rand(10)
|
11
|
+
@log.info "Waiting for #{i} seconds"
|
12
|
+
sleep i
|
13
|
+
@log.info "Done"
|
14
|
+
|
15
|
+
run_result(true, "Super! Done in #{i} seconds")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
class Webget < QuartzPlugin
|
5
|
+
def info
|
6
|
+
{ :uid => "6b5f722d214f4d71a5be237d44094721", :name => "WebGet", :version => "0.0.0" }
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(message)
|
10
|
+
@log.debug "Running with #{message}"
|
11
|
+
payload = payload(message)
|
12
|
+
url = payload['url']
|
13
|
+
local = payload['local file']
|
14
|
+
@log.info "Webget from #{url} into #{local}"
|
15
|
+
|
16
|
+
begin
|
17
|
+
response = HTTParty.get(url)
|
18
|
+
if response.code == 200
|
19
|
+
body = response.body
|
20
|
+
file = File.new(local, "w")
|
21
|
+
begin
|
22
|
+
file.write(body)
|
23
|
+
run_result(true, "Saved WebGet to local file")
|
24
|
+
ensure
|
25
|
+
file.close
|
26
|
+
end
|
27
|
+
else
|
28
|
+
run_result(false, response.message)
|
29
|
+
end
|
30
|
+
rescue => ex
|
31
|
+
run_result(false, "Failed to webget due to #{ex}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudblocks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.6
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Khash Sajadi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-16 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: &70331935927900 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70331935927900
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
requirement: &70331935924160 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.6.3
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70331935924160
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: eventmachine
|
38
|
+
requirement: &70331935922320 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.12.10
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70331935922320
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: faye
|
49
|
+
requirement: &70331935911040 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.0
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70331935911040
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: open4
|
60
|
+
requirement: &70331935910460 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.3.0
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70331935910460
|
69
|
+
description: See http://www.thecloudblocks.com for more info
|
70
|
+
email: khash@thecloudblocks.com
|
71
|
+
executables:
|
72
|
+
- chief
|
73
|
+
- quartz
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- lib/config-chief.rb
|
78
|
+
- lib/cloud-quartz.rb
|
79
|
+
- lib/plugins/broken.rb
|
80
|
+
- lib/plugins/mysql.rb
|
81
|
+
- lib/plugins/quartz_plugin.rb
|
82
|
+
- lib/plugins/rake.rb
|
83
|
+
- lib/plugins/shell.rb
|
84
|
+
- lib/plugins/tester.rb
|
85
|
+
- lib/plugins/webget.rb
|
86
|
+
- bin/chief
|
87
|
+
- bin/quartz
|
88
|
+
homepage: http://www.thecloudblocks.com
|
89
|
+
licenses: []
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.8.17
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: CloudBlocks Gem and Agent
|
112
|
+
test_files: []
|