rudy 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +54 -30
- data/README.rdoc +100 -12
- data/Rakefile +103 -8
- data/Rudyfile +119 -0
- data/bin/ird +175 -0
- data/bin/rudy +259 -156
- data/bin/rudy-ec2 +228 -95
- data/bin/rudy-s3 +76 -0
- data/bin/rudy-sdb +67 -0
- data/lib/annoy.rb +270 -0
- data/lib/console.rb +30 -9
- data/lib/escape.rb +305 -0
- data/lib/rudy.rb +151 -182
- data/lib/rudy/aws.rb +56 -49
- data/lib/rudy/aws/ec2.rb +47 -292
- data/lib/rudy/aws/ec2/address.rb +157 -0
- data/lib/rudy/aws/ec2/group.rb +301 -0
- data/lib/rudy/aws/ec2/image.rb +168 -0
- data/lib/rudy/aws/ec2/instance.rb +434 -0
- data/lib/rudy/aws/ec2/keypair.rb +104 -0
- data/lib/rudy/aws/ec2/snapshot.rb +98 -0
- data/lib/rudy/aws/ec2/volume.rb +230 -0
- data/lib/rudy/aws/ec2/zone.rb +77 -0
- data/lib/rudy/aws/s3.rb +54 -0
- data/lib/rudy/aws/sdb.rb +298 -0
- data/lib/rudy/aws/sdb/error.rb +46 -0
- data/lib/rudy/{metadata/backup.rb → backup.rb} +26 -51
- data/lib/rudy/cli.rb +157 -0
- data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
- data/lib/rudy/cli/aws/ec2/candy.rb +208 -0
- data/lib/rudy/cli/aws/ec2/groups.rb +121 -0
- data/lib/rudy/cli/aws/ec2/images.rb +196 -0
- data/lib/rudy/cli/aws/ec2/instances.rb +194 -0
- data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
- data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
- data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
- data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
- data/lib/rudy/cli/aws/s3/buckets.rb +50 -0
- data/lib/rudy/cli/aws/s3/store.rb +22 -0
- data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
- data/lib/rudy/cli/candy.rb +8 -0
- data/lib/rudy/{command → cli}/config.rb +34 -24
- data/lib/rudy/cli/disks.rb +35 -0
- data/lib/rudy/cli/machines.rb +94 -0
- data/lib/rudy/cli/routines.rb +57 -0
- data/lib/rudy/config.rb +77 -72
- data/lib/rudy/config/objects.rb +29 -0
- data/lib/rudy/disks.rb +248 -0
- data/lib/rudy/global.rb +121 -0
- data/lib/rudy/huxtable.rb +340 -0
- data/lib/rudy/machines.rb +245 -0
- data/lib/rudy/metadata.rb +123 -13
- data/lib/rudy/routines.rb +47 -0
- data/lib/rudy/routines/helpers/diskhelper.rb +101 -0
- data/lib/rudy/routines/helpers/scripthelper.rb +91 -0
- data/lib/rudy/routines/release.rb +34 -0
- data/lib/rudy/routines/shutdown.rb +57 -0
- data/lib/rudy/routines/startup.rb +58 -0
- data/lib/rudy/scm/svn.rb +1 -1
- data/lib/rudy/utils.rb +322 -4
- data/lib/storable.rb +26 -17
- data/lib/sysinfo.rb +274 -0
- data/lib/tryouts.rb +6 -13
- data/rudy.gemspec +128 -42
- data/support/randomize-root-password +45 -0
- data/support/rudy-ec2-startup +9 -9
- data/support/update-ec2-ami-tools +20 -0
- data/test/05_config/00_setup_test.rb +20 -0
- data/test/05_config/30_machines_test.rb +69 -0
- data/test/20_sdb/00_setup_test.rb +16 -0
- data/test/20_sdb/10_domains_test.rb +115 -0
- data/test/25_ec2/00_setup_test.rb +29 -0
- data/test/25_ec2/10_keypairs_test.rb +41 -0
- data/test/25_ec2/20_groups_test.rb +131 -0
- data/test/25_ec2/30_addresses_test.rb +38 -0
- data/test/25_ec2/40_volumes_test.rb +49 -0
- data/test/25_ec2/50_snapshots_test.rb +74 -0
- data/test/26_ec2_instances/00_setup_test.rb +28 -0
- data/test/26_ec2_instances/10_instances_test.rb +83 -0
- data/test/26_ec2_instances/50_images_test.rb +13 -0
- data/test/30_sdb_metadata/00_setup_test.rb +21 -0
- data/test/30_sdb_metadata/10_disks_test.rb +109 -0
- data/test/30_sdb_metadata/20_backups_test.rb +102 -0
- data/test/coverage.txt +51 -0
- data/test/helper.rb +36 -0
- data/vendor/highline-1.5.1/CHANGELOG +222 -0
- data/vendor/highline-1.5.1/INSTALL +35 -0
- data/vendor/highline-1.5.1/LICENSE +7 -0
- data/vendor/highline-1.5.1/README +63 -0
- data/vendor/highline-1.5.1/Rakefile +82 -0
- data/vendor/highline-1.5.1/TODO +6 -0
- data/vendor/highline-1.5.1/examples/ansi_colors.rb +38 -0
- data/vendor/highline-1.5.1/examples/asking_for_arrays.rb +18 -0
- data/vendor/highline-1.5.1/examples/basic_usage.rb +75 -0
- data/vendor/highline-1.5.1/examples/color_scheme.rb +32 -0
- data/vendor/highline-1.5.1/examples/limit.rb +12 -0
- data/vendor/highline-1.5.1/examples/menus.rb +65 -0
- data/vendor/highline-1.5.1/examples/overwrite.rb +19 -0
- data/vendor/highline-1.5.1/examples/page_and_wrap.rb +322 -0
- data/vendor/highline-1.5.1/examples/password.rb +7 -0
- data/vendor/highline-1.5.1/examples/trapping_eof.rb +22 -0
- data/vendor/highline-1.5.1/examples/using_readline.rb +17 -0
- data/vendor/highline-1.5.1/lib/highline.rb +758 -0
- data/vendor/highline-1.5.1/lib/highline/color_scheme.rb +120 -0
- data/vendor/highline-1.5.1/lib/highline/compatibility.rb +17 -0
- data/vendor/highline-1.5.1/lib/highline/import.rb +43 -0
- data/vendor/highline-1.5.1/lib/highline/menu.rb +395 -0
- data/vendor/highline-1.5.1/lib/highline/question.rb +463 -0
- data/vendor/highline-1.5.1/lib/highline/system_extensions.rb +193 -0
- data/vendor/highline-1.5.1/setup.rb +1360 -0
- data/vendor/highline-1.5.1/test/tc_color_scheme.rb +56 -0
- data/vendor/highline-1.5.1/test/tc_highline.rb +823 -0
- data/vendor/highline-1.5.1/test/tc_import.rb +54 -0
- data/vendor/highline-1.5.1/test/tc_menu.rb +429 -0
- data/vendor/highline-1.5.1/test/ts_all.rb +15 -0
- metadata +141 -38
- data/lib/aws_sdb.rb +0 -3
- data/lib/aws_sdb/error.rb +0 -42
- data/lib/aws_sdb/service.rb +0 -215
- data/lib/rudy/aws/simpledb.rb +0 -53
- data/lib/rudy/command/addresses.rb +0 -46
- data/lib/rudy/command/backups.rb +0 -175
- data/lib/rudy/command/base.rb +0 -841
- data/lib/rudy/command/deploy.rb +0 -12
- data/lib/rudy/command/disks.rb +0 -213
- data/lib/rudy/command/environment.rb +0 -73
- data/lib/rudy/command/groups.rb +0 -61
- data/lib/rudy/command/images.rb +0 -91
- data/lib/rudy/command/instances.rb +0 -85
- data/lib/rudy/command/machines.rb +0 -161
- data/lib/rudy/command/metadata.rb +0 -41
- data/lib/rudy/command/release.rb +0 -174
- data/lib/rudy/command/volumes.rb +0 -66
- data/lib/rudy/metadata/disk.rb +0 -138
- data/tryouts/console_tryout.rb +0 -91
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
|
4
|
+
module Rudy; module Routines;
|
5
|
+
|
6
|
+
module ScriptHelper
|
7
|
+
extend self
|
8
|
+
|
9
|
+
@@script_types = [:after, :before, :after_local, :before_local]
|
10
|
+
@@script_config_file = "rudy-config.yml"
|
11
|
+
|
12
|
+
def before_local(routine, sconf, rbox)
|
13
|
+
execute_command(:before_local, routine, sconf, 'localhost', rbox)
|
14
|
+
end
|
15
|
+
def after_local(routine, sconf, rbox)
|
16
|
+
execute_command(:after_local, routine, sconf, 'localhost', rbox)
|
17
|
+
end
|
18
|
+
|
19
|
+
def before(routine, sconf, machine, rbox)
|
20
|
+
raise "ScriptHelper: Not a Rudy::Machine" unless machine.is_a?(Rudy::Machine)
|
21
|
+
execute_command(:before, routine, sconf, machine.name, rbox)
|
22
|
+
end
|
23
|
+
def after(routine, sconf, machine, rbox)
|
24
|
+
raise "ScriptHelper: Not a Rudy::Machine" unless machine.is_a?(Rudy::Machine)
|
25
|
+
execute_command(:after, routine, sconf, machine.name, rbox)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# * +timing+ is one of: after, before
|
32
|
+
# * +routine+ a single routine hash (startup, shutdown, etc...)
|
33
|
+
# * +sconf+ is a config hash from machines config (ignored if nil)
|
34
|
+
# * +hostname+ machine hostname that we're working on
|
35
|
+
# * +rbox+ a Rye::Box instance for the machine we're working on
|
36
|
+
def execute_command(timing, routine, sconf, hostname, rbox)
|
37
|
+
raise "ScriptHelper: Not a Rye::Box" unless rbox.is_a?(Rye::Box)
|
38
|
+
raise "ScriptHelper: #{timing}?" unless @@script_types.member?(timing)
|
39
|
+
|
40
|
+
if sconf && !sconf.empty?
|
41
|
+
tf = Tempfile.new(@@script_config_file)
|
42
|
+
Rudy::Utils.write_to_file(tf.path, sconf.to_hash.to_yaml, 'w')
|
43
|
+
end
|
44
|
+
|
45
|
+
# We need to explicitly add the rm command for rbox so we
|
46
|
+
# can delete the script config file when we're done. This
|
47
|
+
# add the method on for the instance of rbox we are using.
|
48
|
+
def rbox.rm(*args); cmd('rm', args); end
|
49
|
+
|
50
|
+
if routine.is_a?(Caesars::Hash) && routine.has_key?(timing)
|
51
|
+
puts "Connecting to #{hostname}"
|
52
|
+
rbox.connect
|
53
|
+
original_user = rbox.user
|
54
|
+
scripts = [routine[timing]].flatten
|
55
|
+
scripts.each do |script|
|
56
|
+
user, command, *args = script.to_a.flatten.compact
|
57
|
+
rbox.switch_user user # does nothing if it's the same user
|
58
|
+
puts "Creating #{@@script_config_file}"
|
59
|
+
rbox.safe = false
|
60
|
+
puts rbox.echo("'#{sconf.to_hash.to_yaml}' > #{@@script_config_file}")
|
61
|
+
rbox.safe = true
|
62
|
+
rbox.chmod(600, @@script_config_file)
|
63
|
+
puts %Q{Running (as #{user}): #{rbox.preview_command(command, args)}}
|
64
|
+
|
65
|
+
begin
|
66
|
+
ret = rbox.send(command, args)
|
67
|
+
if ret.exit_code > 0
|
68
|
+
puts " Exit code: #{ret.exit_code}".color(:red)
|
69
|
+
puts " STDERR: #{ret.stderr.join("#{$/} ")}".color(:red)
|
70
|
+
puts " STDOUT: #{ret.stdout.join("#{$/} ")}".color(:red)
|
71
|
+
else
|
72
|
+
puts ' ' << ret.stdout.join("#{$/} ")
|
73
|
+
end
|
74
|
+
rescue Rye::CommandNotFound => ex
|
75
|
+
puts " CommandNotFound: #{ex.message}".color(:red)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
rbox.rm(@@script_config_file)
|
80
|
+
end
|
81
|
+
rbox.switch_user original_user
|
82
|
+
else
|
83
|
+
#puts "Nothing to do"
|
84
|
+
end
|
85
|
+
|
86
|
+
tf.delete # delete local copy of script config
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end;end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module Rudy; module Routines;
|
3
|
+
class Release < Rudy::Routines::Base
|
4
|
+
|
5
|
+
def execute
|
6
|
+
p find_scm(:release)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
private
|
11
|
+
def find_scm(routine)
|
12
|
+
env, rol, att = @@global.environment, @@global.role
|
13
|
+
|
14
|
+
# Look for the source control engine, checking all known scm values.
|
15
|
+
# The available one will look like [environment][role][release][svn]
|
16
|
+
params = nil
|
17
|
+
scm_name = nil
|
18
|
+
SUPPORTED_SCM_NAMES.each do |v|
|
19
|
+
scm_name = v
|
20
|
+
params = @@config.routines.find(env, rol, routine, scm_name)
|
21
|
+
break if params
|
22
|
+
end
|
23
|
+
|
24
|
+
if params
|
25
|
+
klass = eval "Rudy::SCM::#{scm_name.to_s.upcase}"
|
26
|
+
scm = klass.new(:base => params[:base])
|
27
|
+
end
|
28
|
+
|
29
|
+
[scm, params]
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end;end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy; module Routines;
|
4
|
+
|
5
|
+
class Shutdown < Rudy::Routines::Base
|
6
|
+
|
7
|
+
def execute
|
8
|
+
raise Rudy::PrivateKeyNotFound, root_keypairpath unless has_keypair?(:root)
|
9
|
+
rmach = Rudy::Machines.new
|
10
|
+
routine = fetch_routine_config(:shutdown)
|
11
|
+
rbox_local = Rye::Box.new('localhost')
|
12
|
+
sconf = fetch_script_config
|
13
|
+
|
14
|
+
# Runs "before_local" scripts of routines config.
|
15
|
+
puts task_separator("BEFORE SCRIPTS (local)")
|
16
|
+
Rudy::Routines::ScriptHelper.before_local(routine, sconf, rbox_local)
|
17
|
+
|
18
|
+
rmach.destroy do |machine|
|
19
|
+
#rmach.list do |machine|
|
20
|
+
|
21
|
+
print "Waiting for instance..."
|
22
|
+
isup = Rudy::Utils.waiter(3, 120, STDOUT, "it's up!", 0) {
|
23
|
+
inst = machine.get_instance
|
24
|
+
inst && inst.running?
|
25
|
+
}
|
26
|
+
machine.update # Add instance info to machine and save it
|
27
|
+
print "Waiting for SSH daemon..."
|
28
|
+
isup = Rudy::Utils.waiter(2, 60, STDOUT, "it's up!", 0) {
|
29
|
+
Rudy::Utils.service_available?(machine.dns_public, 22)
|
30
|
+
}
|
31
|
+
|
32
|
+
opts = { :keys => root_keypairpath, :user => 'root', :debug => nil }
|
33
|
+
rbox = Rye::Box.new(machine.dns_public, opts)
|
34
|
+
|
35
|
+
# Runs "before" scripts of routines config.
|
36
|
+
puts task_separator("BEFORE SCRIPTS")
|
37
|
+
Rudy::Routines::ScriptHelper.before(routine, sconf, machine, rbox)
|
38
|
+
|
39
|
+
# Runs "disk" portion of routines config
|
40
|
+
puts task_separator("DISK ROUTINES")
|
41
|
+
Rudy::Routines::DiskHelper.execute(routine, machine, rbox)
|
42
|
+
|
43
|
+
puts machine_separator(machine.liner_note)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Runs "after_local" scripts
|
48
|
+
# NOTE: There "after" (remote) scripts are not run b/c the machines
|
49
|
+
# are no longer running.
|
50
|
+
puts task_separator("AFTER SCRIPTS (local)")
|
51
|
+
Rudy::Routines::ScriptHelper.after_local(routine, sconf, rbox_local)
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end; end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy; module Routines;
|
4
|
+
|
5
|
+
class Startup < Rudy::Routines::Base
|
6
|
+
|
7
|
+
def execute
|
8
|
+
# There's no keypair check here because Rudy::Machines will attempt
|
9
|
+
# to create one.
|
10
|
+
rmach = Rudy::Machines.new
|
11
|
+
routine = fetch_routine_config(:startup)
|
12
|
+
rbox_local = Rye::Box.new('localhost')
|
13
|
+
sconf = fetch_script_config
|
14
|
+
|
15
|
+
# Runs "before_local" scripts of routines config.
|
16
|
+
# NOTE: Does not run "before" scripts b/c there are no remote machines
|
17
|
+
puts task_separator("BEFORE SCRIPTS (local)")
|
18
|
+
Rudy::Routines::ScriptHelper.before_local(routine, sconf, rbox_local)
|
19
|
+
|
20
|
+
rmach.create do |machine|
|
21
|
+
#rmach.list do |machine|
|
22
|
+
puts machine_separator(machine.liner_note)
|
23
|
+
print "Waiting for instance..."
|
24
|
+
isup = Rudy::Utils.waiter(3, 120, STDOUT, "it's up!", 2) {
|
25
|
+
inst = machine.get_instance
|
26
|
+
inst && inst.running?
|
27
|
+
}
|
28
|
+
machine.update # Add instance info to machine and save it
|
29
|
+
print "Waiting for SSH daemon..."
|
30
|
+
isup = Rudy::Utils.waiter(2, 60, STDOUT, "it's up!", 3) {
|
31
|
+
Rudy::Utils.service_available?(machine.dns_public, 22)
|
32
|
+
}
|
33
|
+
|
34
|
+
opts = { :keys => root_keypairpath, :user => 'root', :debug => nil }
|
35
|
+
rbox = Rye::Box.new(machine.dns_public, opts)
|
36
|
+
|
37
|
+
puts task_separator("DISK ROUTINES")
|
38
|
+
# Runs "disk" portion of routines config
|
39
|
+
Rudy::Routines::DiskHelper.execute(routine, machine, rbox)
|
40
|
+
|
41
|
+
puts task_separator("AFTER SCRIPTS")
|
42
|
+
# Runs "after" scripts of routines config
|
43
|
+
Rudy::Routines::ScriptHelper.after(routine, sconf, machine, rbox)
|
44
|
+
|
45
|
+
puts task_separator("INFO")
|
46
|
+
puts "Filesystem on #{machine.name}:"
|
47
|
+
puts " " << rbox.df(:h).join("#{$/} ")
|
48
|
+
end
|
49
|
+
|
50
|
+
puts task_separator("AFTER SCRIPTS (local)")
|
51
|
+
# Runs "after_local" scripts of routines config
|
52
|
+
Rudy::Routines::ScriptHelper.after_local(routine, sconf, rbox_local)
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end; end
|
data/lib/rudy/scm/svn.rb
CHANGED
@@ -36,7 +36,7 @@ module Rudy
|
|
36
36
|
rev = "01"
|
37
37
|
criteria = ['rel', now.year, mon, day, rev]
|
38
38
|
criteria.insert(-2, username) if username
|
39
|
-
tag = criteria.join(
|
39
|
+
tag = criteria.join(Rudy::DELIM)
|
40
40
|
# Keep incrementing the revision number until we find the next one.
|
41
41
|
tag.succ! while (valid_uri?("#{@base_uri}/#{tag}"))
|
42
42
|
tag
|
data/lib/rudy/utils.rb
CHANGED
@@ -15,9 +15,14 @@ module Rudy
|
|
15
15
|
# Return the external IP address (the one seen by the internet)
|
16
16
|
def external_ip_address
|
17
17
|
ip = nil
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
begin
|
19
|
+
%w{solutious.com/ip/ myip.dk/ whatismyip.com }.each do |sponge| # w/ backup
|
20
|
+
ipstr = Net::HTTP.get(URI.parse("http://#{sponge}")) || ''
|
21
|
+
ip = /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(ipstr).to_s
|
22
|
+
break if ip && !ip.empty?
|
23
|
+
end
|
24
|
+
rescue SocketError, Errno::ETIMEDOUT
|
25
|
+
STDERR.puts "Connection Error. Check your internets!"
|
21
26
|
end
|
22
27
|
ip += "/32" if ip
|
23
28
|
ip
|
@@ -37,7 +42,7 @@ module Rudy
|
|
37
42
|
end
|
38
43
|
|
39
44
|
# Generates a canonical tag name in the form:
|
40
|
-
# rudy-2009-12-31-
|
45
|
+
# rudy-2009-12-31-01
|
41
46
|
# where r1 refers to the revision number that day
|
42
47
|
def generate_tag(revision=1)
|
43
48
|
n = DateTime.now
|
@@ -48,6 +53,156 @@ module Rudy
|
|
48
53
|
end
|
49
54
|
|
50
55
|
|
56
|
+
|
57
|
+
|
58
|
+
# Determine if we're running directly on EC2 or
|
59
|
+
# "some other machine". We do this by checking if
|
60
|
+
# the file /etc/ec2/instance-id exists. This
|
61
|
+
# file is written by /etc/init.d/rudy-ec2-startup.
|
62
|
+
# NOTE: Is there a way to know definitively that this is EC2?
|
63
|
+
# We could make a request to the metadata IP addresses.
|
64
|
+
def Rudy.in_situ?
|
65
|
+
File.exists?('/etc/ec2/instance-id')
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Wait for something to happen.
|
70
|
+
# * +duration+ seconds to wait between tries (default: 2).
|
71
|
+
# * +max+ maximum time to wait (default: 120). Throws an exception when exceeded.
|
72
|
+
# * +logger+ IO object to print +dot+ to.
|
73
|
+
# * +msg+ the message to print on success
|
74
|
+
# * +bells+ number of terminal bells to ring. Set to nil or false to keep the waiter silent
|
75
|
+
#
|
76
|
+
# The +check+ block must return false while waiting. Once it returns true
|
77
|
+
# the waiter will return true too.
|
78
|
+
def waiter(duration=2, max=120, logger=STDOUT, msg=nil, bells=0, &check)
|
79
|
+
# TODO: Move to Drydock. [ed-why?]
|
80
|
+
raise "The waiter needs a block!" unless check
|
81
|
+
duration = 1 if duration < 1
|
82
|
+
max = duration*2 if max < duration
|
83
|
+
dot = '.'
|
84
|
+
begin
|
85
|
+
Timeout::timeout(max) do
|
86
|
+
while !check.call
|
87
|
+
sleep duration
|
88
|
+
logger.print dot if logger.respond_to?(:print)
|
89
|
+
logger.flush if logger.respond_to?(:flush)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
rescue Timeout::Error => ex
|
93
|
+
retry if Annoy.pose_question(" Keep waiting?\a ", /yes|y|ya|sure|you bet!/i, logger)
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
logger.puts msg if msg
|
97
|
+
Rudy::Utils.bell(bells, logger)
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
# Make a terminal bell chime
|
102
|
+
def bell(chimes=1, logger=nil)
|
103
|
+
chimes ||= 0
|
104
|
+
return unless logger
|
105
|
+
chimed = chimes.to_i
|
106
|
+
logger.print "\a"*chimes if chimes > 0 && logger
|
107
|
+
true # be like Rudy.bug()
|
108
|
+
end
|
109
|
+
|
110
|
+
# Have you seen that episode of The Cosby Show where Dizzy Gillespie... ah nevermind.
|
111
|
+
def bug(bugid, logger=STDERR)
|
112
|
+
logger.puts "You have found a bug! If you want, you can email".color(:red)
|
113
|
+
logger.puts 'rudy@solutious.com'.color(:red).bright << " about it. It's bug ##{bugid}.".color(:red)
|
114
|
+
logger.puts "Continuing...".color(:red)
|
115
|
+
true # so we can string it together like: bug('1') && next if ...
|
116
|
+
end
|
117
|
+
|
118
|
+
# Is the given string +str+ an ID of type +identifier+?
|
119
|
+
# * +identifier+ is expected to be a key from ID_MAP
|
120
|
+
# * +str+ is a string you're investigating
|
121
|
+
def is_id?(identifier, str)
|
122
|
+
return false unless identifier && str && known_type?(identifier)
|
123
|
+
identifier &&= identifier.to_sym
|
124
|
+
str &&= str.to_s.strip
|
125
|
+
str.split('-').first == Rudy::ID_MAP[identifier].to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the object type associated to +str+ or nil if unknown.
|
129
|
+
# * +str+ is a string you're investigating
|
130
|
+
def id_type(str)
|
131
|
+
return false unless str
|
132
|
+
str &&= str.to_s.strip
|
133
|
+
(Rudy::ID_MAP.detect { |n,v| v == str.split('-').first } || []).first
|
134
|
+
end
|
135
|
+
|
136
|
+
# Is the given +key+ a known type of object?
|
137
|
+
def known_type?(key)
|
138
|
+
return false unless key
|
139
|
+
key &&= key.to_s.to_sym
|
140
|
+
Rudy::ID_MAP.has_key?(key)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the string identifier associated to this +key+
|
144
|
+
def identifier(key)
|
145
|
+
key &&= key.to_sym
|
146
|
+
return unless Rudy::ID_MAP.has_key?(key)
|
147
|
+
Rudy::ID_MAP[key]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return a string ID without the identifier. i.e. key-stage-app-root => stage-app-root
|
151
|
+
def noid(str)
|
152
|
+
el = str.split('-')
|
153
|
+
el.shift
|
154
|
+
el.join('-')
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
# +msg+ The message to return as a banner
|
159
|
+
# +size+ One of: :normal (default), :huge
|
160
|
+
# +colour+ a valid
|
161
|
+
# Returns a string with styling applying
|
162
|
+
def banner(msg, size = :normal, colour = :black)
|
163
|
+
return unless msg
|
164
|
+
banners = {
|
165
|
+
:huge => Rudy::Utils.without_indent(%Q(
|
166
|
+
=======================================================
|
167
|
+
=======================================================
|
168
|
+
!!!!!!!!! %s !!!!!!!!!
|
169
|
+
=======================================================
|
170
|
+
=======================================================)),
|
171
|
+
:normal => %Q(============ %s ============)
|
172
|
+
}
|
173
|
+
size = :normal unless banners.has_key?(size)
|
174
|
+
colour = :black unless Console.valid_colour?(colour)
|
175
|
+
size, colour = size.to_sym, colour.to_sym
|
176
|
+
sprintf(banners[size], msg).colour(colour).bgcolour(:white).bright
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# <tt>require</tt> a glob of files.
|
181
|
+
# * +path+ is a list of path elements which is sent to File.join
|
182
|
+
# and then to Dir.glob. The list of files found are sent to require.
|
183
|
+
# Nothing is returned but LoadError exceptions are caught. The message
|
184
|
+
# is printed to STDERR and the program exits with 7.
|
185
|
+
def require_glob(*path)
|
186
|
+
begin
|
187
|
+
# TODO: Use autoload
|
188
|
+
Dir.glob(File.join(*path.flatten)).each do |path|
|
189
|
+
require path
|
190
|
+
end
|
191
|
+
rescue LoadError => ex
|
192
|
+
puts "Error: #{ex.message}"
|
193
|
+
exit 7
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Checks whether something is listening to a socket.
|
198
|
+
# * +host+ A hostname
|
199
|
+
# * +port+ The port to check
|
200
|
+
# * +wait+ The number of seconds to wait for before timing out.
|
201
|
+
#
|
202
|
+
# Returns true if +host+ allows a socket connection on +port+.
|
203
|
+
# Returns false if one of the following exceptions is raised:
|
204
|
+
# Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error
|
205
|
+
#
|
51
206
|
def service_available?(host, port, wait=3)
|
52
207
|
begin
|
53
208
|
status = Timeout::timeout(wait) do
|
@@ -60,5 +215,168 @@ module Rudy
|
|
60
215
|
false
|
61
216
|
end
|
62
217
|
end
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
# Capture STDOUT or STDERR to prevent it from being printed.
|
222
|
+
#
|
223
|
+
# capture(:stdout) do
|
224
|
+
# ...
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
def capture(stream)
|
228
|
+
#raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
|
229
|
+
begin
|
230
|
+
stream = stream.to_s
|
231
|
+
eval "$#{stream} = StringIO.new"
|
232
|
+
yield
|
233
|
+
result = eval("$#{stream}").read
|
234
|
+
ensure
|
235
|
+
eval("$#{stream} = #{stream.upcase}")
|
236
|
+
end
|
237
|
+
|
238
|
+
result
|
239
|
+
end
|
240
|
+
|
241
|
+
# A basic file writer
|
242
|
+
def write_to_file(filename, content, mode, chmod=600)
|
243
|
+
mode = (mode == :append) ? 'a' : 'w'
|
244
|
+
f = File.open(filename,mode)
|
245
|
+
f.puts content
|
246
|
+
f.close
|
247
|
+
return unless Rudy.sysinfo.os == :unix
|
248
|
+
raise "Provided chmod is not a Fixnum (#{chmod})" unless chmod.is_a?(Fixnum)
|
249
|
+
File.chmod(chmod, filename)
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# Generates a string of random alphanumeric characters.
|
254
|
+
# * +len+ is the length, an Integer. Default: 8
|
255
|
+
# * +safe+ in safe-mode, ambiguous characters are removed (default: true):
|
256
|
+
# i l o 1 0
|
257
|
+
def strand( len=8, safe=true )
|
258
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
259
|
+
chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
|
260
|
+
str = ""
|
261
|
+
1.upto(len) { |i| str << chars[rand(chars.size-1)] }
|
262
|
+
str
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns +str+ with the leading indentation removed.
|
266
|
+
# Stolen from http://github.com/mynyml/unindent/ because it was better.
|
267
|
+
def without_indent(str)
|
268
|
+
indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min
|
269
|
+
str.gsub(/^[[:blank:]]{#{indent}}/, '')
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
|
274
|
+
|
275
|
+
######### Everything below here is TO BE REMOVED.
|
276
|
+
|
277
|
+
#
|
278
|
+
#
|
279
|
+
# Run a shell command (TO BE REMOVED)
|
280
|
+
def sh(command, chdir=false, verbose=false)
|
281
|
+
prevdir = Dir.pwd
|
282
|
+
Dir.chdir chdir if chdir
|
283
|
+
puts command if verbose
|
284
|
+
system(command)
|
285
|
+
Dir.chdir prevdir if chdir
|
286
|
+
end
|
287
|
+
|
288
|
+
#
|
289
|
+
# Run an SSH command (TO BE REMOVED)
|
290
|
+
def ssh_command(host, keypair, user, command=false, printonly=false, verbose=false)
|
291
|
+
#puts "CONNECTING TO #{host}..."
|
292
|
+
cmd = "ssh -i #{keypair} #{user}@#{host} "
|
293
|
+
cmd += " '#{command}'" if command
|
294
|
+
puts cmd if verbose
|
295
|
+
return cmd if printonly
|
296
|
+
# backticks returns STDOUT
|
297
|
+
# exec replaces current process (it's just like running ssh)
|
298
|
+
# -- UPDATE -- Some problem with exec. "Operation not supported"
|
299
|
+
# using system (http://www.mail-archive.com/mongrel-users@rubyforge.org/msg02018.html)
|
300
|
+
(command) ? `#{cmd}` : Kernel.system(cmd)
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
# (TO BE REMOVED)
|
305
|
+
# TODO: This is old and insecure.
|
306
|
+
def scp_command(host, keypair, user, paths, to_path, to_local=false, verbose=false, printonly=false)
|
307
|
+
|
308
|
+
paths = [paths] unless paths.is_a?(Array)
|
309
|
+
from_paths = ""
|
310
|
+
if to_local
|
311
|
+
paths.each do |path|
|
312
|
+
from_paths << "#{user}@#{host}:#{path} "
|
313
|
+
end
|
314
|
+
#puts "Copying FROM remote TO this machine", $/
|
315
|
+
|
316
|
+
else
|
317
|
+
to_path = "#{user}@#{host}:#{to_path}"
|
318
|
+
from_paths = paths.join(' ')
|
319
|
+
#puts "Copying FROM this machine TO remote", $/
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
cmd = "scp -r "
|
324
|
+
cmd << "-i #{keypair}" if keypair
|
325
|
+
cmd << " #{from_paths} #{to_path}"
|
326
|
+
|
327
|
+
puts cmd if verbose
|
328
|
+
printonly ? (puts cmd) : system(cmd)
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# = RSSReader
|
335
|
+
#
|
336
|
+
# A rudimentary way to read an RSS feed as a hash.
|
337
|
+
# Adapted from: http://snippets.dzone.com/posts/show/68
|
338
|
+
#
|
339
|
+
module Rudy::Utils::RSSReader
|
340
|
+
extend self
|
341
|
+
require 'net/http'
|
342
|
+
require 'rexml/document'
|
343
|
+
|
344
|
+
# Returns a feed as a hash.
|
345
|
+
# * +uri+ to RSS feed
|
346
|
+
def run(uri)
|
347
|
+
begin
|
348
|
+
xmlstr = Net::HTTP.get(URI.parse(uri))
|
349
|
+
rescue SocketError, Errno::ETIMEDOUT
|
350
|
+
STDERR.puts "Connection Error. Check your internets!"
|
351
|
+
end
|
352
|
+
|
353
|
+
xml = REXML::Document.new xmlstr
|
354
|
+
|
355
|
+
data = { :items => [] }
|
356
|
+
xml.elements.each '//channel' do |item|
|
357
|
+
item.elements.each do |e|
|
358
|
+
n = e.name.downcase.gsub(/^dc:(\w)/,"\1").to_sym
|
359
|
+
next if n == :item
|
360
|
+
data[n] = e.text
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
#data = {
|
365
|
+
# :title => xml.root.elements['channel/title'].text,
|
366
|
+
# :link => xml.root.elements['channel/link'].text,
|
367
|
+
# :updated => xml.root.elements['channel/lastBuildDate'].text,
|
368
|
+
# :uri => uri,
|
369
|
+
# :items => []
|
370
|
+
#}
|
371
|
+
#data[:updated] &&= DateTime.parse(data[:updated])
|
372
|
+
|
373
|
+
xml.elements.each '//item' do |item|
|
374
|
+
new_items = {} and item.elements.each do |e|
|
375
|
+
n = e.name.downcase.gsub(/^dc:(\w)/,"\1").to_sym
|
376
|
+
new_items[n] = e.text
|
377
|
+
end
|
378
|
+
data[:items] << new_items
|
379
|
+
end
|
380
|
+
data
|
63
381
|
end
|
64
382
|
end
|