#require 'drydock/mixins'
require 'socket'
require 'open-uri'
require 'date'
require 'timeout'
module Rudy
# A motley collection of methods that Rudy loves to call!
module Utils
extend self
include Socket::Constants
# Return the external IP address (the one seen by the internet)
def external_ip_address
ip = nil
begin
%w{solutious.heroku.com/ip}.each do |sponge|
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 => ex
STDERR.puts "Connection Error. Check your internets!"
end
ip += "/32" if ip
ip
end
# Return the local IP address which receives external traffic
# from: http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
# NOTE: This does not open a connection to the IP address.
def internal_ip_address
# turn off reverse DNS resolution temporarily
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
ip = UDPSocket.open {|s| s.connect('75.101.137.7', 1); s.addr.last } # Solutious IP
ip += "/24" if ip
ip
ensure
Socket.do_not_reverse_lookup = orig
end
# Generates a canonical tag name in the form:
# 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 before executing the block.
# * +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
if msg && logger
logger.print msg
logger.flush
end
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
if msg && logger
logger.puts " done"
logger.flush
end
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 Drydock::Console.valid_colour?(colour)
size, colour = size.to_sym, colour.to_sym
sprintf(banners[size], msg).bright.att(:reverse)
end
# require 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 )
socket.connect( sockaddr )
end
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.
# (TO BE REMOVED)
# TODO: This is old and nasty.
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