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.
@@ -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
-