right_chimp 1.1.3 → 2.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.
- data/.gitignore +1 -0
- data/CHANGES +4 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +2 -22
- data/README +25 -47
- data/chimp.gemspec +4 -4
- data/lib/right_chimp/Chimp.rb +498 -317
- data/lib/right_chimp/Log.rb +7 -4
- data/lib/right_chimp/daemon/ChimpDaemon.rb +75 -10
- data/lib/right_chimp/daemon/ChimpDaemonClient.rb +16 -8
- data/lib/right_chimp/exec/ExecReport.rb +14 -20
- data/lib/right_chimp/exec/ExecRightScript.rb +7 -6
- data/lib/right_chimp/exec/ExecSSH.rb +4 -9
- data/lib/right_chimp/exec/Executor.rb +7 -5
- data/lib/right_chimp/objects/ChimpObjects.rb +393 -0
- data/lib/right_chimp/queue/ChimpQueue.rb +32 -32
- data/lib/right_chimp/queue/ExecutionGroup.rb +3 -8
- data/lib/right_chimp/queue/QueueWorker.rb +0 -4
- data/lib/right_chimp/templates/all_jobs.erb +4 -4
- data/lib/right_chimp/version.rb +1 -1
- data/lib/right_chimp.rb +11 -2
- metadata +9 -38
data/lib/right_chimp/Chimp.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
#
|
2
2
|
# The Chimp class encapsulates the command-line program logic
|
3
3
|
#
|
4
|
-
|
5
4
|
module Chimp
|
6
5
|
class Chimp
|
6
|
+
|
7
7
|
attr_accessor :concurrency, :delay, :retry_count, :hold, :progress, :prompt,
|
8
8
|
:quiet, :use_chimpd, :chimpd_host, :chimpd_port, :tags, :array_names,
|
9
|
-
:deployment_names, :script, :servers, :ssh, :report, :interactive, :action,
|
10
|
-
:limit_start, :limit_end, :dry_run, :group, :job_id, :verify
|
11
|
-
|
9
|
+
:deployment_names, :script, :all_scripts, :servers, :ssh, :report, :interactive, :action,
|
10
|
+
:limit_start, :limit_end, :dry_run, :group, :job_id, :job_uuid, :verify, :cli_args
|
12
11
|
#
|
13
12
|
# These class variables control verbosity
|
14
13
|
#
|
@@ -53,7 +52,10 @@ module Chimp
|
|
53
52
|
#
|
54
53
|
@current = true
|
55
54
|
@match_all = true
|
55
|
+
|
56
|
+
# This is an array of json data for each instance
|
56
57
|
@servers = []
|
58
|
+
|
57
59
|
@arrays = []
|
58
60
|
@tags = []
|
59
61
|
@array_names = []
|
@@ -78,7 +80,15 @@ module Chimp
|
|
78
80
|
@chimpd_port = 9055
|
79
81
|
@chimpd_wait_until_done = false
|
80
82
|
|
81
|
-
|
83
|
+
#
|
84
|
+
# Will contain the operational scripts we have found
|
85
|
+
# In the form: [name, href]
|
86
|
+
@op_scripts = []
|
87
|
+
|
88
|
+
#
|
89
|
+
# This will contain the href and the name of the script to be run
|
90
|
+
# in the form: [name, href]
|
91
|
+
@script_to_run = nil
|
82
92
|
end
|
83
93
|
|
84
94
|
#
|
@@ -87,10 +97,23 @@ module Chimp
|
|
87
97
|
def run
|
88
98
|
queue = ChimpQueue.instance
|
89
99
|
|
100
|
+
arguments = []
|
101
|
+
|
102
|
+
ARGV.each { |arg| arguments << arg.clone }
|
103
|
+
|
104
|
+
self.cli_args=arguments.collect {|param|
|
105
|
+
param.gsub(/(?<==).*/) do |match|
|
106
|
+
match='"'+match+'"'
|
107
|
+
end
|
108
|
+
}.join(" ")
|
109
|
+
|
110
|
+
|
90
111
|
parse_command_line if @interactive
|
112
|
+
|
91
113
|
check_option_validity if @interactive
|
92
114
|
disable_logging unless @@verbose
|
93
115
|
|
116
|
+
|
94
117
|
puts "chimp #{VERSION} executing..." if (@interactive and not @use_chimpd) and not @@quiet
|
95
118
|
|
96
119
|
#
|
@@ -105,20 +128,50 @@ module Chimp
|
|
105
128
|
# Send the command to chimpd for execution
|
106
129
|
#
|
107
130
|
if @use_chimpd
|
108
|
-
|
131
|
+
timestamp=Time.now.to_i
|
132
|
+
length=6
|
133
|
+
self.job_uuid = (36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
|
134
|
+
ChimpDaemonClient.submit(@chimpd_host, @chimpd_port, self,self.job_uuid)
|
109
135
|
exit
|
136
|
+
else
|
137
|
+
#Connect to the Api
|
138
|
+
Connection.instance
|
139
|
+
if @interactive
|
140
|
+
Connection.connect
|
141
|
+
else
|
142
|
+
Connection.connect_and_cache
|
143
|
+
end
|
110
144
|
end
|
111
145
|
|
112
|
-
#
|
113
146
|
# If we're processing the command ourselves, then go
|
114
147
|
# ahead and start making API calls to select the objects
|
115
148
|
# to operate upon
|
116
149
|
#
|
150
|
+
|
151
|
+
# Get elements if --array has been passed
|
117
152
|
get_array_info
|
153
|
+
|
154
|
+
# Get elements if we are searching by tags
|
118
155
|
get_server_info
|
119
|
-
get_template_info
|
120
|
-
get_executable_info
|
121
156
|
|
157
|
+
# At this stage @servers should be populated with our findings
|
158
|
+
# Get ST info for all elements
|
159
|
+
if not @servers.empty?
|
160
|
+
get_template_info unless @servers.empty?
|
161
|
+
|
162
|
+
puts "Looking for the rightscripts (This might take some time)" if (@interactive and not @use_chimpd) and not @@quiet
|
163
|
+
get_executable_info
|
164
|
+
end
|
165
|
+
|
166
|
+
if Chimp.failure
|
167
|
+
#This is the failure point when executing standalone
|
168
|
+
Log.error "##################################################"
|
169
|
+
Log.error " API CALL FAILED FOR:"
|
170
|
+
Log.error " chimp #{@cli_args} "
|
171
|
+
Log.error " Run manually!"
|
172
|
+
Log.error "##################################################"
|
173
|
+
exit 1
|
174
|
+
end
|
122
175
|
#
|
123
176
|
# Optionally display the list of objects to operate on
|
124
177
|
# and prompt the user
|
@@ -127,48 +180,70 @@ module Chimp
|
|
127
180
|
list_of_objects = make_human_readable_list_of_objects
|
128
181
|
confirm = (list_of_objects.size > 0 and @action != :action_none) or @action == :action_none
|
129
182
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
183
|
+
if @script_to_run.nil?
|
184
|
+
verify("Your command will be executed on the following:", list_of_objects, confirm)
|
185
|
+
else
|
186
|
+
verify("Your command \""+@script_to_run.params['right_script']['name']+"\" will be executed on the following:", list_of_objects, confirm)
|
134
187
|
end
|
135
188
|
end
|
136
|
-
|
137
189
|
#
|
138
190
|
# Load the queue with work
|
139
191
|
#
|
140
|
-
|
141
|
-
|
192
|
+
if not @servers.first.nil? and ( not @executable.nil? or @action == :action_ssh or @action == :action_report)
|
193
|
+
jobs = generate_jobs(@servers, @server_template, @executable)
|
194
|
+
add_to_queue(jobs)
|
195
|
+
end
|
142
196
|
|
143
197
|
#
|
144
198
|
# Exit early if there is nothing to do
|
145
199
|
#
|
146
200
|
if @action == :action_none or queue.group[@group].size == 0
|
147
|
-
puts "No actions to perform." unless
|
201
|
+
puts "No actions to perform." unless self.quiet
|
148
202
|
else
|
149
203
|
do_work
|
150
204
|
end
|
151
205
|
end
|
152
206
|
|
153
207
|
#
|
154
|
-
#
|
155
|
-
# Used by chimpd
|
208
|
+
# Load up @array with server arrays to operate on
|
156
209
|
#
|
157
|
-
def
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
210
|
+
def get_array_info
|
211
|
+
return if @array_names.empty?
|
212
|
+
|
213
|
+
# The first thing to do here is make an api1.5 call to get the array hrefs.
|
214
|
+
arrays_hrefs=get_hrefs_for_arrays(@array_names)
|
215
|
+
# Then we filter on all the instances by this href
|
216
|
+
all_instances = Connection.all_instances() unless arrays_hrefs.empty?
|
217
|
+
if all_instances.nil?
|
218
|
+
Log.debug "No results from API query"
|
219
|
+
else
|
220
|
+
arrays_hrefs.each { |href|
|
221
|
+
@servers += all_instances.select {|s|
|
222
|
+
s['links']['incarnator']['href'] == href
|
223
|
+
}
|
224
|
+
}
|
225
|
+
end
|
226
|
+
# The result will be stored (not returned) into @servers
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# Go through each of the various ways to specify servers via
|
231
|
+
# the command line (tags, deployments, etc.) and get all the info
|
232
|
+
# needed from the RightScale API.
|
233
|
+
#
|
234
|
+
def get_server_info
|
235
|
+
@servers += get_servers_by_tag(@tags) unless tags.empty?
|
236
|
+
# Perhaps allow searchign by deployment
|
237
|
+
@servers += get_servers_by_deployment(@deployment_names) unless @deployment_names.empty?
|
164
238
|
end
|
165
239
|
|
166
240
|
#
|
167
241
|
# Get the ServerTemplate info from the API
|
168
242
|
#
|
169
243
|
def get_template_info
|
170
|
-
|
171
|
-
|
244
|
+
# If we have a server or an array
|
245
|
+
if not (@servers.first.nil? and @array_names.empty?)
|
246
|
+
@server_template = detect_server_template(@servers)
|
172
247
|
end
|
173
248
|
end
|
174
249
|
|
@@ -176,9 +251,38 @@ module Chimp
|
|
176
251
|
# Get the Executable (RightScript) info from the API
|
177
252
|
#
|
178
253
|
def get_executable_info
|
179
|
-
if not (@servers.empty?
|
180
|
-
|
181
|
-
|
254
|
+
if not (@servers.empty? )
|
255
|
+
if (@script != nil)
|
256
|
+
# If script is an uri/url no need to "detect it"
|
257
|
+
# https://my.rightscale.com/acct/9202/right_scripts/205347
|
258
|
+
if @script =~ /\A#{URI::regexp}\z/
|
259
|
+
if not @use_chimpd
|
260
|
+
puts "=================================================="
|
261
|
+
puts "WARNING! You will be running this script on all "
|
262
|
+
puts "server matches! (Press enter to continue)"
|
263
|
+
puts "=================================================="
|
264
|
+
gets
|
265
|
+
end
|
266
|
+
|
267
|
+
script_number = File.basename(@script)
|
268
|
+
|
269
|
+
s=Executable.new
|
270
|
+
s.params['right_script']['href']="right_script_href=/api/right_scripts/"+script_number
|
271
|
+
#Make an 1.5 call to extract name, by loading resource.
|
272
|
+
Log.debug "Making API 1.5 call : client.resource(#{s.params['right_script']['href'].scan(/=(.*)/).last.last})"
|
273
|
+
the_name = Connection.client.resource(s.params['right_script']['href'].scan(/=(.*)/).last.last).name
|
274
|
+
s.params['right_script']['name'] = the_name
|
275
|
+
@executable=s
|
276
|
+
else
|
277
|
+
#If its not an url, go ahead try to locate it in the ST"
|
278
|
+
@executable = detect_right_script(@server_template, @script)
|
279
|
+
end
|
280
|
+
else
|
281
|
+
# @script could be nil because we want to run ssh
|
282
|
+
if @action == :action_ssh
|
283
|
+
puts "Using SSH command: \"#{@ssh}\"" if @action == :action_ssh
|
284
|
+
end
|
285
|
+
end
|
182
286
|
end
|
183
287
|
end
|
184
288
|
|
@@ -292,6 +396,7 @@ module Chimp
|
|
292
396
|
@limit_start, @limit_end = arg.split(',')
|
293
397
|
when '--verbose', '-v'
|
294
398
|
@@verbose = true
|
399
|
+
Log.threshold = Logger::DEBUG
|
295
400
|
when '--quiet', '-q'
|
296
401
|
@@quiet = true
|
297
402
|
when '--dont-check-templates', '-0'
|
@@ -323,6 +428,17 @@ module Chimp
|
|
323
428
|
@verify = false
|
324
429
|
end
|
325
430
|
end
|
431
|
+
|
432
|
+
if @use_chimpd && ( @script.nil? || @script.empty? )
|
433
|
+
if @chimpd_wait_until_done == false
|
434
|
+
puts "#######################################################"
|
435
|
+
puts "ERROR: --script cannot be empty when sending to chimpd"
|
436
|
+
puts "#######################################################"
|
437
|
+
exit 1
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
|
326
442
|
rescue GetoptLong::InvalidOption => ex
|
327
443
|
help
|
328
444
|
exit 1
|
@@ -335,7 +451,6 @@ module Chimp
|
|
335
451
|
if @group_concurrency > @concurrency
|
336
452
|
@concurrency = @group_concurrency
|
337
453
|
end
|
338
|
-
|
339
454
|
end
|
340
455
|
|
341
456
|
#
|
@@ -361,269 +476,327 @@ module Chimp
|
|
361
476
|
end
|
362
477
|
|
363
478
|
#
|
364
|
-
#
|
365
|
-
# the command line (tags, deployments, etc.) and get all the info
|
366
|
-
# needed from the RightScale API.
|
367
|
-
#
|
368
|
-
def get_server_info
|
369
|
-
@servers += get_servers_by_tag(@tags)
|
370
|
-
@servers += get_servers_by_deployment(@deployment_names)
|
371
|
-
@servers = filter_out_non_operational_servers(@servers)
|
372
|
-
end
|
373
|
-
|
479
|
+
# Api1.6 equivalent
|
374
480
|
#
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
Log.debug "Breaking array into instances..."
|
387
|
-
@servers += get_servers_by_array(@array_names)
|
388
|
-
@array_names = []
|
481
|
+
def get_servers_by_tag(tags)
|
482
|
+
# Take tags and collapse it,
|
483
|
+
# Default case, tag is AND
|
484
|
+
if @match_all
|
485
|
+
t = tags.join("&tag=")
|
486
|
+
filter = "tag=#{t}"
|
487
|
+
servers = Connection.instances(filter)
|
488
|
+
else
|
489
|
+
t = tags.join(",")
|
490
|
+
filter = "tag=#{t}"
|
491
|
+
servers = Connection.instances(filter)
|
389
492
|
end
|
390
493
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
if not a.nil?
|
395
|
-
@arrays << a
|
494
|
+
if servers.nil?
|
495
|
+
if @ignore_errors
|
496
|
+
Log.warn "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}"
|
396
497
|
else
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
498
|
+
raise "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}\n"
|
499
|
+
end
|
500
|
+
elsif servers.empty?
|
501
|
+
if @ignore_errors
|
502
|
+
Log.warn "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}"
|
503
|
+
else
|
504
|
+
raise "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}\n"
|
402
505
|
end
|
403
506
|
end
|
507
|
+
|
508
|
+
servers = verify_tagged_instances(servers,tags)
|
509
|
+
|
510
|
+
return(servers)
|
404
511
|
end
|
405
512
|
|
406
513
|
#
|
407
|
-
#
|
514
|
+
# Verify that all returned instances from the API match our tag request
|
408
515
|
#
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
516
|
+
def verify_tagged_instances(servers,tags)
|
517
|
+
array_list = servers
|
518
|
+
# servers is an array of hashes
|
519
|
+
# verify that each object contains the tags.
|
520
|
+
if @match_all
|
521
|
+
# has to contain BOTH
|
522
|
+
matching_servers = array_list.select { |instance| (tags - instance['tags']).empty? }
|
523
|
+
|
524
|
+
else
|
525
|
+
# has to contain ANY
|
526
|
+
matching_servers = array_list.select { |instance| tags.any? {|tag| instance['tags'].include?(tag) }}
|
527
|
+
end
|
414
528
|
|
415
|
-
|
529
|
+
# Shall there be a discrepancy, we need to raise an error and end the run.
|
530
|
+
if matching_servers.size != servers.size
|
416
531
|
if @ignore_errors
|
417
|
-
Log.
|
532
|
+
Log.error "[#{Chimp.get_job_uuid}] #{servers.size - matching_servers.size} instances didnt match tag selection."
|
533
|
+
Log.error "[#{Chimp.get_job_uuid}] #{tags.join(" ")}"
|
534
|
+
Chimp.set_failure(true)
|
535
|
+
servers = []
|
418
536
|
else
|
419
|
-
|
537
|
+
raise "[#{Chimp.get_job_uuid}] #{servers.size - matching_servers.size} instances didnt match tag selection"
|
420
538
|
end
|
421
539
|
end
|
422
540
|
|
423
|
-
return
|
541
|
+
return servers
|
424
542
|
end
|
425
|
-
|
426
543
|
#
|
427
544
|
# Parse deployment names and get Server objects
|
428
545
|
#
|
429
|
-
# Returns: array of RestConnection::Server objects
|
430
|
-
#
|
431
546
|
def get_servers_by_deployment(names)
|
432
547
|
servers = []
|
548
|
+
all_instances = Connection.all_instances
|
433
549
|
|
434
|
-
|
435
|
-
|
436
|
-
d = ::Deployment.find_by_nickname(deployment).first
|
437
|
-
|
438
|
-
if d == nil
|
439
|
-
if @ignore_errors
|
440
|
-
Log.warn "cannot find deployment #{deployment}"
|
441
|
-
else
|
442
|
-
raise "cannot find deployment #{deployment}"
|
443
|
-
end
|
444
|
-
else
|
445
|
-
d.servers_no_reload.each do |s|
|
446
|
-
servers << s
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
end
|
550
|
+
result = all_instances.select {|i| names.any? {|n| i['links']['deployment']['name'] =~ /#{n}/ }}
|
551
|
+
servers = result
|
451
552
|
|
452
553
|
return(servers)
|
453
554
|
end
|
454
555
|
|
455
556
|
#
|
456
|
-
#
|
457
|
-
#
|
458
|
-
# Returns: array of RestConnection::Server objects
|
557
|
+
# Given some array names, return the arrays hrefs
|
558
|
+
# Api1.5
|
459
559
|
#
|
460
|
-
def
|
461
|
-
|
560
|
+
def get_hrefs_for_arrays(names)
|
561
|
+
result = []
|
562
|
+
arrays_hrefs = []
|
462
563
|
if names.size > 0
|
463
564
|
names.each do |array_name|
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
565
|
+
# Find if arrays exist, if not raise warning.
|
566
|
+
# One API call per array
|
567
|
+
Log.debug "Making API 1.5 call: client.server_arrays.index(:filter => [#{array_name}])"
|
568
|
+
result = Connection.client.server_arrays.index(:filter => ["name==#{array_name}"])
|
569
|
+
# Result is an array with all the server arrays
|
570
|
+
if result.size != 0
|
571
|
+
arrays_hrefs += result.collect(&:href)
|
572
|
+
else
|
573
|
+
if @ignore_errors
|
574
|
+
Log.debug "[#{Chimp.get_job_uuid}] Could not find array \"#{array_name}\""
|
575
|
+
else
|
576
|
+
Log.error "[#{Chimp.get_job_uuid}] Could not find array \"#{array_name}\""
|
469
577
|
end
|
470
578
|
end
|
471
579
|
end
|
472
|
-
|
580
|
+
if ( arrays_hrefs.empty? )
|
581
|
+
Log.debug "[#{Chimp.get_job_uuid}] Did not find any arrays that matched!" unless names.size == 1
|
582
|
+
end
|
583
|
+
|
584
|
+
return(arrays_hrefs)
|
473
585
|
|
474
|
-
|
586
|
+
end
|
475
587
|
end
|
476
588
|
|
477
589
|
#
|
478
|
-
#
|
479
|
-
#
|
480
|
-
# Returns: RestConnection::ServerTemplate
|
590
|
+
# Given a list of servers
|
481
591
|
#
|
482
|
-
def detect_server_template(
|
483
|
-
st = nil
|
592
|
+
def detect_server_template(servers)
|
484
593
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
if script and template == nil
|
490
|
-
Log.debug "Getting template URI..."
|
491
|
-
|
492
|
-
if not servers.empty?
|
493
|
-
for i in (0..servers.size - 1)
|
494
|
-
|
495
|
-
template = servers[i]['server_template_href'] if not servers[i].empty?
|
496
|
-
break if template
|
497
|
-
end
|
498
|
-
|
499
|
-
elsif not array_names_to_detect.empty?
|
500
|
-
array_names_to_detect.each do |array_name|
|
501
|
-
a = Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }.first
|
502
|
-
next unless a
|
503
|
-
template = a['server_template_href']
|
504
|
-
break if template
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
raise "Unable to locate ServerTemplate!" unless template
|
509
|
-
Log.debug "Template: #{template}"
|
594
|
+
Log.debug "Looking for server template"
|
595
|
+
st = []
|
596
|
+
if servers[0].nil?
|
597
|
+
return (st)
|
510
598
|
end
|
511
599
|
|
600
|
+
st += servers.collect { |s|
|
601
|
+
[s['href'],s['server_template']]
|
602
|
+
}.uniq {|a| a[0]}
|
603
|
+
|
512
604
|
#
|
513
|
-
#
|
605
|
+
# We return an array of server_template resources
|
606
|
+
# of the type [ st_href, st object ]
|
514
607
|
#
|
515
|
-
|
516
|
-
Log.debug "Looking up template..."
|
517
|
-
|
518
|
-
if template =~ /^http/
|
519
|
-
st = ::ServerTemplate.find(template)
|
520
|
-
else
|
521
|
-
st = ::ServerTemplate.find_by_nickname(template).first
|
522
|
-
end
|
523
|
-
|
524
|
-
if st == nil
|
525
|
-
raise "No matching ServerTemplate found!"
|
526
|
-
else
|
527
|
-
Log.debug "ServerTemplate: \"#{st['nickname']}\""
|
528
|
-
end
|
529
|
-
end
|
608
|
+
Log.debug "Found server templates"
|
530
609
|
|
531
610
|
return(st)
|
532
611
|
end
|
533
612
|
|
534
613
|
#
|
535
|
-
#
|
536
|
-
#
|
537
|
-
# Returns: RestConnection::Executable
|
614
|
+
# This function returns @script_to_run which is extracted from matching
|
615
|
+
# the desired script against all server templates or the script URL
|
538
616
|
#
|
539
617
|
def detect_right_script(st, script)
|
618
|
+
Log.debug "Looking for rightscript"
|
540
619
|
executable = nil
|
620
|
+
# In the event that chimpd find @op_scripts as nil, set it as an array.
|
621
|
+
if @op_scripts.nil?
|
622
|
+
@op_scripts = []
|
623
|
+
end
|
624
|
+
if st.nil?
|
625
|
+
return executable
|
626
|
+
end
|
541
627
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
628
|
+
# Take the sts and extract all operational scripts
|
629
|
+
@op_scripts = extract_operational_scripts(st)
|
630
|
+
|
631
|
+
# if script is empty, we will list all common scripts
|
632
|
+
# if not empty, we will list the first matching one
|
633
|
+
if @script == "" and @script != nil
|
634
|
+
# list all operational scripts
|
635
|
+
reduce_to_common_scripts(st.size)
|
636
|
+
|
637
|
+
script_id = list_and_select_op_script
|
638
|
+
|
639
|
+
# Provide the name + href
|
640
|
+
s = Executable.new
|
641
|
+
s.params['right_script']['href'] = @op_scripts[script_id][1].right_script.href
|
642
|
+
s.params['right_script']['name'] = @op_scripts[script_id][0]
|
643
|
+
@script_to_run = s
|
644
|
+
else
|
645
|
+
# Try to find the rightscript in our list of common operational scripts
|
646
|
+
@op_scripts.each do |rb|
|
647
|
+
script_name = rb[0]
|
648
|
+
if script_name.downcase.include?(script.downcase)
|
649
|
+
# We only need the name and the href
|
650
|
+
s = Executable.new
|
651
|
+
s.params['right_script']['href'] = rb[1].right_script.href
|
652
|
+
s.params['right_script']['name'] = script_name
|
653
|
+
@script_to_run = s
|
654
|
+
|
655
|
+
Log.debug "Found rightscript"
|
656
|
+
return @script_to_run
|
564
657
|
end
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
658
|
+
end
|
659
|
+
#
|
660
|
+
# If we reach here it means we didnt find the script in the operationals
|
661
|
+
#
|
662
|
+
if @script_to_run == nil
|
663
|
+
# Search outside common op scripts
|
664
|
+
search_for_script_in_sts(script, st)
|
665
|
+
if @script_to_run.nil?
|
666
|
+
if @interactive
|
667
|
+
puts "ERROR: Sorry, didnt find that ( "+script+" ), provide an URI instead"
|
668
|
+
puts "I searched in:"
|
669
|
+
st.each { |s|
|
670
|
+
puts " * "+s[1]['name']+" [Rev"+s[1]['version'].to_s+"]"
|
671
|
+
}
|
672
|
+
if not @ignore_errors
|
673
|
+
exit 1
|
674
|
+
end
|
572
675
|
else
|
573
|
-
|
676
|
+
Log.error "["+self.job_uuid+"] Sorry, didnt find the script: ( "+script+" )!"
|
677
|
+
return nil
|
574
678
|
end
|
679
|
+
else
|
680
|
+
if self.job_uuid.nil?
|
681
|
+
self.job_uuid = ""
|
682
|
+
end
|
683
|
+
Log.warn "["+self.job_uuid+"]\"#{@script_to_run.params['right_script']['name']}\" is not a common operational script!"
|
684
|
+
return @script_to_run
|
575
685
|
end
|
576
|
-
# Provide the href as the input for the block that will do the lookup
|
577
|
-
script = op_script_hrefs[ op_script_id ]
|
578
686
|
end
|
579
687
|
end
|
688
|
+
end
|
580
689
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
script_data = {}
|
596
|
-
script_data[ 'name' ] = script_obj.params['name']
|
597
|
-
script = ::RightScript.new({ :href => script_URI, :right_script => script_data })
|
690
|
+
def search_for_script_in_sts(script, st)
|
691
|
+
# Loop and look inside every st
|
692
|
+
st.each do |s|
|
693
|
+
Log.debug "Making API 1.5 call: client.resource(#{s[1]['href']})"
|
694
|
+
temp=Connection.client.resource(s[1]['href'])
|
695
|
+
temp.runnable_bindings.index.each do |x|
|
696
|
+
# Look for first match
|
697
|
+
if x.raw['right_script']['name'].downcase.include?(script.downcase)
|
698
|
+
Log.debug "Found requested righscript: #{script}"
|
699
|
+
# Provide the name + href
|
700
|
+
s = Executable.new
|
701
|
+
s.params['right_script']['href'] = x.raw['links'].find{|i| i['rel'] == 'right_script'}['href']
|
702
|
+
s.params['right_script']['name'] = x.raw['right_script']['name']
|
703
|
+
@script_to_run = s
|
598
704
|
end
|
705
|
+
end
|
706
|
+
end
|
707
|
+
# If we hit here, we never found the desired script
|
708
|
+
return
|
709
|
+
end
|
710
|
+
|
711
|
+
#
|
712
|
+
# Presents the user with a list of scripts contained in @op_scripts
|
713
|
+
# and Returns an integer indicating the selection
|
714
|
+
#
|
715
|
+
#
|
716
|
+
def list_and_select_op_script
|
717
|
+
puts "List of available operational scripts:"
|
718
|
+
puts "------------------------------------------------------------"
|
719
|
+
for i in 0..@op_scripts.length - 1
|
720
|
+
puts " %3d. #{@op_scripts[i][0]}" % i
|
721
|
+
end
|
722
|
+
puts "------------------------------------------------------------"
|
723
|
+
while true
|
724
|
+
printf "Type the number of the script to run and press Enter (Ctrl-C to quit): "
|
725
|
+
script_id = Integer(gets.chomp) rescue -1
|
726
|
+
if script_id >= 0 && script_id < @op_scripts.length
|
727
|
+
puts "Script choice: #{script_id}. #{@op_scripts[ script_id ][0]}"
|
728
|
+
break
|
599
729
|
else
|
600
|
-
|
601
|
-
|
730
|
+
puts "#{script_id < 0 ? 'Invalid input' : 'Input out of range'}."
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
return script_id
|
735
|
+
end
|
736
|
+
|
737
|
+
#
|
738
|
+
# Takes the number of st's to search in,
|
739
|
+
# and reduces @op_scripts to only those who are
|
740
|
+
# repeated enough times.
|
741
|
+
#
|
742
|
+
def reduce_to_common_scripts(number_of_st)
|
743
|
+
counts = Hash.new 0
|
744
|
+
@op_scripts.each { |s| counts[s[0]] +=1 }
|
745
|
+
|
746
|
+
b = @op_scripts.inject({}) do |res, row|
|
747
|
+
res[row[0]] ||= []
|
748
|
+
res[row[0]] << row[1]
|
749
|
+
res
|
602
750
|
end
|
603
751
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
end
|
752
|
+
b.inject([]) do |res, (key, values)|
|
753
|
+
res << [key, values.first] if values.size >= number_of_st
|
754
|
+
@op_scripts = res
|
755
|
+
end
|
756
|
+
end
|
610
757
|
|
611
|
-
|
758
|
+
#
|
759
|
+
# Returns all matching operational scripts in the st list passed
|
760
|
+
#
|
761
|
+
def extract_operational_scripts(st)
|
762
|
+
op_scripts = []
|
763
|
+
size = st.size
|
764
|
+
st.each do |s|
|
765
|
+
# Example of s structure
|
766
|
+
# ["/api/server_templates/351930003",
|
767
|
+
# {"id"=>351930003,
|
768
|
+
# "name"=>"RightScale Right_Site - 2015q1",
|
769
|
+
# "kind"=>"cm#server_template",
|
770
|
+
# "version"=>5,
|
771
|
+
# "href"=>"/api/server_templates/351930003"} ]
|
772
|
+
Log.debug "Making API 1.5 call: client.resource"
|
773
|
+
temp=Connection.client.resource(s[1]['href'])
|
774
|
+
temp.runnable_bindings.index.each do |x|
|
775
|
+
# only add the operational ones
|
776
|
+
if x.sequence == "operational"
|
777
|
+
name = x.raw['right_script']['name']
|
778
|
+
op_scripts.push([name, x])
|
779
|
+
end
|
780
|
+
end
|
612
781
|
end
|
613
782
|
|
614
|
-
|
783
|
+
#We now only have operational runnable_bindings under the script_objects array
|
784
|
+
if op_scripts.length < 1
|
785
|
+
raise "ERROR: No operational scripts found on the server(s). "
|
786
|
+
st.each {|s|
|
787
|
+
puts " (Search performed on server template '#{s[1]['name']}')"
|
788
|
+
}
|
789
|
+
end
|
790
|
+
return op_scripts
|
615
791
|
end
|
616
792
|
|
617
793
|
#
|
618
794
|
# Load up the queue with work
|
619
795
|
#
|
620
|
-
|
621
|
-
#
|
622
|
-
def generate_jobs(queue_servers, queue_arrays, queue_template, queue_executable)
|
796
|
+
def generate_jobs(queue_servers, queue_template, queue_executable)
|
623
797
|
counter = 0
|
624
798
|
tasks = []
|
625
799
|
Log.debug "Loading queue..."
|
626
|
-
|
627
800
|
#
|
628
801
|
# Configure group
|
629
802
|
#
|
@@ -631,50 +804,13 @@ module Chimp
|
|
631
804
|
ChimpQueue.instance.create_group(@group, @group_type, @group_concurrency)
|
632
805
|
end
|
633
806
|
|
634
|
-
#
|
635
|
-
# Process ServerArray selection
|
636
|
-
#
|
637
|
-
Log.debug("processing queue selection")
|
638
|
-
if not queue_arrays.empty?
|
639
|
-
queue_arrays.each do |array|
|
640
|
-
instances = filter_out_non_operational_servers(array.instances)
|
641
|
-
|
642
|
-
if not instances
|
643
|
-
Log.error("no instances in array!")
|
644
|
-
break
|
645
|
-
end
|
646
|
-
|
647
|
-
instances.each do |array_instance|
|
648
|
-
#
|
649
|
-
# Handle limiting options
|
650
|
-
#
|
651
|
-
counter += 1
|
652
|
-
next if @limit_start.to_i > 0 and counter < @limit_start.to_i
|
653
|
-
break if @limit_end.to_i > 0 and counter > @limit_end.to_i
|
654
|
-
a = ExecArray.new(
|
655
|
-
:array => array,
|
656
|
-
:server => array_instance,
|
657
|
-
:exec => queue_executable,
|
658
|
-
:inputs => @inputs,
|
659
|
-
:template => queue_template,
|
660
|
-
:timeout => @timeout,
|
661
|
-
:verbose => @@verbose,
|
662
|
-
:quiet => @@quiet
|
663
|
-
)
|
664
|
-
a.dry_run = @dry_run
|
665
|
-
ChimpQueue.instance.push(@group, a)
|
666
|
-
end
|
667
|
-
end
|
668
|
-
end
|
669
|
-
|
670
807
|
#
|
671
808
|
# Process Server selection
|
672
809
|
#
|
673
810
|
Log.debug("Processing server selection")
|
674
811
|
|
675
|
-
queue_servers.sort! { |a,b| a['
|
812
|
+
queue_servers.sort! { |a,b| a['name'] <=> b['name'] }
|
676
813
|
queue_servers.each do |server|
|
677
|
-
|
678
814
|
#
|
679
815
|
# Handle limiting options
|
680
816
|
#
|
@@ -685,18 +821,49 @@ module Chimp
|
|
685
821
|
#
|
686
822
|
# Construct the Server object
|
687
823
|
#
|
688
|
-
s =
|
689
|
-
|
690
|
-
s.
|
691
|
-
|
692
|
-
s.
|
693
|
-
s.
|
824
|
+
s = Server.new
|
825
|
+
|
826
|
+
s.params['href'] = server['href']
|
827
|
+
|
828
|
+
s.params['current_instance_href'] = s.params['href']
|
829
|
+
s.params['current-instance-href'] = s.params['href']
|
830
|
+
|
831
|
+
s.params['name'] = server['name']
|
832
|
+
s.params['nickname'] = s.params['name']
|
833
|
+
|
834
|
+
s.params['ip_address'] = server['public_ip_addresses'].first
|
835
|
+
s.params['ip-address'] = s.params['ip_address']
|
836
|
+
|
837
|
+
s.params['private-ip-address'] = server['private_ip_addresses'].first
|
838
|
+
s.params['private_ip_address'] = s.params['private-ip-address']
|
839
|
+
|
840
|
+
s.params['resource_uid'] = server['resource_uid']
|
841
|
+
s.params['resource-uid'] = s.params['resource_uid']
|
842
|
+
|
843
|
+
s.params['instance-type'] = server['links']['instance_type']['name']
|
844
|
+
s.params['instance_type'] = s.params['instance-type']
|
845
|
+
s.params['ec2_instance_type'] = s.params['instance-type']
|
846
|
+
s.params['ec2-instance-type'] = s.params['instance-type']
|
847
|
+
|
848
|
+
s.params['dns-name'] = server['public_dns_names'].first
|
849
|
+
s.params['dns_name'] = s.params['dns-name']
|
850
|
+
|
851
|
+
s.params['locked'] = server['locked']
|
852
|
+
s.params['state'] = server['state']
|
853
|
+
s.params['datacenter'] = server['links']['datacenter']['name']
|
854
|
+
|
855
|
+
# This will be useful for later on when we need to run scripts
|
856
|
+
Log.debug "Making API 1.5 call: client.resource"
|
857
|
+
s.object = Connection.client.resource(server['href'])
|
858
|
+
|
694
859
|
e = nil
|
695
860
|
|
861
|
+
# If @script has been passed
|
696
862
|
if queue_executable
|
697
863
|
e = ExecRightScript.new(
|
698
864
|
:server => s,
|
699
865
|
:exec => queue_executable,
|
866
|
+
:job_uuid => @job_uuid,
|
700
867
|
:inputs => @inputs,
|
701
868
|
:timeout => @timeout,
|
702
869
|
:verbose => @@verbose,
|
@@ -710,16 +877,8 @@ module Chimp
|
|
710
877
|
:verbose => @@verbose,
|
711
878
|
:quiet => @@quiet
|
712
879
|
)
|
713
|
-
elsif queue_template and not clone
|
714
|
-
e = ExecSetTemplate.new(
|
715
|
-
:server => s,
|
716
|
-
:template => queue_template,
|
717
|
-
:verbose => @@verbose,
|
718
|
-
:quiet => @@quiet
|
719
|
-
)
|
720
880
|
elsif @report
|
721
881
|
if s.href
|
722
|
-
s.href = s.href.sub("/current","")
|
723
882
|
e = ExecReport.new(:server => s, :verbose => @@verbose, :quiet => @@quiet)
|
724
883
|
e.fields = @report
|
725
884
|
end
|
@@ -735,9 +894,7 @@ module Chimp
|
|
735
894
|
|
736
895
|
tasks.push(e)
|
737
896
|
end
|
738
|
-
|
739
897
|
end
|
740
|
-
|
741
898
|
return(tasks)
|
742
899
|
end
|
743
900
|
|
@@ -846,16 +1003,6 @@ module Chimp
|
|
846
1003
|
return get_results(@group)
|
847
1004
|
end
|
848
1005
|
|
849
|
-
#
|
850
|
-
# Filter out non-operational servers
|
851
|
-
# Then add operational servers to the list of objects to display
|
852
|
-
#
|
853
|
-
def filter_out_non_operational_servers(servers)
|
854
|
-
Log.debug "Filtering out non-operational servers..."
|
855
|
-
servers.reject! { |s| s == nil || s['state'] != "operational" }
|
856
|
-
return(servers)
|
857
|
-
end
|
858
|
-
|
859
1006
|
#
|
860
1007
|
# Do work: either by submitting to chimpd
|
861
1008
|
# or running it ourselves.
|
@@ -881,22 +1028,61 @@ module Chimp
|
|
881
1028
|
puts "chimp run complete"
|
882
1029
|
end
|
883
1030
|
|
1031
|
+
#
|
1032
|
+
# Allow the set/retrieval of job_uuid from outside
|
1033
|
+
#
|
1034
|
+
def self.get_job_uuid
|
1035
|
+
@job_uuid
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
def self.set_job_uuid(value)
|
1039
|
+
@job_uuid = value
|
1040
|
+
end
|
1041
|
+
|
884
1042
|
#
|
885
1043
|
# Completely process a non-interactive chimp object command
|
1044
|
+
# This is used by chimpd, when processing a task.
|
886
1045
|
#
|
887
1046
|
def process
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
1047
|
+
Chimp.set_failure(false)
|
1048
|
+
Chimp.set_job_uuid(self.job_uuid)
|
1049
|
+
|
1050
|
+
Log.debug "[#{Chimp.get_job_uuid}] Processing task"
|
1051
|
+
|
1052
|
+
get_array_info unless Chimp.failure
|
1053
|
+
get_server_info unless Chimp.failure
|
1054
|
+
get_template_info unless Chimp.failure
|
1055
|
+
get_executable_info unless Chimp.failure
|
1056
|
+
|
1057
|
+
if Chimp.failure
|
1058
|
+
Log.error "##################################################"
|
1059
|
+
Log.error "["+self.job_uuid+"] API CALL FAILED FOR:"
|
1060
|
+
Log.error "["+self.job_uuid+"] chimp #{@cli_args} "
|
1061
|
+
Log.error "["+self.job_uuid+"] Run manually!"
|
1062
|
+
Log.error "##################################################"
|
1063
|
+
return []
|
1064
|
+
else
|
1065
|
+
if @servers.first.nil? or @executable.nil?
|
1066
|
+
Log.warn "["+self.job_uuid+"] Nothing to do for \"chimp #{@cli_args}\"."
|
1067
|
+
return []
|
1068
|
+
else
|
1069
|
+
return generate_jobs(@servers, @server_template, @executable)
|
1070
|
+
end
|
1071
|
+
end
|
893
1072
|
end
|
894
1073
|
|
895
1074
|
#
|
896
|
-
#
|
1075
|
+
# Asks for confirmation before continuing
|
897
1076
|
#
|
898
|
-
def
|
899
|
-
|
1077
|
+
def ask_confirmation(prompt = 'Continue?', default = false)
|
1078
|
+
a = ''
|
1079
|
+
s = default ? '[Y/n]' : '[y/N]'
|
1080
|
+
d = default ? 'y' : 'n'
|
1081
|
+
until %w[y n].include? a
|
1082
|
+
a = ask("#{prompt} #{s} ") { |q| q.limit = 1; q.case = :downcase }
|
1083
|
+
a = d if a.length == 0
|
1084
|
+
end
|
1085
|
+
a == 'y'
|
900
1086
|
end
|
901
1087
|
|
902
1088
|
#
|
@@ -967,13 +1153,7 @@ module Chimp
|
|
967
1153
|
STDOUT.sync = true
|
968
1154
|
STDERR.sync = true
|
969
1155
|
|
970
|
-
if @@verbose
|
971
|
-
Log.threshold = Logger::DEBUG
|
972
|
-
elsif @@quiet == true
|
973
|
-
Log.threshold = Logger::WARN
|
974
|
-
else
|
975
|
-
Log.threshold = Logger::INFO
|
976
|
-
end
|
1156
|
+
Log.threshold= Logger::DEBUG if @@verbose
|
977
1157
|
end
|
978
1158
|
|
979
1159
|
def self.verbose?
|
@@ -987,8 +1167,20 @@ module Chimp
|
|
987
1167
|
return 0
|
988
1168
|
end
|
989
1169
|
|
1170
|
+
def self.get_job_uuid
|
1171
|
+
@job_uuid
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
def self.failure
|
1175
|
+
return @failure
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
def self.set_failure(status)
|
1179
|
+
@failure = status
|
1180
|
+
end
|
1181
|
+
|
990
1182
|
####################################################
|
991
|
-
private
|
1183
|
+
#private
|
992
1184
|
####################################################
|
993
1185
|
|
994
1186
|
#
|
@@ -1105,7 +1297,6 @@ module Chimp
|
|
1105
1297
|
puts "Script OK. All the servers share the same template and the script is included in it."
|
1106
1298
|
end
|
1107
1299
|
end
|
1108
|
-
puts
|
1109
1300
|
end
|
1110
1301
|
|
1111
1302
|
#
|
@@ -1114,16 +1305,10 @@ module Chimp
|
|
1114
1305
|
def make_human_readable_list_of_objects
|
1115
1306
|
list_of_objects = []
|
1116
1307
|
|
1117
|
-
if @servers
|
1118
|
-
list_of_objects += @servers.map { |s| s['
|
1308
|
+
if @servers and not @servers.first.nil?
|
1309
|
+
list_of_objects += @servers.map { |s| s['name'] }
|
1119
1310
|
end
|
1120
1311
|
|
1121
|
-
if @arrays
|
1122
|
-
@arrays.each do |a|
|
1123
|
-
i = filter_out_non_operational_servers(a.instances)
|
1124
|
-
list_of_objects += i.map { |j| j['nickname'] }
|
1125
|
-
end
|
1126
|
-
end
|
1127
1312
|
return(list_of_objects)
|
1128
1313
|
end
|
1129
1314
|
|
@@ -1168,7 +1353,7 @@ module Chimp
|
|
1168
1353
|
puts " --progress toggle progress indicator"
|
1169
1354
|
puts " --noprompt don't prompt with list of objects to run against"
|
1170
1355
|
puts " --noverify disable interactive verification of errors"
|
1171
|
-
puts " --verbose
|
1356
|
+
puts " --verbose be more verbose"
|
1172
1357
|
puts " --dont-check-templates don't check for script even if servers have diff. templates"
|
1173
1358
|
puts " --quiet suppress non-essential output"
|
1174
1359
|
puts " --version display version and exit"
|
@@ -1180,13 +1365,9 @@ module Chimp
|
|
1180
1365
|
puts
|
1181
1366
|
puts "Misc Notes:"
|
1182
1367
|
puts " * If you leave the name of a --script or --ssh command blank, chimp will prompt you"
|
1183
|
-
puts " *
|
1184
|
-
puts " *
|
1185
|
-
puts " *
|
1186
|
-
puts " server_template_href, deployment_href, created_at, updated_at"
|
1187
|
-
puts
|
1368
|
+
puts " * URIs must be API URIs in the format https://us-3.rightscale.com/acct/<acct>/right_scripts/<script_id>"
|
1369
|
+
puts " * The following reporting keywords can be used: ip-address,name,href,private-ip-address,resource_uid,"
|
1370
|
+
puts " * ec2-instance-type,datacenter,dns-name,locked,tag=foo"
|
1188
1371
|
end
|
1189
|
-
|
1190
1372
|
end
|
1191
1373
|
end
|
1192
|
-
|