lib/right_chimp/Chimp.rb in right_chimp-1.1.3 vs lib/right_chimp/Chimp.rb in right_chimp-2.0
- old
+ new
@@ -1,16 +1,15 @@
#
# The Chimp class encapsulates the command-line program logic
#
-
module Chimp
class Chimp
+
attr_accessor :concurrency, :delay, :retry_count, :hold, :progress, :prompt,
:quiet, :use_chimpd, :chimpd_host, :chimpd_port, :tags, :array_names,
- :deployment_names, :script, :servers, :ssh, :report, :interactive, :action,
- :limit_start, :limit_end, :dry_run, :group, :job_id, :verify
-
+ :deployment_names, :script, :all_scripts, :servers, :ssh, :report, :interactive, :action,
+ :limit_start, :limit_end, :dry_run, :group, :job_id, :job_uuid, :verify, :cli_args
#
# These class variables control verbosity
#
@@verbose = false
@@quiet = false
@@ -51,11 +50,14 @@
#
# Options for selecting objects to work on
#
@current = true
@match_all = true
+
+ # This is an array of json data for each instance
@servers = []
+
@arrays = []
@tags = []
@array_names = []
@deployment_names = []
@template = nil
@@ -76,23 +78,44 @@
@use_chimpd = false
@chimpd_host = 'localhost'
@chimpd_port = 9055
@chimpd_wait_until_done = false
- RestClient.log = nil
+ #
+ # Will contain the operational scripts we have found
+ # In the form: [name, href]
+ @op_scripts = []
+
+ #
+ # This will contain the href and the name of the script to be run
+ # in the form: [name, href]
+ @script_to_run = nil
end
#
# Entry point for the chimp command line application
#
def run
queue = ChimpQueue.instance
+ arguments = []
+
+ ARGV.each { |arg| arguments << arg.clone }
+
+ self.cli_args=arguments.collect {|param|
+ param.gsub(/(?<==).*/) do |match|
+ match='"'+match+'"'
+ end
+ }.join(" ")
+
+
parse_command_line if @interactive
+
check_option_validity if @interactive
disable_logging unless @@verbose
+
puts "chimp #{VERSION} executing..." if (@interactive and not @use_chimpd) and not @@quiet
#
# Wait for chimpd to complete tasks
#
@@ -103,84 +126,165 @@
#
# Send the command to chimpd for execution
#
if @use_chimpd
- ChimpDaemonClient.submit(@chimpd_host, @chimpd_port, self)
+ timestamp=Time.now.to_i
+ length=6
+ self.job_uuid = (36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
+ ChimpDaemonClient.submit(@chimpd_host, @chimpd_port, self,self.job_uuid)
exit
+ else
+ #Connect to the Api
+ Connection.instance
+ if @interactive
+ Connection.connect
+ else
+ Connection.connect_and_cache
+ end
end
- #
# If we're processing the command ourselves, then go
# ahead and start making API calls to select the objects
# to operate upon
#
+
+ # Get elements if --array has been passed
get_array_info
+
+ # Get elements if we are searching by tags
get_server_info
- get_template_info
- get_executable_info
+ # At this stage @servers should be populated with our findings
+ # Get ST info for all elements
+ if not @servers.empty?
+ get_template_info unless @servers.empty?
+
+ puts "Looking for the rightscripts (This might take some time)" if (@interactive and not @use_chimpd) and not @@quiet
+ get_executable_info
+ end
+
+ if Chimp.failure
+ #This is the failure point when executing standalone
+ Log.error "##################################################"
+ Log.error " API CALL FAILED FOR:"
+ Log.error " chimp #{@cli_args} "
+ Log.error " Run manually!"
+ Log.error "##################################################"
+ exit 1
+ end
#
# Optionally display the list of objects to operate on
# and prompt the user
#
if @prompt and @interactive
list_of_objects = make_human_readable_list_of_objects
confirm = (list_of_objects.size > 0 and @action != :action_none) or @action == :action_none
- verify("Your command will be executed on the following:", list_of_objects, confirm)
-
- if @servers.length >= 2 and @server_template and @executable and not @dont_check_templates_for_script
- warn_if_rightscript_not_in_all_servers @servers, @server_template, @executable
+ if @script_to_run.nil?
+ verify("Your command will be executed on the following:", list_of_objects, confirm)
+ else
+ verify("Your command \""+@script_to_run.params['right_script']['name']+"\" will be executed on the following:", list_of_objects, confirm)
end
end
-
#
# Load the queue with work
#
- jobs = generate_jobs(@servers, @arrays, @server_template, @executable)
- add_to_queue(jobs)
+ if not @servers.first.nil? and ( not @executable.nil? or @action == :action_ssh or @action == :action_report)
+ jobs = generate_jobs(@servers, @server_template, @executable)
+ add_to_queue(jobs)
+ end
#
# Exit early if there is nothing to do
#
if @action == :action_none or queue.group[@group].size == 0
- puts "No actions to perform." unless @@quiet
+ puts "No actions to perform." unless self.quiet
else
do_work
end
end
#
- # Process a non-interactive chimp object command
- # Used by chimpd
+ # Load up @array with server arrays to operate on
#
- def process
- get_array_info
- get_server_info
- get_template_info
- get_executable_info
- jobs = generate_jobs(@servers, @arrays, @server_template, @executable)
- return(jobs)
+ def get_array_info
+ return if @array_names.empty?
+
+ # The first thing to do here is make an api1.5 call to get the array hrefs.
+ arrays_hrefs=get_hrefs_for_arrays(@array_names)
+ # Then we filter on all the instances by this href
+ all_instances = Connection.all_instances() unless arrays_hrefs.empty?
+ if all_instances.nil?
+ Log.debug "No results from API query"
+ else
+ arrays_hrefs.each { |href|
+ @servers += all_instances.select {|s|
+ s['links']['incarnator']['href'] == href
+ }
+ }
+ end
+ # The result will be stored (not returned) into @servers
end
#
+ # Go through each of the various ways to specify servers via
+ # the command line (tags, deployments, etc.) and get all the info
+ # needed from the RightScale API.
+ #
+ def get_server_info
+ @servers += get_servers_by_tag(@tags) unless tags.empty?
+ # Perhaps allow searchign by deployment
+ @servers += get_servers_by_deployment(@deployment_names) unless @deployment_names.empty?
+ end
+
+ #
# Get the ServerTemplate info from the API
#
def get_template_info
- if not (@servers.empty? and @array_names.empty?)
- @server_template = detect_server_template(@template, @script, @servers, @array_names)
+ # If we have a server or an array
+ if not (@servers.first.nil? and @array_names.empty?)
+ @server_template = detect_server_template(@servers)
end
end
#
# Get the Executable (RightScript) info from the API
#
def get_executable_info
- if not (@servers.empty? and @array_names.empty?)
- @executable = detect_right_script(@server_template, @script)
- puts "Using SSH command: \"#{@ssh}\"" if @action == :action_ssh
+ if not (@servers.empty? )
+ if (@script != nil)
+ # If script is an uri/url no need to "detect it"
+ # https://my.rightscale.com/acct/9202/right_scripts/205347
+ if @script =~ /\A#{URI::regexp}\z/
+ if not @use_chimpd
+ puts "=================================================="
+ puts "WARNING! You will be running this script on all "
+ puts "server matches! (Press enter to continue)"
+ puts "=================================================="
+ gets
+ end
+
+ script_number = File.basename(@script)
+
+ s=Executable.new
+ s.params['right_script']['href']="right_script_href=/api/right_scripts/"+script_number
+ #Make an 1.5 call to extract name, by loading resource.
+ Log.debug "Making API 1.5 call : client.resource(#{s.params['right_script']['href'].scan(/=(.*)/).last.last})"
+ the_name = Connection.client.resource(s.params['right_script']['href'].scan(/=(.*)/).last.last).name
+ s.params['right_script']['name'] = the_name
+ @executable=s
+ else
+ #If its not an url, go ahead try to locate it in the ST"
+ @executable = detect_right_script(@server_template, @script)
+ end
+ else
+ # @script could be nil because we want to run ssh
+ if @action == :action_ssh
+ puts "Using SSH command: \"#{@ssh}\"" if @action == :action_ssh
+ end
+ end
end
end
#
# Parse command line options
@@ -290,10 +394,11 @@
@retry_count = arg.to_i
when '--limit', '-l'
@limit_start, @limit_end = arg.split(',')
when '--verbose', '-v'
@@verbose = true
+ Log.threshold = Logger::DEBUG
when '--quiet', '-q'
@@quiet = true
when '--dont-check-templates', '-0'
@dont_check_templates_for_script = true
when '--version'
@@ -321,10 +426,21 @@
@timeout = arg
when '--noverify'
@verify = false
end
end
+
+ if @use_chimpd && ( @script.nil? || @script.empty? )
+ if @chimpd_wait_until_done == false
+ puts "#######################################################"
+ puts "ERROR: --script cannot be empty when sending to chimpd"
+ puts "#######################################################"
+ exit 1
+ end
+ end
+
+
rescue GetoptLong::InvalidOption => ex
help
exit 1
end
@@ -333,11 +449,10 @@
# let's make sure that a few things make sense
#
if @group_concurrency > @concurrency
@concurrency = @group_concurrency
end
-
end
#
# Check for any invalid combinations of command line options
#
@@ -359,346 +474,398 @@
exit 1
end
end
#
- # Go through each of the various ways to specify servers via
- # the command line (tags, deployments, etc.) and get all the info
- # needed from the RightScale API.
+ # Api1.6 equivalent
#
- def get_server_info
- @servers += get_servers_by_tag(@tags)
- @servers += get_servers_by_deployment(@deployment_names)
- @servers = filter_out_non_operational_servers(@servers)
- end
-
- #
- # Load up @array with server arrays to operate on
- #
- def get_array_info
- return if @array_names.empty?
-
- #
- # Some operations (e.g. ExecSSH) require individual server information.
- # Check for @break_array_into_instances and break up the ServerArray
- # into Servers as necessary.
- #
- if @break_array_into_instances
- Log.debug "Breaking array into instances..."
- @servers += get_servers_by_array(@array_names)
- @array_names = []
+ def get_servers_by_tag(tags)
+ # Take tags and collapse it,
+ # Default case, tag is AND
+ if @match_all
+ t = tags.join("&tag=")
+ filter = "tag=#{t}"
+ servers = Connection.instances(filter)
+ else
+ t = tags.join(",")
+ filter = "tag=#{t}"
+ servers = Connection.instances(filter)
end
- @array_names.each do |array_name|
- Log.debug "Querying API for ServerArray \'#{array_name}\'..."
- a = Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }.first
- if not a.nil?
- @arrays << a
+ if servers.nil?
+ if @ignore_errors
+ Log.warn "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}"
else
- if @ignore_errors
- Log.warn "cannot find ServerArray #{array_name}"
- else
- raise "cannot find ServerArray #{array_name}"
- end
+ raise "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}\n"
end
+ elsif servers.empty?
+ if @ignore_errors
+ Log.warn "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}"
+ else
+ raise "[#{Chimp.get_job_uuid}]Tag query returned no results: #{tags.join(" ")}\n"
+ end
end
+
+ servers = verify_tagged_instances(servers,tags)
+
+ return(servers)
end
#
- # Get servers to operate on via a tag query
+ # Verify that all returned instances from the API match our tag request
#
- # Returns: array of RestConnection::Server objects
- #
- def get_servers_by_tag(tags)
- return([]) unless tags.size > 0
- servers = ::Tag.search("ec2_instance", tags, :match_all => @match_all)
+ def verify_tagged_instances(servers,tags)
+ array_list = servers
+ # servers is an array of hashes
+ # verify that each object contains the tags.
+ if @match_all
+ # has to contain BOTH
+ matching_servers = array_list.select { |instance| (tags - instance['tags']).empty? }
- if tags.size > 0 and servers.nil? or servers.empty?
+ else
+ # has to contain ANY
+ matching_servers = array_list.select { |instance| tags.any? {|tag| instance['tags'].include?(tag) }}
+ end
+
+ # Shall there be a discrepancy, we need to raise an error and end the run.
+ if matching_servers.size != servers.size
if @ignore_errors
- Log.warn "Tag query returned no results: #{tags.join(" ")}"
+ Log.error "[#{Chimp.get_job_uuid}] #{servers.size - matching_servers.size} instances didnt match tag selection."
+ Log.error "[#{Chimp.get_job_uuid}] #{tags.join(" ")}"
+ Chimp.set_failure(true)
+ servers = []
else
- raise "Tag query returned no results: #{tags.join(" ")}"
+ raise "[#{Chimp.get_job_uuid}] #{servers.size - matching_servers.size} instances didnt match tag selection"
end
end
- return(servers)
+ return servers
end
-
#
# Parse deployment names and get Server objects
#
- # Returns: array of RestConnection::Server objects
- #
def get_servers_by_deployment(names)
servers = []
+ all_instances = Connection.all_instances
- if names.size > 0
- names.each do |deployment|
- d = ::Deployment.find_by_nickname(deployment).first
+ result = all_instances.select {|i| names.any? {|n| i['links']['deployment']['name'] =~ /#{n}/ }}
+ servers = result
- if d == nil
- if @ignore_errors
- Log.warn "cannot find deployment #{deployment}"
- else
- raise "cannot find deployment #{deployment}"
- end
- else
- d.servers_no_reload.each do |s|
- servers << s
- end
- end
- end
- end
-
return(servers)
end
#
- # Parse array names
+ # Given some array names, return the arrays hrefs
+ # Api1.5
#
- # Returns: array of RestConnection::Server objects
- #
- def get_servers_by_array(names)
- array_servers = []
+ def get_hrefs_for_arrays(names)
+ result = []
+ arrays_hrefs = []
if names.size > 0
names.each do |array_name|
- all_arrays = ::Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }
-
- if all_arrays != nil and all_arrays.first != nil
- all_arrays.first.instances.each do |s|
- array_servers << s
+ # Find if arrays exist, if not raise warning.
+ # One API call per array
+ Log.debug "Making API 1.5 call: client.server_arrays.index(:filter => [#{array_name}])"
+ result = Connection.client.server_arrays.index(:filter => ["name==#{array_name}"])
+ # Result is an array with all the server arrays
+ if result.size != 0
+ arrays_hrefs += result.collect(&:href)
+ else
+ if @ignore_errors
+ Log.debug "[#{Chimp.get_job_uuid}] Could not find array \"#{array_name}\""
+ else
+ Log.error "[#{Chimp.get_job_uuid}] Could not find array \"#{array_name}\""
end
end
end
- end
+ if ( arrays_hrefs.empty? )
+ Log.debug "[#{Chimp.get_job_uuid}] Did not find any arrays that matched!" unless names.size == 1
+ end
- return(array_servers)
+ return(arrays_hrefs)
+
+ end
end
#
- # ServerTemplate auto-detection
+ # Given a list of servers
#
- # Returns: RestConnection::ServerTemplate
- #
- def detect_server_template(template, script, servers, array_names_to_detect)
- st = nil
+ def detect_server_template(servers)
- #
- # If we have a script name but no template, check
- # each server for the script until we locate it.
- #
- if script and template == nil
- Log.debug "Getting template URI..."
-
- if not servers.empty?
- for i in (0..servers.size - 1)
-
- template = servers[i]['server_template_href'] if not servers[i].empty?
- break if template
- end
-
- elsif not array_names_to_detect.empty?
- array_names_to_detect.each do |array_name|
- a = Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }.first
- next unless a
- template = a['server_template_href']
- break if template
- end
- end
-
- raise "Unable to locate ServerTemplate!" unless template
- Log.debug "Template: #{template}"
+ Log.debug "Looking for server template"
+ st = []
+ if servers[0].nil?
+ return (st)
end
+ st += servers.collect { |s|
+ [s['href'],s['server_template']]
+ }.uniq {|a| a[0]}
+
#
- # Now look up the ServerTemplate via the RightScale API
+ # We return an array of server_template resources
+ # of the type [ st_href, st object ]
#
- if template
- Log.debug "Looking up template..."
+ Log.debug "Found server templates"
- if template =~ /^http/
- st = ::ServerTemplate.find(template)
- else
- st = ::ServerTemplate.find_by_nickname(template).first
- end
-
- if st == nil
- raise "No matching ServerTemplate found!"
- else
- Log.debug "ServerTemplate: \"#{st['nickname']}\""
- end
- end
-
return(st)
end
#
- # Look up the RightScript
+ # This function returns @script_to_run which is extracted from matching
+ # the desired script against all server templates or the script URL
#
- # Returns: RestConnection::Executable
- #
def detect_right_script(st, script)
+ Log.debug "Looking for rightscript"
executable = nil
+ # In the event that chimpd find @op_scripts as nil, set it as an array.
+ if @op_scripts.nil?
+ @op_scripts = []
+ end
+ if st.nil?
+ return executable
+ end
- if script == ""
- if not @interactive
- puts "Error: empty --script= option is supported only in interactive mode. Exiting."
- exit 1
- end
- # Find operational scripts that exist in this server template
- op_script_names = ['dummy name'] # Placeholder for #0 since we want to offer choices 1..n
- op_script_hrefs = [ 'dummy href' ]
- st.executables.each do |ex|
- if ex.apply == "operational"
- op_script_names.push( ex.name )
- op_script_hrefs.push( ex.href )
- end
- end
- if op_script_names.length <= 1
- puts "Warning: No operational scripts found on the server(s). "
- puts " (Search performed on server template '#{st.nickname}')"
- else
- puts "List of available operational scripts in the server template: ('#{st.nickname}')"
- puts "------------------------------------------------------------"
- for i in 1..op_script_names.length - 1
- puts " %3d. #{op_script_names[i]}" % i
+ # Take the sts and extract all operational scripts
+ @op_scripts = extract_operational_scripts(st)
+
+ # if script is empty, we will list all common scripts
+ # if not empty, we will list the first matching one
+ if @script == "" and @script != nil
+ # list all operational scripts
+ reduce_to_common_scripts(st.size)
+
+ script_id = list_and_select_op_script
+
+ # Provide the name + href
+ s = Executable.new
+ s.params['right_script']['href'] = @op_scripts[script_id][1].right_script.href
+ s.params['right_script']['name'] = @op_scripts[script_id][0]
+ @script_to_run = s
+ else
+ # Try to find the rightscript in our list of common operational scripts
+ @op_scripts.each do |rb|
+ script_name = rb[0]
+ if script_name.downcase.include?(script.downcase)
+ # We only need the name and the href
+ s = Executable.new
+ s.params['right_script']['href'] = rb[1].right_script.href
+ s.params['right_script']['name'] = script_name
+ @script_to_run = s
+
+ Log.debug "Found rightscript"
+ return @script_to_run
end
- puts "------------------------------------------------------------"
- while true
- printf "Type the number of the script to run and press Enter (Ctrl-C to quit): "
- op_script_id = Integer(gets.chomp) rescue -1
- if op_script_id > 0 && op_script_id < op_script_names.length
- puts "Script choice: #{op_script_id}. #{op_script_names[ op_script_id ]}"
- break
+ end
+ #
+ # If we reach here it means we didnt find the script in the operationals
+ #
+ if @script_to_run == nil
+ # Search outside common op scripts
+ search_for_script_in_sts(script, st)
+ if @script_to_run.nil?
+ if @interactive
+ puts "ERROR: Sorry, didnt find that ( "+script+" ), provide an URI instead"
+ puts "I searched in:"
+ st.each { |s|
+ puts " * "+s[1]['name']+" [Rev"+s[1]['version'].to_s+"]"
+ }
+ if not @ignore_errors
+ exit 1
+ end
else
- puts "#{op_script_id < 0 ? 'Invalid input' : 'Input out of range'}."
+ Log.error "["+self.job_uuid+"] Sorry, didnt find the script: ( "+script+" )!"
+ return nil
end
+ else
+ if self.job_uuid.nil?
+ self.job_uuid = ""
+ end
+ Log.warn "["+self.job_uuid+"]\"#{@script_to_run.params['right_script']['name']}\" is not a common operational script!"
+ return @script_to_run
end
- # Provide the href as the input for the block that will do the lookup
- script = op_script_hrefs[ op_script_id ]
end
end
+ end
- if script
- if script =~ /^http/ or script =~ /^\d+$/
- if script =~ /^\d+$/
- 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
- script = url_prefix + "/right_scripts/#{script}"
+ def search_for_script_in_sts(script, st)
+ # Loop and look inside every st
+ st.each do |s|
+ Log.debug "Making API 1.5 call: client.resource(#{s[1]['href']})"
+ temp=Connection.client.resource(s[1]['href'])
+ temp.runnable_bindings.index.each do |x|
+ # Look for first match
+ if x.raw['right_script']['name'].downcase.include?(script.downcase)
+ Log.debug "Found requested righscript: #{script}"
+ # Provide the name + href
+ s = Executable.new
+ s.params['right_script']['href'] = x.raw['links'].find{|i| i['rel'] == 'right_script'}['href']
+ s.params['right_script']['name'] = x.raw['right_script']['name']
+ @script_to_run = s
end
- script_URI = script
- Log.debug "Looking for script href \"#{script_URI}\""
- puts
- # First look up the script URI in the template.
- # It *will* be found if we came here from the 'if script = ""' block
- script = st.executables.detect { |ex| ex.href == script }
- if not script
- script_obj = ::RightScript.find(script_URI)
- script_data = {}
- script_data[ 'name' ] = script_obj.params['name']
- script = ::RightScript.new({ :href => script_URI, :right_script => script_data })
- end
+ end
+ end
+ # If we hit here, we never found the desired script
+ return
+ end
+
+ #
+ # Presents the user with a list of scripts contained in @op_scripts
+ # and Returns an integer indicating the selection
+ #
+ #
+ def list_and_select_op_script
+ puts "List of available operational scripts:"
+ puts "------------------------------------------------------------"
+ for i in 0..@op_scripts.length - 1
+ puts " %3d. #{@op_scripts[i][0]}" % i
+ end
+ puts "------------------------------------------------------------"
+ while true
+ printf "Type the number of the script to run and press Enter (Ctrl-C to quit): "
+ script_id = Integer(gets.chomp) rescue -1
+ if script_id >= 0 && script_id < @op_scripts.length
+ puts "Script choice: #{script_id}. #{@op_scripts[ script_id ][0]}"
+ break
else
- Log.debug "Looking for script \"#{script}\""
- script = st.executables.detect { |ex| ex.name =~ /#{script}/ }
+ puts "#{script_id < 0 ? 'Invalid input' : 'Input out of range'}."
end
+ end
- if script != nil and script['right_script'] != nil
- puts "RightScript: \"#{script['right_script']['name']}\"" if @interactive
- else
- puts "No matching RightScript found!"
- raise "No matching RightScript found!"
- end
+ return script_id
+ end
- executable = script
+ #
+ # Takes the number of st's to search in,
+ # and reduces @op_scripts to only those who are
+ # repeated enough times.
+ #
+ def reduce_to_common_scripts(number_of_st)
+ counts = Hash.new 0
+ @op_scripts.each { |s| counts[s[0]] +=1 }
+
+ b = @op_scripts.inject({}) do |res, row|
+ res[row[0]] ||= []
+ res[row[0]] << row[1]
+ res
+ end
+
+ b.inject([]) do |res, (key, values)|
+ res << [key, values.first] if values.size >= number_of_st
+ @op_scripts = res
+ end
+ end
+
+ #
+ # Returns all matching operational scripts in the st list passed
+ #
+ def extract_operational_scripts(st)
+ op_scripts = []
+ size = st.size
+ st.each do |s|
+ # Example of s structure
+ # ["/api/server_templates/351930003",
+ # {"id"=>351930003,
+ # "name"=>"RightScale Right_Site - 2015q1",
+ # "kind"=>"cm#server_template",
+ # "version"=>5,
+ # "href"=>"/api/server_templates/351930003"} ]
+ Log.debug "Making API 1.5 call: client.resource"
+ temp=Connection.client.resource(s[1]['href'])
+ temp.runnable_bindings.index.each do |x|
+ # only add the operational ones
+ if x.sequence == "operational"
+ name = x.raw['right_script']['name']
+ op_scripts.push([name, x])
+ end
+ end
end
- return(executable)
+ #We now only have operational runnable_bindings under the script_objects array
+ if op_scripts.length < 1
+ raise "ERROR: No operational scripts found on the server(s). "
+ st.each {|s|
+ puts " (Search performed on server template '#{s[1]['name']}')"
+ }
+ end
+ return op_scripts
end
#
# Load up the queue with work
#
- # FIXME this needs to be refactored
- #
- def generate_jobs(queue_servers, queue_arrays, queue_template, queue_executable)
+ def generate_jobs(queue_servers, queue_template, queue_executable)
counter = 0
tasks = []
Log.debug "Loading queue..."
-
#
# Configure group
#
if not ChimpQueue[@group]
ChimpQueue.instance.create_group(@group, @group_type, @group_concurrency)
end
#
- # Process ServerArray selection
- #
- Log.debug("processing queue selection")
- if not queue_arrays.empty?
- queue_arrays.each do |array|
- instances = filter_out_non_operational_servers(array.instances)
-
- if not instances
- Log.error("no instances in array!")
- break
- end
-
- instances.each do |array_instance|
- #
- # Handle limiting options
- #
- counter += 1
- next if @limit_start.to_i > 0 and counter < @limit_start.to_i
- break if @limit_end.to_i > 0 and counter > @limit_end.to_i
- a = ExecArray.new(
- :array => array,
- :server => array_instance,
- :exec => queue_executable,
- :inputs => @inputs,
- :template => queue_template,
- :timeout => @timeout,
- :verbose => @@verbose,
- :quiet => @@quiet
- )
- a.dry_run = @dry_run
- ChimpQueue.instance.push(@group, a)
- end
- end
- end
-
- #
# Process Server selection
#
Log.debug("Processing server selection")
- queue_servers.sort! { |a,b| a['nickname'] <=> b['nickname'] }
+ queue_servers.sort! { |a,b| a['name'] <=> b['name'] }
queue_servers.each do |server|
-
#
# Handle limiting options
#
counter += 1
next if @limit_start.to_i > 0 and counter < @limit_start.to_i
break if @limit_end.to_i > 0 and counter > @limit_end.to_i
#
# Construct the Server object
#
- s = ::Server.new
- s.href = server['href']
- s.current_instance_href = server['current_instance_href']
- s.name = server['nickname'] || server['name']
- s.nickname = s.name
- s.ip_address = server['ip-address'] || server['ip_address']
+ s = Server.new
+
+ s.params['href'] = server['href']
+
+ s.params['current_instance_href'] = s.params['href']
+ s.params['current-instance-href'] = s.params['href']
+
+ s.params['name'] = server['name']
+ s.params['nickname'] = s.params['name']
+
+ s.params['ip_address'] = server['public_ip_addresses'].first
+ s.params['ip-address'] = s.params['ip_address']
+
+ s.params['private-ip-address'] = server['private_ip_addresses'].first
+ s.params['private_ip_address'] = s.params['private-ip-address']
+
+ s.params['resource_uid'] = server['resource_uid']
+ s.params['resource-uid'] = s.params['resource_uid']
+
+ s.params['instance-type'] = server['links']['instance_type']['name']
+ s.params['instance_type'] = s.params['instance-type']
+ s.params['ec2_instance_type'] = s.params['instance-type']
+ s.params['ec2-instance-type'] = s.params['instance-type']
+
+ s.params['dns-name'] = server['public_dns_names'].first
+ s.params['dns_name'] = s.params['dns-name']
+
+ s.params['locked'] = server['locked']
+ s.params['state'] = server['state']
+ s.params['datacenter'] = server['links']['datacenter']['name']
+
+ # This will be useful for later on when we need to run scripts
+ Log.debug "Making API 1.5 call: client.resource"
+ s.object = Connection.client.resource(server['href'])
+
e = nil
+ # If @script has been passed
if queue_executable
e = ExecRightScript.new(
:server => s,
:exec => queue_executable,
+ :job_uuid => @job_uuid,
:inputs => @inputs,
:timeout => @timeout,
:verbose => @@verbose,
:quiet => @@quiet
)
@@ -708,20 +875,12 @@
:ssh_user => @ssh_user,
:exec => @ssh,
:verbose => @@verbose,
:quiet => @@quiet
)
- elsif queue_template and not clone
- e = ExecSetTemplate.new(
- :server => s,
- :template => queue_template,
- :verbose => @@verbose,
- :quiet => @@quiet
- )
elsif @report
if s.href
- s.href = s.href.sub("/current","")
e = ExecReport.new(:server => s, :verbose => @@verbose, :quiet => @@quiet)
e.fields = @report
end
elsif @set_tags.size > 0
e = ExecSetTags.new(:server => s, :verbose => @@verbose, :quiet => @@quiet)
@@ -733,13 +892,11 @@
e.quiet = @@quiet
e.status = Executor::STATUS_HOLDING if @hold
tasks.push(e)
end
-
end
-
return(tasks)
end
def add_to_queue(a)
a.each { |task| ChimpQueue.instance.push(@group, task) }
@@ -845,20 +1002,10 @@
def get_failures
return get_results(@group)
end
#
- # Filter out non-operational servers
- # Then add operational servers to the list of objects to display
- #
- def filter_out_non_operational_servers(servers)
- Log.debug "Filtering out non-operational servers..."
- servers.reject! { |s| s == nil || s['state'] != "operational" }
- return(servers)
- end
-
- #
# Do work: either by submitting to chimpd
# or running it ourselves.
#
def do_work
done = false
@@ -880,25 +1027,64 @@
puts "chimp run complete"
end
#
+ # Allow the set/retrieval of job_uuid from outside
+ #
+ def self.get_job_uuid
+ @job_uuid
+ end
+
+ def self.set_job_uuid(value)
+ @job_uuid = value
+ end
+
+ #
# Completely process a non-interactive chimp object command
+ # This is used by chimpd, when processing a task.
#
def process
- get_array_info
- get_server_info
- get_template_info
- get_executable_info
- return generate_jobs(@servers, @arrays, @server_template, @executable)
+ Chimp.set_failure(false)
+ Chimp.set_job_uuid(self.job_uuid)
+
+ Log.debug "[#{Chimp.get_job_uuid}] Processing task"
+
+ get_array_info unless Chimp.failure
+ get_server_info unless Chimp.failure
+ get_template_info unless Chimp.failure
+ get_executable_info unless Chimp.failure
+
+ if Chimp.failure
+ Log.error "##################################################"
+ Log.error "["+self.job_uuid+"] API CALL FAILED FOR:"
+ Log.error "["+self.job_uuid+"] chimp #{@cli_args} "
+ Log.error "["+self.job_uuid+"] Run manually!"
+ Log.error "##################################################"
+ return []
+ else
+ if @servers.first.nil? or @executable.nil?
+ Log.warn "["+self.job_uuid+"] Nothing to do for \"chimp #{@cli_args}\"."
+ return []
+ else
+ return generate_jobs(@servers, @server_template, @executable)
+ end
+ end
end
#
- # Always returns 0. Used for chimpd compatibility.
+ # Asks for confirmation before continuing
#
- def job_id
- return 0
+ def ask_confirmation(prompt = 'Continue?', default = false)
+ a = ''
+ s = default ? '[Y/n]' : '[y/N]'
+ d = default ? 'y' : 'n'
+ until %w[y n].include? a
+ a = ask("#{prompt} #{s} ") { |q| q.limit = 1; q.case = :downcase }
+ a = d if a.length == 0
+ end
+ a == 'y'
end
#
# Connect to chimpd and wait for the work queue to empty, and
# prompt the user if there are any errors.
@@ -965,17 +1151,11 @@
@@quiet = q
STDOUT.sync = true
STDERR.sync = true
- if @@verbose == true
- Log.threshold = Logger::DEBUG
- elsif @@quiet == true
- Log.threshold = Logger::WARN
- else
- Log.threshold = Logger::INFO
- end
+ Log.threshold= Logger::DEBUG if @@verbose
end
def self.verbose?
return @@verbose
end
@@ -985,12 +1165,24 @@
#
def job_id
return 0
end
+ def self.get_job_uuid
+ @job_uuid
+ end
+
+ def self.failure
+ return @failure
+ end
+
+ def self.set_failure(status)
+ @failure = status
+ end
+
####################################################
- private
+ #private
####################################################
#
# Allow the user to verify the list of servers that an
# operation will be run against.
@@ -1103,29 +1295,22 @@
else
if not @@quiet
puts "Script OK. All the servers share the same template and the script is included in it."
end
end
- puts
end
#
# Generate a human readable list of objects
#
def make_human_readable_list_of_objects
list_of_objects = []
- if @servers
- list_of_objects += @servers.map { |s| s['nickname'] }
+ if @servers and not @servers.first.nil?
+ list_of_objects += @servers.map { |s| s['name'] }
end
- if @arrays
- @arrays.each do |a|
- i = filter_out_non_operational_servers(a.instances)
- list_of_objects += i.map { |j| j['nickname'] }
- end
- end
return(list_of_objects)
end
#
# Print out help information
@@ -1166,11 +1351,11 @@
puts " --retry=<n> number of times to retry. Default: 0"
puts " --timeout=<seconds> set the timeout to wait for a RightScript to complete"
puts " --progress toggle progress indicator"
puts " --noprompt don't prompt with list of objects to run against"
puts " --noverify disable interactive verification of errors"
- puts " --verbose display rest_connection log messages"
+ puts " --verbose be more verbose"
puts " --dont-check-templates don't check for script even if servers have diff. templates"
puts " --quiet suppress non-essential output"
puts " --version display version and exit"
puts
puts "chimpd options:"
@@ -1178,15 +1363,11 @@
puts " --chimpd-wait-until-done wait until all chimpd jobs are done"
puts " --hold create a job in chimpd without executing until requested"
puts
puts "Misc Notes:"
puts " * If you leave the name of a --script or --ssh command blank, chimp will prompt you"
- puts " * You cannot operate on array instances by selecting them with tag queries"
- puts " * URIs must be API URIs in the format https://my.rightscale.com/api/acct/<acct>/ec2_server_templates/<id>"
- puts " * The following reporting keywords can be used: nickname, ip-address, state, server_type, href"
- puts " server_template_href, deployment_href, created_at, updated_at"
- puts
+ puts " * URIs must be API URIs in the format https://us-3.rightscale.com/acct/<acct>/right_scripts/<script_id>"
+ puts " * The following reporting keywords can be used: ip-address,name,href,private-ip-address,resource_uid,"
+ puts " * ec2-instance-type,datacenter,dns-name,locked,tag=foo"
end
-
end
end
-