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.
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