sabat-rudy 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/CHANGES.txt +188 -0
  2. data/LICENSE.txt +19 -0
  3. data/README.rdoc +118 -0
  4. data/Rakefile +165 -0
  5. data/Rudyfile +184 -0
  6. data/bin/ird +153 -0
  7. data/bin/rudy +232 -0
  8. data/bin/rudy-ec2 +241 -0
  9. data/bin/rudy-s3 +79 -0
  10. data/bin/rudy-sdb +69 -0
  11. data/examples/README.md +10 -0
  12. data/examples/debian-sinatra-passenger/commands.rb +19 -0
  13. data/examples/debian-sinatra-passenger/machines.rb +32 -0
  14. data/examples/debian-sinatra-passenger/routines.rb +30 -0
  15. data/examples/debian-sinatra-thin/commands.rb +17 -0
  16. data/examples/debian-sinatra-thin/machines.rb +35 -0
  17. data/examples/debian-sinatra-thin/routines.rb +72 -0
  18. data/lib/rudy.rb +170 -0
  19. data/lib/rudy/aws.rb +75 -0
  20. data/lib/rudy/aws/ec2.rb +59 -0
  21. data/lib/rudy/aws/ec2/address.rb +157 -0
  22. data/lib/rudy/aws/ec2/group.rb +301 -0
  23. data/lib/rudy/aws/ec2/image.rb +168 -0
  24. data/lib/rudy/aws/ec2/instance.rb +438 -0
  25. data/lib/rudy/aws/ec2/keypair.rb +104 -0
  26. data/lib/rudy/aws/ec2/snapshot.rb +109 -0
  27. data/lib/rudy/aws/ec2/volume.rb +230 -0
  28. data/lib/rudy/aws/ec2/zone.rb +77 -0
  29. data/lib/rudy/aws/s3.rb +60 -0
  30. data/lib/rudy/aws/sdb.rb +312 -0
  31. data/lib/rudy/aws/sdb/error.rb +47 -0
  32. data/lib/rudy/cli.rb +186 -0
  33. data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
  34. data/lib/rudy/cli/aws/ec2/candy.rb +191 -0
  35. data/lib/rudy/cli/aws/ec2/groups.rb +118 -0
  36. data/lib/rudy/cli/aws/ec2/images.rb +185 -0
  37. data/lib/rudy/cli/aws/ec2/instances.rb +222 -0
  38. data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
  39. data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
  40. data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
  41. data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
  42. data/lib/rudy/cli/aws/s3/buckets.rb +49 -0
  43. data/lib/rudy/cli/aws/s3/store.rb +22 -0
  44. data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
  45. data/lib/rudy/cli/candy.rb +19 -0
  46. data/lib/rudy/cli/config.rb +81 -0
  47. data/lib/rudy/cli/disks.rb +58 -0
  48. data/lib/rudy/cli/machines.rb +114 -0
  49. data/lib/rudy/cli/routines.rb +108 -0
  50. data/lib/rudy/config.rb +116 -0
  51. data/lib/rudy/config/objects.rb +148 -0
  52. data/lib/rudy/global.rb +130 -0
  53. data/lib/rudy/guidelines.rb +18 -0
  54. data/lib/rudy/huxtable.rb +373 -0
  55. data/lib/rudy/machines.rb +281 -0
  56. data/lib/rudy/metadata.rb +51 -0
  57. data/lib/rudy/metadata/backup.rb +113 -0
  58. data/lib/rudy/metadata/backups.rb +65 -0
  59. data/lib/rudy/metadata/disk.rb +177 -0
  60. data/lib/rudy/metadata/disks.rb +67 -0
  61. data/lib/rudy/metadata/objectbase.rb +104 -0
  62. data/lib/rudy/mixins.rb +2 -0
  63. data/lib/rudy/mixins/hash.rb +25 -0
  64. data/lib/rudy/routines.rb +318 -0
  65. data/lib/rudy/routines/helper.rb +55 -0
  66. data/lib/rudy/routines/helpers/dependshelper.rb +34 -0
  67. data/lib/rudy/routines/helpers/diskhelper.rb +331 -0
  68. data/lib/rudy/routines/helpers/scmhelper.rb +39 -0
  69. data/lib/rudy/routines/helpers/scripthelper.rb +198 -0
  70. data/lib/rudy/routines/helpers/userhelper.rb +37 -0
  71. data/lib/rudy/routines/passthrough.rb +38 -0
  72. data/lib/rudy/routines/reboot.rb +75 -0
  73. data/lib/rudy/routines/release.rb +50 -0
  74. data/lib/rudy/routines/shutdown.rb +33 -0
  75. data/lib/rudy/routines/startup.rb +36 -0
  76. data/lib/rudy/scm.rb +75 -0
  77. data/lib/rudy/scm/git.rb +217 -0
  78. data/lib/rudy/scm/svn.rb +110 -0
  79. data/lib/rudy/utils.rb +365 -0
  80. data/rudy.gemspec +151 -0
  81. data/support/mailtest +40 -0
  82. data/support/randomize-root-password +45 -0
  83. data/support/rudy-ec2-startup +200 -0
  84. data/support/update-ec2-ami-tools +20 -0
  85. data/test/01_mixins/10_hash_test.rb +25 -0
  86. data/test/10_config/00_setup_test.rb +20 -0
  87. data/test/10_config/30_machines_test.rb +69 -0
  88. data/test/15_scm/00_setup_test.rb +20 -0
  89. data/test/15_scm/20_git_test.rb +61 -0
  90. data/test/20_sdb/00_setup_test.rb +16 -0
  91. data/test/20_sdb/10_domains_test.rb +115 -0
  92. data/test/25_ec2/00_setup_test.rb +29 -0
  93. data/test/25_ec2/10_keypairs_test.rb +41 -0
  94. data/test/25_ec2/20_groups_test.rb +131 -0
  95. data/test/25_ec2/30_addresses_test.rb +38 -0
  96. data/test/25_ec2/40_volumes_test.rb +49 -0
  97. data/test/25_ec2/50_snapshots_test.rb +74 -0
  98. data/test/26_ec2_instances/00_setup_test.rb +28 -0
  99. data/test/26_ec2_instances/10_instances_test.rb +83 -0
  100. data/test/26_ec2_instances/50_images_test.rb +13 -0
  101. data/test/30_sdb_metadata/00_setup_test.rb +21 -0
  102. data/test/30_sdb_metadata/10_disks_test.rb +109 -0
  103. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  104. data/test/coverage.txt +51 -0
  105. data/test/helper.rb +36 -0
  106. metadata +276 -0
