cloudblocks 0.0.6
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/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: []
|