rudy 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|