right_chimp 1.1.3 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- RestClient.log = nil
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
- ChimpDaemonClient.submit(@chimpd_host, @chimpd_port, self)
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
- verify("Your command will be executed on the following:", list_of_objects, confirm)
131
-
132
- if @servers.length >= 2 and @server_template and @executable and not @dont_check_templates_for_script
133
- warn_if_rightscript_not_in_all_servers @servers, @server_template, @executable
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
- jobs = generate_jobs(@servers, @arrays, @server_template, @executable)
141
- add_to_queue(jobs)
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 @@quiet
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
- # Process a non-interactive chimp object command
155
- # Used by chimpd
208
+ # Load up @array with server arrays to operate on
156
209
  #
157
- def process
158
- get_array_info
159
- get_server_info
160
- get_template_info
161
- get_executable_info
162
- jobs = generate_jobs(@servers, @arrays, @server_template, @executable)
163
- return(jobs)
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
- if not (@servers.empty? and @array_names.empty?)
171
- @server_template = detect_server_template(@template, @script, @servers, @array_names)
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? and @array_names.empty?)
180
- @executable = detect_right_script(@server_template, @script)
181
- puts "Using SSH command: \"#{@ssh}\"" if @action == :action_ssh
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
- # Go through each of the various ways to specify servers via
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
- # Load up @array with server arrays to operate on
376
- #
377
- def get_array_info
378
- return if @array_names.empty?
379
-
380
- #
381
- # Some operations (e.g. ExecSSH) require individual server information.
382
- # Check for @break_array_into_instances and break up the ServerArray
383
- # into Servers as necessary.
384
- #
385
- if @break_array_into_instances
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
- @array_names.each do |array_name|
392
- Log.debug "Querying API for ServerArray \'#{array_name}\'..."
393
- a = Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }.first
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
- if @ignore_errors
398
- Log.warn "cannot find ServerArray #{array_name}"
399
- else
400
- raise "cannot find ServerArray #{array_name}"
401
- end
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
- # Get servers to operate on via a tag query
514
+ # Verify that all returned instances from the API match our tag request
408
515
  #
409
- # Returns: array of RestConnection::Server objects
410
- #
411
- def get_servers_by_tag(tags)
412
- return([]) unless tags.size > 0
413
- servers = ::Tag.search("ec2_instance", tags, :match_all => @match_all)
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
- if tags.size > 0 and servers.nil? or servers.empty?
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.warn "Tag query returned no results: #{tags.join(" ")}"
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
- raise "Tag query returned no results: #{tags.join(" ")}"
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(servers)
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
- if names.size > 0
435
- names.each do |deployment|
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
- # Parse array names
457
- #
458
- # Returns: array of RestConnection::Server objects
557
+ # Given some array names, return the arrays hrefs
558
+ # Api1.5
459
559
  #
460
- def get_servers_by_array(names)
461
- array_servers = []
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
- all_arrays = ::Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }
465
-
466
- if all_arrays != nil and all_arrays.first != nil
467
- all_arrays.first.instances.each do |s|
468
- array_servers << s
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
- end
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
- return(array_servers)
586
+ end
475
587
  end
476
588
 
477
589
  #
478
- # ServerTemplate auto-detection
479
- #
480
- # Returns: RestConnection::ServerTemplate
590
+ # Given a list of servers
481
591
  #
482
- def detect_server_template(template, script, servers, array_names_to_detect)
483
- st = nil
592
+ def detect_server_template(servers)
484
593
 
485
- #
486
- # If we have a script name but no template, check
487
- # each server for the script until we locate it.
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
- # Now look up the ServerTemplate via the RightScale API
605
+ # We return an array of server_template resources
606
+ # of the type [ st_href, st object ]
514
607
  #
515
- if template
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
- # Look up the RightScript
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
- if script == ""
543
- if not @interactive
544
- puts "Error: empty --script= option is supported only in interactive mode. Exiting."
545
- exit 1
546
- end
547
- # Find operational scripts that exist in this server template
548
- op_script_names = ['dummy name'] # Placeholder for #0 since we want to offer choices 1..n
549
- op_script_hrefs = [ 'dummy href' ]
550
- st.executables.each do |ex|
551
- if ex.apply == "operational"
552
- op_script_names.push( ex.name )
553
- op_script_hrefs.push( ex.href )
554
- end
555
- end
556
- if op_script_names.length <= 1
557
- puts "Warning: No operational scripts found on the server(s). "
558
- puts " (Search performed on server template '#{st.nickname}')"
559
- else
560
- puts "List of available operational scripts in the server template: ('#{st.nickname}')"
561
- puts "------------------------------------------------------------"
562
- for i in 1..op_script_names.length - 1
563
- puts " %3d. #{op_script_names[i]}" % i
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
- puts "------------------------------------------------------------"
566
- while true
567
- printf "Type the number of the script to run and press Enter (Ctrl-C to quit): "
568
- op_script_id = Integer(gets.chomp) rescue -1
569
- if op_script_id > 0 && op_script_id < op_script_names.length
570
- puts "Script choice: #{op_script_id}. #{op_script_names[ op_script_id ]}"
571
- break
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
- puts "#{op_script_id < 0 ? 'Invalid input' : 'Input out of range'}."
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
- if script
582
- if script =~ /^http/ or script =~ /^\d+$/
583
- if script =~ /^\d+$/
584
- url_prefix = st.params['href'].match( /^.*\/acct\/\d+/)[0] # extract the 'https://my.rightscale.com/api/acct/<account_id>' part from the template's href
585
- script = url_prefix + "/right_scripts/#{script}"
586
- end
587
- script_URI = script
588
- Log.debug "Looking for script href \"#{script_URI}\""
589
- puts
590
- # First look up the script URI in the template.
591
- # It *will* be found if we came here from the 'if script = ""' block
592
- script = st.executables.detect { |ex| ex.href == script }
593
- if not script
594
- script_obj = ::RightScript.find(script_URI)
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
- Log.debug "Looking for script \"#{script}\""
601
- script = st.executables.detect { |ex| ex.name =~ /#{script}/ }
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
- if script != nil and script['right_script'] != nil
605
- puts "RightScript: \"#{script['right_script']['name']}\"" if @interactive
606
- else
607
- puts "No matching RightScript found!"
608
- raise "No matching RightScript found!"
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
- executable = script
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
- return(executable)
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
- # FIXME this needs to be refactored
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['nickname'] <=> b['nickname'] }
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 = ::Server.new
689
- s.href = server['href']
690
- s.current_instance_href = server['current_instance_href']
691
- s.name = server['nickname'] || server['name']
692
- s.nickname = s.name
693
- s.ip_address = server['ip-address'] || server['ip_address']
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
- get_array_info
889
- get_server_info
890
- get_template_info
891
- get_executable_info
892
- return generate_jobs(@servers, @arrays, @server_template, @executable)
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
- # Always returns 0. Used for chimpd compatibility.
1075
+ # Asks for confirmation before continuing
897
1076
  #
898
- def job_id
899
- return 0
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 == true
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['nickname'] }
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 display rest_connection log messages"
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 " * You cannot operate on array instances by selecting them with tag queries"
1184
- puts " * URIs must be API URIs in the format https://my.rightscale.com/api/acct/<acct>/ec2_server_templates/<id>"
1185
- puts " * The following reporting keywords can be used: nickname, ip-address, state, server_type, href"
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
-