rudy 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/CHANGES.txt +54 -30
  2. data/README.rdoc +100 -12
  3. data/Rakefile +103 -8
  4. data/Rudyfile +119 -0
  5. data/bin/ird +175 -0
  6. data/bin/rudy +259 -156
  7. data/bin/rudy-ec2 +228 -95
  8. data/bin/rudy-s3 +76 -0
  9. data/bin/rudy-sdb +67 -0
  10. data/lib/annoy.rb +270 -0
  11. data/lib/console.rb +30 -9
  12. data/lib/escape.rb +305 -0
  13. data/lib/rudy.rb +151 -182
  14. data/lib/rudy/aws.rb +56 -49
  15. data/lib/rudy/aws/ec2.rb +47 -292
  16. data/lib/rudy/aws/ec2/address.rb +157 -0
  17. data/lib/rudy/aws/ec2/group.rb +301 -0
  18. data/lib/rudy/aws/ec2/image.rb +168 -0
  19. data/lib/rudy/aws/ec2/instance.rb +434 -0
  20. data/lib/rudy/aws/ec2/keypair.rb +104 -0
  21. data/lib/rudy/aws/ec2/snapshot.rb +98 -0
  22. data/lib/rudy/aws/ec2/volume.rb +230 -0
  23. data/lib/rudy/aws/ec2/zone.rb +77 -0
  24. data/lib/rudy/aws/s3.rb +54 -0
  25. data/lib/rudy/aws/sdb.rb +298 -0
  26. data/lib/rudy/aws/sdb/error.rb +46 -0
  27. data/lib/rudy/{metadata/backup.rb → backup.rb} +26 -51
  28. data/lib/rudy/cli.rb +157 -0
  29. data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
  30. data/lib/rudy/cli/aws/ec2/candy.rb +208 -0
  31. data/lib/rudy/cli/aws/ec2/groups.rb +121 -0
  32. data/lib/rudy/cli/aws/ec2/images.rb +196 -0
  33. data/lib/rudy/cli/aws/ec2/instances.rb +194 -0
  34. data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
  35. data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
  36. data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
  37. data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
  38. data/lib/rudy/cli/aws/s3/buckets.rb +50 -0
  39. data/lib/rudy/cli/aws/s3/store.rb +22 -0
  40. data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
  41. data/lib/rudy/cli/candy.rb +8 -0
  42. data/lib/rudy/{command → cli}/config.rb +34 -24
  43. data/lib/rudy/cli/disks.rb +35 -0
  44. data/lib/rudy/cli/machines.rb +94 -0
  45. data/lib/rudy/cli/routines.rb +57 -0
  46. data/lib/rudy/config.rb +77 -72
  47. data/lib/rudy/config/objects.rb +29 -0
  48. data/lib/rudy/disks.rb +248 -0
  49. data/lib/rudy/global.rb +121 -0
  50. data/lib/rudy/huxtable.rb +340 -0
  51. data/lib/rudy/machines.rb +245 -0
  52. data/lib/rudy/metadata.rb +123 -13
  53. data/lib/rudy/routines.rb +47 -0
  54. data/lib/rudy/routines/helpers/diskhelper.rb +101 -0
  55. data/lib/rudy/routines/helpers/scripthelper.rb +91 -0
  56. data/lib/rudy/routines/release.rb +34 -0
  57. data/lib/rudy/routines/shutdown.rb +57 -0
  58. data/lib/rudy/routines/startup.rb +58 -0
  59. data/lib/rudy/scm/svn.rb +1 -1
  60. data/lib/rudy/utils.rb +322 -4
  61. data/lib/storable.rb +26 -17
  62. data/lib/sysinfo.rb +274 -0
  63. data/lib/tryouts.rb +6 -13
  64. data/rudy.gemspec +128 -42
  65. data/support/randomize-root-password +45 -0
  66. data/support/rudy-ec2-startup +9 -9
  67. data/support/update-ec2-ami-tools +20 -0
  68. data/test/05_config/00_setup_test.rb +20 -0
  69. data/test/05_config/30_machines_test.rb +69 -0
  70. data/test/20_sdb/00_setup_test.rb +16 -0
  71. data/test/20_sdb/10_domains_test.rb +115 -0
  72. data/test/25_ec2/00_setup_test.rb +29 -0
  73. data/test/25_ec2/10_keypairs_test.rb +41 -0
  74. data/test/25_ec2/20_groups_test.rb +131 -0
  75. data/test/25_ec2/30_addresses_test.rb +38 -0
  76. data/test/25_ec2/40_volumes_test.rb +49 -0
  77. data/test/25_ec2/50_snapshots_test.rb +74 -0
  78. data/test/26_ec2_instances/00_setup_test.rb +28 -0
  79. data/test/26_ec2_instances/10_instances_test.rb +83 -0
  80. data/test/26_ec2_instances/50_images_test.rb +13 -0
  81. data/test/30_sdb_metadata/00_setup_test.rb +21 -0
  82. data/test/30_sdb_metadata/10_disks_test.rb +109 -0
  83. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  84. data/test/coverage.txt +51 -0
  85. data/test/helper.rb +36 -0
  86. data/vendor/highline-1.5.1/CHANGELOG +222 -0
  87. data/vendor/highline-1.5.1/INSTALL +35 -0
  88. data/vendor/highline-1.5.1/LICENSE +7 -0
  89. data/vendor/highline-1.5.1/README +63 -0
  90. data/vendor/highline-1.5.1/Rakefile +82 -0
  91. data/vendor/highline-1.5.1/TODO +6 -0
  92. data/vendor/highline-1.5.1/examples/ansi_colors.rb +38 -0
  93. data/vendor/highline-1.5.1/examples/asking_for_arrays.rb +18 -0
  94. data/vendor/highline-1.5.1/examples/basic_usage.rb +75 -0
  95. data/vendor/highline-1.5.1/examples/color_scheme.rb +32 -0
  96. data/vendor/highline-1.5.1/examples/limit.rb +12 -0
  97. data/vendor/highline-1.5.1/examples/menus.rb +65 -0
  98. data/vendor/highline-1.5.1/examples/overwrite.rb +19 -0
  99. data/vendor/highline-1.5.1/examples/page_and_wrap.rb +322 -0
  100. data/vendor/highline-1.5.1/examples/password.rb +7 -0
  101. data/vendor/highline-1.5.1/examples/trapping_eof.rb +22 -0
  102. data/vendor/highline-1.5.1/examples/using_readline.rb +17 -0
  103. data/vendor/highline-1.5.1/lib/highline.rb +758 -0
  104. data/vendor/highline-1.5.1/lib/highline/color_scheme.rb +120 -0
  105. data/vendor/highline-1.5.1/lib/highline/compatibility.rb +17 -0
  106. data/vendor/highline-1.5.1/lib/highline/import.rb +43 -0
  107. data/vendor/highline-1.5.1/lib/highline/menu.rb +395 -0
  108. data/vendor/highline-1.5.1/lib/highline/question.rb +463 -0
  109. data/vendor/highline-1.5.1/lib/highline/system_extensions.rb +193 -0
  110. data/vendor/highline-1.5.1/setup.rb +1360 -0
  111. data/vendor/highline-1.5.1/test/tc_color_scheme.rb +56 -0
  112. data/vendor/highline-1.5.1/test/tc_highline.rb +823 -0
  113. data/vendor/highline-1.5.1/test/tc_import.rb +54 -0
  114. data/vendor/highline-1.5.1/test/tc_menu.rb +429 -0
  115. data/vendor/highline-1.5.1/test/ts_all.rb +15 -0
  116. metadata +141 -38
  117. data/lib/aws_sdb.rb +0 -3
  118. data/lib/aws_sdb/error.rb +0 -42
  119. data/lib/aws_sdb/service.rb +0 -215
  120. data/lib/rudy/aws/simpledb.rb +0 -53
  121. data/lib/rudy/command/addresses.rb +0 -46
  122. data/lib/rudy/command/backups.rb +0 -175
  123. data/lib/rudy/command/base.rb +0 -841
  124. data/lib/rudy/command/deploy.rb +0 -12
  125. data/lib/rudy/command/disks.rb +0 -213
  126. data/lib/rudy/command/environment.rb +0 -73
  127. data/lib/rudy/command/groups.rb +0 -61
  128. data/lib/rudy/command/images.rb +0 -91
  129. data/lib/rudy/command/instances.rb +0 -85
  130. data/lib/rudy/command/machines.rb +0 -161
  131. data/lib/rudy/command/metadata.rb +0 -41
  132. data/lib/rudy/command/release.rb +0 -174
  133. data/lib/rudy/command/volumes.rb +0 -66
  134. data/lib/rudy/metadata/disk.rb +0 -138
  135. 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
@@ -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(RUDY_DELIM)
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
@@ -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
- %w{solutious.com/ip myip.dk/ whatismyip.com }.each do |sponge| # w/ backup
19
- break unless ip.nil?
20
- ip = (open("http://#{sponge}") { |f| /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(f.read) }).to_s rescue nil
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-r1
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