data/lib/rudy/scm.rb ADDED
@@ -0,0 +1,75 @@
1
+
2
+
3
+ module Rudy
4
+ module SCM
5
+
6
+ class NotAWorkingCopy < Rudy::Error
7
+ def message
8
+ "Not the root directory of a #{@obj} working copy"
9
+ end
10
+ end
11
+ class CannotCreateTag < Rudy::Error
12
+ def message
13
+ "There was an unknown problem creating a release tag (#{@obj})"
14
+ end
15
+ end
16
+ class DirtyWorkingCopy < Rudy::Error
17
+ def message
18
+ "Please commit local #{@obj} changes"
19
+ end
20
+ end
21
+ class RemoteError < Rudy::Error; end
22
+ class NoRemoteURI < Rudy::Error; end
23
+ class TooManyTags < Rudy::Error
24
+ def message; "Too many tag creation attempts!"; end
25
+ end
26
+ class NoRemotePath < Rudy::Error
27
+ def message
28
+ "Add a path for #{@obj} in your routines config"
29
+ end
30
+ end
31
+
32
+
33
+ module ObjectBase
34
+
35
+
36
+ def raise_early_exceptions; raise "override raise_early_exceptions"; end
37
+
38
+ # copied from routines/helper.rb
39
+ def execute_rbox_command(ret=nil, &command)
40
+ begin
41
+ ret = command.call
42
+ puts ' ' << ret.stdout.join("#{$/} ") if !ret.stdout.empty?
43
+ print_response(ret)
44
+ rescue Rye::CommandError => ex
45
+ print_response(ex)
46
+ exit 12 unless keep_going?
47
+ rescue Rye::CommandNotFound => ex
48
+ STDERR.puts " CommandNotFound: #{ex.message}".color(:red)
49
+ STDERR.puts ex.backtrace
50
+ exit 12 unless keep_going?
51
+ end
52
+
53
+ ret
54
+ end
55
+
56
+
57
+ private
58
+ def keep_going?
59
+ Annoy.pose_question(" Keep going?\a ", /yes|y|ya|sure|you bet!/i, STDERR)
60
+ end
61
+
62
+ def print_response(rap)
63
+ [:stderr].each do |sumpin|
64
+ next if rap.send(sumpin).empty?
65
+ STDERR.puts " #{sumpin}: #{rap.send(sumpin).join("#{$/} ")}".color(:red)
66
+ end
67
+ STDERR.puts " Exit code: #{rap.exit_code}".color(:red) if rap.exit_code != 0
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ Rudy::Utils.require_glob(RUDY_LIB, 'rudy', 'scm', '*.rb')
@@ -0,0 +1,217 @@
1
+
2
+ require 'date'
3
+
4
+
5
+ module Rudy
6
+ module SCM
7
+ class GIT
8
+ require 'grit'
9
+ include Rudy::SCM::ObjectBase
10
+ include Grit
11
+
12
+ attr_accessor :base_uri
13
+ attr_accessor :remote
14
+ attr_accessor :branch
15
+ attr_reader :repo
16
+ attr_reader :rbox
17
+ attr_reader :rtag
18
+ attr_reader :user
19
+ attr_reader :pkey
20
+ attr_reader :changes
21
+
22
+ # * +args+ a hash of params from the git block in the routines config
23
+ #
24
+ def initialize(args={})
25
+ args = {
26
+ :privatekey => nil,
27
+ :remote => :origin,
28
+ :branch => :master,
29
+ :user => :root,
30
+ :changes => :enforce,
31
+ :path => nil
32
+ }.merge(args)
33
+ @remote, @branch, @path = args[:remote], args[:branch], args[:path]
34
+ @user, @pkey, @changes = args[:user], args[:privatekey], args[:changes]
35
+ @repo = Repo.new(Dir.pwd) if GIT.working_copy?
36
+ end
37
+
38
+ def engine; :git; end
39
+
40
+ def liner_note
41
+ "%-40s (git:%s:%s)" % [@rtag, @remote, @branch]
42
+ end
43
+
44
+ def create_release(username=nil, msg=nil)
45
+ @rtag = find_next_rtag(username)
46
+ msg ||= 'Another Release by Rudy!'
47
+ msg.tr!("'", "''")
48
+ ret = Rye.shell(:git, "tag", @rtag) # Use annotated? -a -m '#{msg}'
49
+ raise ret.stderr.join($/) if ret.exit_code > 0
50
+ ret = Rye.shell(:git, "push") if @remote
51
+ raise ret.stderr.join($/) if ret.exit_code > 0
52
+ ret = Rye.shell(:git, "push #{@remote} #{rtag}") if @remote
53
+ raise ret.stderr.join($/) if ret.exit_code > 0
54
+ @rtag
55
+ end
56
+
57
+ # rel-2009-03-05-user-rev
58
+ def find_next_rtag(username=nil)
59
+ now = Time.now
60
+ mon = now.mon.to_s.rjust(2, '0')
61
+ day = now.day.to_s.rjust(2, '0')
62
+ rev = "01"
63
+ criteria = ['rel', now.year, mon, day, rev]
64
+ criteria.insert(-2, username) if username
65
+ rev.succ! while valid_rtag?(criteria.join(Rudy::DELIM)) && rev.to_i < 50
66
+ raise TooManyTags if rev.to_i >= 50
67
+ criteria.join(Rudy::DELIM)
68
+ end
69
+
70
+ def delete_rtag(rtag=nil)
71
+ rtag ||= @rtag
72
+ ret = execute_rbox_command { Rye.shell(:git, 'tag', :d, rtag) }
73
+ raise ret.stderr.join($/) if ret.exit_code > 0 # TODO: retest
74
+ # Equivalent to: "git push origin :tag-name" which deletes a remote tag
75
+ ret = execute_rbox_command { Rye.shell(:git, "push #{@remote} :#{rtag}") } if @remote
76
+ raise ret.stderr.join($/) if ret.exit_code > 0
77
+ true
78
+ end
79
+
80
+ def create_remote_checkout(rbox)
81
+
82
+ # Make sure the directory above the clone path exists
83
+ # and that it's owned by the request user.
84
+ rbox.mkdir(:p, File.dirname(@path))
85
+ rbox.chown(@user, File.dirname(@path))
86
+
87
+ begin
88
+ original_user = rbox.user
89
+ rbox.switch_user(@user)
90
+
91
+ if @pkey
92
+ # Try when debugging: ssh -vi path/2/pkey git@github.com
93
+ key = File.basename(@pkey)
94
+ homedir = rbox.getenv['HOME']
95
+ rbox.mkdir(:p, :m, '700', '.ssh') rescue nil # :p says keep quiet if it exists
96
+ if rbox.file_exists?(".ssh/#{key}")
97
+ puts " Remote private key #{key} already exists".colour(:red)
98
+ else
99
+ rbox.upload(@pkey, ".ssh/#{key}")
100
+ end
101
+
102
+ ## NOTE: The following are two attempts at telling git which
103
+ ## private key to use. Both fail. The only thing I could get
104
+ ## to work is modifying the ~/.ssh/config file.
105
+ ##
106
+ ## This runs fine, but "git clone" doesn't care.
107
+ ## git config --global --replace-all http.sslKey /home/delano/.ssh/id_rsa
108
+ ## rbox.git('config', '--global', '--replace-all', 'http.sslKey', "#{homedir}/.ssh/#{key}")
109
+ ##
110
+ ## "git clone" doesn't care about this either. Note that both these
111
+ ## config attempts come directly from the git-config man page:
112
+ ## http://www.kernel.org/pub/software/scm/git/docs/git-config.html
113
+ ## export GIT_SSL_KEY=/home/delano/.ssh/id_rsa
114
+ ## rbox.setenv("GIT_SSL_KEY", "#{homedir}/.ssh/#{key}")
115
+
116
+ if rbox.file_exists?('.ssh/config')
117
+ rbox.cp('.ssh/config', ".ssh/config-previous")
118
+ ssh_config = rbox.download('.ssh/config')
119
+ end
120
+
121
+ ssh_config ||= StringIO.new
122
+ ssh_config.puts $/, "IdentityFile #{homedir}/.ssh/#{key}"
123
+ puts " Adding IdentityFile #{key} to #{homedir}/.ssh/config"
124
+
125
+ rbox.upload(ssh_config, '.ssh/config')
126
+ rbox.chmod('0600', '.ssh/config')
127
+
128
+ end
129
+
130
+ # We need to add the host keys to the user's known_hosts file
131
+ # to prevent the git commands from failing when it raises the
132
+ # "Host key verification failed." messsage.
133
+ if rbox.file_exists?('.ssh/known_hosts')
134
+ rbox.cp('.ssh/known_hosts', ".ssh/known_hosts-previous")
135
+ known_hosts = rbox.download('.ssh/known_hosts')
136
+ end
137
+ known_hosts ||= StringIO.new
138
+ remote = get_remote_uri
139
+ host = URI.parse(remote).host rescue nil
140
+ host ||= remote.scan(/\A.+?@(.+?)\:/).flatten.first
141
+ known_hosts.puts $/, Rye.remote_host_keys(host)
142
+ puts " Adding host key for #{host} to .ssh/known_hosts"
143
+
144
+ rbox.upload(known_hosts, '.ssh/known_hosts')
145
+ rbox.chmod('0600', '.ssh/known_hosts')
146
+
147
+ execute_rbox_command {
148
+ rbox.git('clone', get_remote_uri, @path)
149
+ }
150
+ rbox.cd(@path)
151
+ execute_rbox_command {
152
+ rbox.git('checkout', :b, @rtag)
153
+ }
154
+ rescue Rye::CommandError => ex
155
+ puts ex.message
156
+ ensure
157
+ # Return to the original user and directory
158
+ rbox.switch_user(original_user)
159
+ rbox.cd
160
+ end
161
+
162
+ end
163
+
164
+
165
+ def get_remote_uri
166
+ ret = Rye.shell(:git, "config", "remote.#{@remote}.url")
167
+ ret.stdout.first
168
+ end
169
+
170
+ # Check if the given remote is valid.
171
+ #def has_remote?(remote)
172
+ # success = false
173
+ # (@repo.remotes || []).each do |r|
174
+ # end
175
+ # success
176
+ #end
177
+
178
+ def valid_rtag?(tag)
179
+ # git tag -l tagname returns a 0 exit code and stdout is empty
180
+ # when a tag does not exit. When it does exist, the exit code
181
+ # is 0 and stdout contains the tagname.
182
+ ret = Rye.shell(:git, 'tag', :l, tag)
183
+ # change :l to :d for quick deleting above and return true
184
+ # OR: just change to :d to always recreate the same tag
185
+ (ret.exit_code == 0 && ret.stdout.to_s == tag)
186
+ end
187
+
188
+ # Are all local changes committed?
189
+ def self.clean_working_copy?(path=Dir.pwd)
190
+ Rye.shell(:git, 'diff').stdout == []
191
+ end
192
+ def clean_working_copy?; GIT.clean_working_copy?; end
193
+
194
+ def self.working_copy?(path=Dir.pwd)
195
+ (File.exists?(File.join(path, '.git')))
196
+ end
197
+ def working_copy?; GIT.working_copy?; end
198
+
199
+ def raise_early_exceptions
200
+ raise NotAWorkingCopy, :git unless working_copy?
201
+ raise DirtyWorkingCopy, :git unless @changes.to_s == 'ignore' || clean_working_copy?
202
+ raise NoRemoteURI, "remote.#{@remote}.url not set" if get_remote_uri.nil?
203
+ raise NoRemotePath, :git if @path.nil?
204
+ raise PrivateKeyNotFound, @pkey if @pkey && !File.exists?(@pkey)
205
+ find_next_rtag # will raise exception is there's a problem
206
+
207
+ # We can't check stuff that requires access to the machine b/c the
208
+ # machine may not be running yet. These include:
209
+ # * Remote checkout path already exists
210
+ # * No git available
211
+ # ...
212
+ # If create_remote_checkout should fail, it should print a message
213
+ # about the release that was created and how to install it manually
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,110 @@
1
+
2
+ require 'date'
3
+
4
+ module Rudy
5
+ module SCM
6
+ class SVN
7
+ attr_accessor :base_uri
8
+
9
+ attr_reader :changes
10
+
11
+ def initialize(args={})
12
+ args = {
13
+ :privatekey => nil,
14
+ :base_uri => nil,
15
+ :user => :root,
16
+ :changes => :enforce,
17
+ :path => nil
18
+ }.merge(args)
19
+ @base_uri, @path = args[:base_uri], args[:path]
20
+ @user, @pkey, @changes = args[:user], args[:privatekey], args[:changes]
21
+ end
22
+
23
+ def engine; :svn; end
24
+
25
+ def liner_note
26
+ "%-40s (svn:%s:%s)" % [@rtag, @base_uri, @branch]
27
+ end
28
+
29
+ def create_release(username=nil, msg=nil)
30
+ local_uri, local_revision = local_info
31
+ rtag = find_next_rtag(username)
32
+ release_uri = "#{@base_uri}/#{rtag}"
33
+ msg ||= 'Another Release by Rudy!'
34
+ msg.tr!("'", "\\'")
35
+ cmd = "svn copy -m '#{msg}' #{local_uri} #{release_uri}"
36
+
37
+ `#{cmd} 2>&1`
38
+
39
+ release_uri
40
+ end
41
+
42
+ def switch_working_copy(tag)
43
+ raise "Invalid release tag (#{tag})." unless valid_rtag?(tag)
44
+ `svn switch #{tag}`
45
+ end
46
+
47
+ # rel-2009-03-05-user-rev
48
+ def find_next_rtag(username=nil)
49
+ now = Time.now
50
+ mon = now.mon.to_s.rjust(2, '0')
51
+ day = now.day.to_s.rjust(2, '0')
52
+ rev = "01"
53
+ criteria = ['rel', now.year, mon, day, rev]
54
+ criteria.insert(-2, username) if username
55
+ tag = criteria.join(Rudy::DELIM)
56
+ # Keep incrementing the revision number until we find the next one.
57
+ tag.succ! while (valid_rtag?("#{@base_uri}/#{tag}"))
58
+ tag
59
+ end
60
+
61
+ def local_info
62
+ ret = Rye.shell(:svn, "info").join
63
+ # URL: http://some/uri/path
64
+ # Repository Root: http://some/uri
65
+ # Repository UUID: c5abe49d-53e4-4ea3-9314-89e1e25aa7e1
66
+ # Revision: 921
67
+ ret.scan(/URL: (http:.+?)\s*\n.+Revision: (\d+)/m).flatten
68
+ end
69
+
70
+ def working_copy?(path)
71
+ (File.exists?(File.join(path, '.svn')))
72
+ end
73
+
74
+ def valid_rtag?(uri)
75
+ ret = `svn info #{uri} 2>&1` || '' # Valid SVN URIs will return some info
76
+ (ret =~ /Repository UUID/) ? true : false
77
+ end
78
+
79
+ # Are all local changes committed?
80
+ def self.clean_working_copy?(path=Dir.pwd)
81
+ Rye.shell(:svn, 'diff', '.').stdout == []
82
+ end
83
+ def clean_working_copy?; SVN.clean_working_copy?; end
84
+
85
+ def self.working_copy?(path=Dir.pwd)
86
+ (File.exists?(File.join(path, '.svn')))
87
+ end
88
+ def working_copy?; SVN.working_copy?; end
89
+
90
+
91
+ def raise_early_exceptions
92
+ raise NotAWorkingCopy, :svn unless working_copy?
93
+ raise DirtyWorkingCopy, :svn unless @changes.to_s == 'ignore' || clean_working_copy?
94
+ #raise NoRemoteURI, "remote.#{@remote}.url not set" if get_remote_uri.nil?
95
+ raise NoRemotePath, :svn if @path.nil?
96
+ raise PrivateKeyNotFound, @pkey if @pkey && !File.exists?(@pkey)
97
+ find_next_rtag # will raise exception is there's a problem
98
+
99
+ # We can't check stuff that requires access to the machine b/c the
100
+ # machine may not be running yet. These include:
101
+ # * Remote checkout path already exists
102
+ # * No git available
103
+ # ...
104
+ # If create_remote_checkout should fail, it should print a message
105
+ # about the release that was created and how to install it manually
106
+ end
107
+
108
+ end
109
+ end
110
+ end
data/lib/rudy/utils.rb ADDED
@@ -0,0 +1,365 @@
1
+
2
+ #require 'drydock/mixins'
3
+ require 'socket'
4
+ require 'open-uri'
5
+ require 'date'
6
+
7
+ require 'timeout'
8
+
9
+ module Rudy
10
+
11
+ # A motley collection of methods that Rudy loves to call!
12
+ module Utils
13
+ extend self
14
+ include Socket::Constants
15
+
16
+ # Return the external IP address (the one seen by the internet)
17
+ def external_ip_address
18
+ ip = nil
19
+ begin
20
+ %w{solutious.com/ip/ myip.dk/ whatismyip.com }.each do |sponge| # w/ backup
21
+ ipstr = Net::HTTP.get(URI.parse("http://#{sponge}")) || ''
22
+ ip = /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(ipstr).to_s
23
+ break if ip && !ip.empty?
24
+ end
25
+ rescue SocketError, Errno::ETIMEDOUT => ex
26
+ STDERR.puts "Connection Error. Check your internets!"
27
+ end
28
+ ip += "/32" if ip
29
+ ip
30
+ end
31
+
32
+ # Return the local IP address which receives external traffic
33
+ # from: http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
34
+ # NOTE: This <em>does not</em> open a connection to the IP address.
35
+ def internal_ip_address
36
+ # turn off reverse DNS resolution temporarily
37
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
38
+ ip = UDPSocket.open {|s| s.connect('75.101.137.7', 1); s.addr.last } # Solutious IP
39
+ ip += "/24" if ip
40
+ ip
41
+ ensure
42
+ Socket.do_not_reverse_lookup = orig
43
+ end
44
+
45
+ # Generates a canonical tag name in the form:
46
+ # rudy-2009-12-31-01
47
+ # where r1 refers to the revision number that day
48
+ def generate_tag(revision=1)
49
+ n = DateTime.now
50
+ y = n.year.to_s.rjust(4, "20")
51
+ m = n.month.to_s.rjust(2, "0")
52
+ d = n.mday.to_s.rjust(2, "0")
53
+ "rudy-%4s-%2s-%2s-r%s" % [y, m, d, revision.to_s.rjust(2, "0")]
54
+ end
55
+
56
+
57
+
58
+
59
+ # Determine if we're running directly on EC2 or
60
+ # "some other machine". We do this by checking if
61
+ # the file /etc/ec2/instance-id exists. This
62
+ # file is written by /etc/init.d/rudy-ec2-startup.
63
+ # NOTE: Is there a way to know definitively that this is EC2?
64
+ # We could make a request to the metadata IP addresses.
65
+ def Rudy.in_situ?
66
+ File.exists?('/etc/ec2/instance-id')
67
+ end
68
+
69
+
70
+ # Wait for something to happen.
71
+ # * +duration+ seconds to wait between tries (default: 2).
72
+ # * +max+ maximum time to wait (default: 120). Throws an exception when exceeded.
73
+ # * +logger+ IO object to print +dot+ to.
74
+ # * +msg+ the message to print before executing the block.
75
+ # * +bells+ number of terminal bells to ring. Set to nil or false to keep the waiter silent
76
+ #
77
+ # The +check+ block must return false while waiting. Once it returns true
78
+ # the waiter will return true too.
79
+ def waiter(duration=2, max=120, logger=STDOUT, msg=nil, bells=0, &check)
80
+ # TODO: Move to Drydock. [ed-why?]
81
+ raise "The waiter needs a block!" unless check
82
+ duration = 1 if duration < 1
83
+ max = duration*2 if max < duration
84
+ dot = '.'
85
+ begin
86
+ if msg && logger
87
+ logger.print msg
88
+ logger.flush
89
+ end
90
+ Timeout::timeout(max) do
91
+ while !check.call
92
+ sleep duration
93
+ logger.print dot if logger.respond_to?(:print)
94
+ logger.flush if logger.respond_to?(:flush)
95
+ end
96
+ end
97
+ rescue Timeout::Error => ex
98
+ retry if Annoy.pose_question(" Keep waiting?\a ", /yes|y|ya|sure|you bet!/i, logger)
99
+ return false
100
+ end
101
+
102
+ if msg && logger
103
+ logger.puts " done"
104
+ logger.flush
105
+ end
106
+
107
+ Rudy::Utils.bell(bells, logger)
108
+ true
109
+ end
110
+
111
+ # Make a terminal bell chime
112
+ def bell(chimes=1, logger=nil)
113
+ chimes ||= 0
114
+ return unless logger
115
+ chimed = chimes.to_i
116
+ logger.print "\a"*chimes if chimes > 0 && logger
117
+ true # be like Rudy.bug()
118
+ end
119
+
120
+ # Have you seen that episode of The Cosby Show where Dizzy Gillespie... ah nevermind.
121
+ def bug(bugid, logger=STDERR)
122
+ logger.puts "You have found a bug! If you want, you can email".color(:red)
123
+ logger.puts 'rudy@solutious.com'.color(:red).bright << " about it. It's bug ##{bugid}.".color(:red)
124
+ logger.puts "Continuing...".color(:red)
125
+ true # so we can string it together like: bug('1') && next if ...
126
+ end
127
+
128
+ # Is the given string +str+ an ID of type +identifier+?
129
+ # * +identifier+ is expected to be a key from ID_MAP
130
+ # * +str+ is a string you're investigating
131
+ def is_id?(identifier, str)
132
+ return false unless identifier && str && known_type?(identifier)
133
+ identifier &&= identifier.to_sym
134
+ str &&= str.to_s.strip
135
+ str.split('-').first == Rudy::ID_MAP[identifier].to_s
136
+ end
137
+
138
+ # Returns the object type associated to +str+ or nil if unknown.
139
+ # * +str+ is a string you're investigating
140
+ def id_type(str)
141
+ return false unless str
142
+ str &&= str.to_s.strip
143
+ (Rudy::ID_MAP.detect { |n,v| v == str.split('-').first } || []).first
144
+ end
145
+
146
+ # Is the given +key+ a known type of object?
147
+ def known_type?(key)
148
+ return false unless key
149
+ key &&= key.to_s.to_sym
150
+ Rudy::ID_MAP.has_key?(key)
151
+ end
152
+
153
+ # Returns the string identifier associated to this +key+
154
+ def identifier(key)
155
+ key &&= key.to_sym
156
+ return unless Rudy::ID_MAP.has_key?(key)
157
+ Rudy::ID_MAP[key]
158
+ end
159
+
160
+ # Return a string ID without the identifier. i.e. key-stage-app-root => stage-app-root
161
+ def noid(str)
162
+ el = str.split('-')
163
+ el.shift
164
+ el.join('-')
165
+ end
166
+
167
+
168
+ # +msg+ The message to return as a banner
169
+ # +size+ One of: :normal (default), :huge
170
+ # +colour+ a valid
171
+ # Returns a string with styling applying
172
+ def banner(msg, size = :normal, colour = :black)
173
+ return unless msg
174
+ banners = {
175
+ :huge => Rudy::Utils.without_indent(%Q(
176
+ =======================================================
177
+ =======================================================
178
+ !!!!!!!!! %s !!!!!!!!!
179
+ =======================================================
180
+ =======================================================)),
181
+ :normal => %Q(============ %s ============)
182
+ }
183
+ size = :normal unless banners.has_key?(size)
184
+ #colour = :black unless Drydock::Console.valid_colour?(colour)
185
+ size, colour = size.to_sym, colour.to_sym
186
+ sprintf(banners[size], msg).bright.att(:reverse)
187
+ end
188
+
189
+
190
+ # <tt>require</tt> a glob of files.
191
+ # * +path+ is a list of path elements which is sent to File.join
192
+ # and then to Dir.glob. The list of files found are sent to require.
193
+ # Nothing is returned but LoadError exceptions are caught. The message
194
+ # is printed to STDERR and the program exits with 7.
195
+ def require_glob(*path)
196
+ begin
197
+ # TODO: Use autoload
198
+ Dir.glob(File.join(*path.flatten)).each do |path|
199
+ require path
200
+ end
201
+ rescue LoadError => ex
202
+ puts "Error: #{ex.message}"
203
+ exit 7
204
+ end
205
+ end
206
+
207
+ # Checks whether something is listening to a socket.
208
+ # * +host+ A hostname
209
+ # * +port+ The port to check
210
+ # * +wait+ The number of seconds to wait for before timing out.
211
+ #
212
+ # Returns true if +host+ allows a socket connection on +port+.
213
+ # Returns false if one of the following exceptions is raised:
214
+ # Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error
215
+ #
216
+ def service_available?(host, port, wait=3)
217
+ begin
218
+ status = Timeout::timeout(wait) do
219
+ socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
220
+ sockaddr = Socket.pack_sockaddr_in( port, host )
221
+ socket.connect( sockaddr )
222
+ end
223
+ true
224
+ rescue Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error => ex
225
+ false
226
+ end
227
+ end
228
+
229
+
230
+
231
+ # Capture STDOUT or STDERR to prevent it from being printed.
232
+ #
233
+ # capture(:stdout) do
234
+ # ...
235
+ # end
236
+ #
237
+ def capture(stream)
238
+ #raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
239
+ begin
240
+ stream = stream.to_s
241
+ eval "$#{stream} = StringIO.new"
242
+ yield
243
+ result = eval("$#{stream}").read
244
+ ensure
245
+ eval("$#{stream} = #{stream.upcase}")
246
+ end
247
+
248
+ result
249
+ end
250
+
251
+ # A basic file writer
252
+ def write_to_file(filename, content, mode, chmod=600)
253
+ mode = (mode == :append) ? 'a' : 'w'
254
+ f = File.open(filename,mode)
255
+ f.puts content
256
+ f.close
257
+ return unless Rudy.sysinfo.os == :unix
258
+ raise "Provided chmod is not a Fixnum (#{chmod})" unless chmod.is_a?(Fixnum)
259
+ File.chmod(chmod, filename)
260
+ end
261
+
262
+ #
263
+ # Generates a string of random alphanumeric characters.
264
+ # * +len+ is the length, an Integer. Default: 8
265
+ # * +safe+ in safe-mode, ambiguous characters are removed (default: true):
266
+ # i l o 1 0
267
+ def strand( len=8, safe=true )
268
+ chars = ("a".."z").to_a + ("0".."9").to_a
269
+ chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
270
+ str = ""
271
+ 1.upto(len) { |i| str << chars[rand(chars.size-1)] }
272
+ str
273
+ end
274
+
275
+ # Returns +str+ with the leading indentation removed.
276
+ # Stolen from http://github.com/mynyml/unindent/ because it was better.
277
+ def without_indent(str)
278
+ indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min
279
+ str.gsub(/^[[:blank:]]{#{indent}}/, '')
280
+ end
281
+
282
+
283
+
284
+
285
+ ######### Everything below here is TO BE REMOVED.
286
+
287
+ # (TO BE REMOVED)
288
+ # TODO: This is old and nasty.
289
+ def scp_command(host, keypair, user, paths, to_path, to_local=false, verbose=false, printonly=false)
290
+
291
+ paths = [paths] unless paths.is_a?(Array)
292
+ from_paths = ""
293
+ if to_local
294
+ paths.each do |path|
295
+ from_paths << "#{user}@#{host}:#{path} "
296
+ end
297
+ #puts "Copying FROM remote TO this machine", $/
298
+
299
+ else
300
+ to_path = "#{user}@#{host}:#{to_path}"
301
+ from_paths = paths.join(' ')
302
+ #puts "Copying FROM this machine TO remote", $/
303
+ end
304
+
305
+
306
+ cmd = "scp -r "
307
+ cmd << "-i #{keypair}" if keypair
308
+ cmd << " #{from_paths} #{to_path}"
309
+
310
+ puts cmd if verbose
311
+ printonly ? (puts cmd) : system(cmd)
312
+ end
313
+
314
+ end
315
+ end
316
+
317
+ # = RSSReader
318
+ #
319
+ # A rudimentary way to read an RSS feed as a hash.
320
+ # Adapted from: http://snippets.dzone.com/posts/show/68
321
+ #
322
+ module Rudy::Utils::RSSReader
323
+ extend self
324
+ require 'net/http'
325
+ require 'rexml/document'
326
+
327
+ # Returns a feed as a hash.
328
+ # * +uri+ to RSS feed
329
+ def run(uri)
330
+ begin
331
+ xmlstr = Net::HTTP.get(URI.parse(uri))
332
+ rescue SocketError, Errno::ETIMEDOUT
333
+ STDERR.puts "Connection Error. Check your internets!"
334
+ end
335
+
336
+ xml = REXML::Document.new xmlstr
337
+
338
+ data = { :items => [] }
339
+ xml.elements.each '//channel' do |item|
340
+ item.elements.each do |e|
341
+ n = e.name.downcase.gsub(/^dc:(\w)/,"\1").to_sym
342
+ next if n == :item
343
+ data[n] = e.text
344
+ end
345
+ end
346
+
347
+ #data = {
348
+ # :title => xml.root.elements['channel/title'].text,
349
+ # :link => xml.root.elements['channel/link'].text,
350
+ # :updated => xml.root.elements['channel/lastBuildDate'].text,
351
+ # :uri => uri,
352
+ # :items => []
353
+ #}
354
+ #data[:updated] &&= DateTime.parse(data[:updated])
355
+
356
+ xml.elements.each '//item' do |item|
357
+ new_items = {} and item.elements.each do |e|
358
+ n = e.name.downcase.gsub(/^dc:(\w)/,"\1").to_sym
359
+ new_items[n] = e.text
360
+ end
361
+ data[:items] << new_items
362
+ end
363
+ data
364
+ end
365
+ end