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 -