lib/rudy/utils.rb in rudy-0.4.0 vs lib/rudy/utils.rb in rudy-0.6.0

- old
+ new

@@ -13,13 +13,18 @@ include Socket::Constants # Return the external IP address (the one seen by the internet) def external_ip_address ip = nil - %w{solutious.com/ip myip.dk/ whatismyip.com }.each do |sponge| # w/ backup - break unless ip.nil? - ip = (open("http://#{sponge}") { |f| /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(f.read) }).to_s rescue nil + begin + %w{solutious.com/ip/ myip.dk/ whatismyip.com }.each do |sponge| # w/ backup + ipstr = Net::HTTP.get(URI.parse("http://#{sponge}")) || '' + ip = /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(ipstr).to_s + break if ip && !ip.empty? + end + rescue SocketError, Errno::ETIMEDOUT + STDERR.puts "Connection Error. Check your internets!" end ip += "/32" if ip ip end @@ -35,21 +40,171 @@ ensure Socket.do_not_reverse_lookup = orig end # Generates a canonical tag name in the form: - # rudy-2009-12-31-r1 + # rudy-2009-12-31-01 # where r1 refers to the revision number that day def generate_tag(revision=1) n = DateTime.now y = n.year.to_s.rjust(4, "20") m = n.month.to_s.rjust(2, "0") d = n.mday.to_s.rjust(2, "0") "rudy-%4s-%2s-%2s-r%s" % [y, m, d, revision.to_s.rjust(2, "0")] end + + + # Determine if we're running directly on EC2 or + # "some other machine". We do this by checking if + # the file /etc/ec2/instance-id exists. This + # file is written by /etc/init.d/rudy-ec2-startup. + # NOTE: Is there a way to know definitively that this is EC2? + # We could make a request to the metadata IP addresses. + def Rudy.in_situ? + File.exists?('/etc/ec2/instance-id') + end + + + # Wait for something to happen. + # * +duration+ seconds to wait between tries (default: 2). + # * +max+ maximum time to wait (default: 120). Throws an exception when exceeded. + # * +logger+ IO object to print +dot+ to. + # * +msg+ the message to print on success + # * +bells+ number of terminal bells to ring. Set to nil or false to keep the waiter silent + # + # The +check+ block must return false while waiting. Once it returns true + # the waiter will return true too. + def waiter(duration=2, max=120, logger=STDOUT, msg=nil, bells=0, &check) + # TODO: Move to Drydock. [ed-why?] + raise "The waiter needs a block!" unless check + duration = 1 if duration < 1 + max = duration*2 if max < duration + dot = '.' + begin + Timeout::timeout(max) do + while !check.call + sleep duration + logger.print dot if logger.respond_to?(:print) + logger.flush if logger.respond_to?(:flush) + end + end + rescue Timeout::Error => ex + retry if Annoy.pose_question(" Keep waiting?\a ", /yes|y|ya|sure|you bet!/i, logger) + return false + end + logger.puts msg if msg + Rudy::Utils.bell(bells, logger) + true + end + + # Make a terminal bell chime + def bell(chimes=1, logger=nil) + chimes ||= 0 + return unless logger + chimed = chimes.to_i + logger.print "\a"*chimes if chimes > 0 && logger + true # be like Rudy.bug() + end + + # Have you seen that episode of The Cosby Show where Dizzy Gillespie... ah nevermind. + def bug(bugid, logger=STDERR) + logger.puts "You have found a bug! If you want, you can email".color(:red) + logger.puts 'rudy@solutious.com'.color(:red).bright << " about it. It's bug ##{bugid}.".color(:red) + logger.puts "Continuing...".color(:red) + true # so we can string it together like: bug('1') && next if ... + end + + # Is the given string +str+ an ID of type +identifier+? + # * +identifier+ is expected to be a key from ID_MAP + # * +str+ is a string you're investigating + def is_id?(identifier, str) + return false unless identifier && str && known_type?(identifier) + identifier &&= identifier.to_sym + str &&= str.to_s.strip + str.split('-').first == Rudy::ID_MAP[identifier].to_s + end + + # Returns the object type associated to +str+ or nil if unknown. + # * +str+ is a string you're investigating + def id_type(str) + return false unless str + str &&= str.to_s.strip + (Rudy::ID_MAP.detect { |n,v| v == str.split('-').first } || []).first + end + + # Is the given +key+ a known type of object? + def known_type?(key) + return false unless key + key &&= key.to_s.to_sym + Rudy::ID_MAP.has_key?(key) + end + + # Returns the string identifier associated to this +key+ + def identifier(key) + key &&= key.to_sym + return unless Rudy::ID_MAP.has_key?(key) + Rudy::ID_MAP[key] + end + + # Return a string ID without the identifier. i.e. key-stage-app-root => stage-app-root + def noid(str) + el = str.split('-') + el.shift + el.join('-') + end + + + # +msg+ The message to return as a banner + # +size+ One of: :normal (default), :huge + # +colour+ a valid + # Returns a string with styling applying + def banner(msg, size = :normal, colour = :black) + return unless msg + banners = { + :huge => Rudy::Utils.without_indent(%Q( + ======================================================= + ======================================================= + !!!!!!!!! %s !!!!!!!!! + ======================================================= + =======================================================)), + :normal => %Q(============ %s ============) + } + size = :normal unless banners.has_key?(size) + colour = :black unless Console.valid_colour?(colour) + size, colour = size.to_sym, colour.to_sym + sprintf(banners[size], msg).colour(colour).bgcolour(:white).bright + end + + + # <tt>require</tt> a glob of files. + # * +path+ is a list of path elements which is sent to File.join + # and then to Dir.glob. The list of files found are sent to require. + # Nothing is returned but LoadError exceptions are caught. The message + # is printed to STDERR and the program exits with 7. + def require_glob(*path) + begin + # TODO: Use autoload + Dir.glob(File.join(*path.flatten)).each do |path| + require path + end + rescue LoadError => ex + puts "Error: #{ex.message}" + exit 7 + end + end + + # Checks whether something is listening to a socket. + # * +host+ A hostname + # * +port+ The port to check + # * +wait+ The number of seconds to wait for before timing out. + # + # Returns true if +host+ allows a socket connection on +port+. + # Returns false if one of the following exceptions is raised: + # Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error + # def service_available?(host, port, wait=3) begin status = Timeout::timeout(wait) do socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) sockaddr = Socket.pack_sockaddr_in( port, host ) @@ -58,7 +213,170 @@ true rescue Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error => ex false end end + + + + # Capture STDOUT or STDERR to prevent it from being printed. + # + # capture(:stdout) do + # ... + # end + # + def capture(stream) + #raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr + begin + stream = stream.to_s + eval "$#{stream} = StringIO.new" + yield + result = eval("$#{stream}").read + ensure + eval("$#{stream} = #{stream.upcase}") + end + + result + end + + # A basic file writer + def write_to_file(filename, content, mode, chmod=600) + mode = (mode == :append) ? 'a' : 'w' + f = File.open(filename,mode) + f.puts content + f.close + return unless Rudy.sysinfo.os == :unix + raise "Provided chmod is not a Fixnum (#{chmod})" unless chmod.is_a?(Fixnum) + File.chmod(chmod, filename) + end + + # + # Generates a string of random alphanumeric characters. + # * +len+ is the length, an Integer. Default: 8 + # * +safe+ in safe-mode, ambiguous characters are removed (default: true): + # i l o 1 0 + def strand( len=8, safe=true ) + chars = ("a".."z").to_a + ("0".."9").to_a + chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe + str = "" + 1.upto(len) { |i| str << chars[rand(chars.size-1)] } + str + end + + # Returns +str+ with the leading indentation removed. + # Stolen from http://github.com/mynyml/unindent/ because it was better. + def without_indent(str) + indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min + str.gsub(/^[[:blank:]]{#{indent}}/, '') + end + + + + + ######### Everything below here is TO BE REMOVED. + + # + # + # Run a shell command (TO BE REMOVED) + def sh(command, chdir=false, verbose=false) + prevdir = Dir.pwd + Dir.chdir chdir if chdir + puts command if verbose + system(command) + Dir.chdir prevdir if chdir + end + + # + # Run an SSH command (TO BE REMOVED) + def ssh_command(host, keypair, user, command=false, printonly=false, verbose=false) + #puts "CONNECTING TO #{host}..." + cmd = "ssh -i #{keypair} #{user}@#{host} " + cmd += " '#{command}'" if command + puts cmd if verbose + return cmd if printonly + # backticks returns STDOUT + # exec replaces current process (it's just like running ssh) + # -- UPDATE -- Some problem with exec. "Operation not supported" + # using system (http://www.mail-archive.com/mongrel-users@rubyforge.org/msg02018.html) + (command) ? `#{cmd}` : Kernel.system(cmd) + end + + + # (TO BE REMOVED) + # TODO: This is old and insecure. + def scp_command(host, keypair, user, paths, to_path, to_local=false, verbose=false, printonly=false) + + paths = [paths] unless paths.is_a?(Array) + from_paths = "" + if to_local + paths.each do |path| + from_paths << "#{user}@#{host}:#{path} " + end + #puts "Copying FROM remote TO this machine", $/ + + else + to_path = "#{user}@#{host}:#{to_path}" + from_paths = paths.join(' ') + #puts "Copying FROM this machine TO remote", $/ + end + + + cmd = "scp -r " + cmd << "-i #{keypair}" if keypair + cmd << " #{from_paths} #{to_path}" + + puts cmd if verbose + printonly ? (puts cmd) : system(cmd) + end + + end +end + +# = RSSReader +# +# A rudimentary way to read an RSS feed as a hash. +# Adapted from: http://snippets.dzone.com/posts/show/68 +# +module Rudy::Utils::RSSReader + extend self + require 'net/http' + require 'rexml/document' + + # Returns a feed as a hash. + # * +uri+ to RSS feed + def run(uri) + begin + xmlstr = Net::HTTP.get(URI.parse(uri)) + rescue SocketError, Errno::ETIMEDOUT + STDERR.puts "Connection Error. Check your internets!" + end + + xml = REXML::Document.new xmlstr + + data = { :items => [] } + xml.elements.each '//channel' do |item| + item.elements.each do |e| + n = e.name.downcase.gsub(/^dc:(\w)/,"\1").to_sym + next if n == :item + data[n] = e.text + end + end + + #data = { + # :title => xml.root.elements['channel/title'].text, + # :link => xml.root.elements['channel/link'].text, + # :updated => xml.root.elements['channel/lastBuildDate'].text, + # :uri => uri, + # :items => [] + #} + #data[:updated] &&= DateTime.parse(data[:updated]) + + xml.elements.each '//item' do |item| + new_items = {} and item.elements.each do |e| + n = e.name.downcase.gsub(/^dc:(\w)/,"\1").to_sym + new_items[n] = e.text + end + data[:items] << new_items + end + data end end \ No newline at end of file