sabat-rudy 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +188 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +118 -0
- data/Rakefile +165 -0
- data/Rudyfile +184 -0
- data/bin/ird +153 -0
- data/bin/rudy +232 -0
- data/bin/rudy-ec2 +241 -0
- data/bin/rudy-s3 +79 -0
- data/bin/rudy-sdb +69 -0
- data/examples/README.md +10 -0
- data/examples/debian-sinatra-passenger/commands.rb +19 -0
- data/examples/debian-sinatra-passenger/machines.rb +32 -0
- data/examples/debian-sinatra-passenger/routines.rb +30 -0
- data/examples/debian-sinatra-thin/commands.rb +17 -0
- data/examples/debian-sinatra-thin/machines.rb +35 -0
- data/examples/debian-sinatra-thin/routines.rb +72 -0
- data/lib/rudy.rb +170 -0
- data/lib/rudy/aws.rb +75 -0
- data/lib/rudy/aws/ec2.rb +59 -0
- 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 +438 -0
- data/lib/rudy/aws/ec2/keypair.rb +104 -0
- data/lib/rudy/aws/ec2/snapshot.rb +109 -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 +60 -0
- data/lib/rudy/aws/sdb.rb +312 -0
- data/lib/rudy/aws/sdb/error.rb +47 -0
- data/lib/rudy/cli.rb +186 -0
- data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
- data/lib/rudy/cli/aws/ec2/candy.rb +191 -0
- data/lib/rudy/cli/aws/ec2/groups.rb +118 -0
- data/lib/rudy/cli/aws/ec2/images.rb +185 -0
- data/lib/rudy/cli/aws/ec2/instances.rb +222 -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 +49 -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 +19 -0
- data/lib/rudy/cli/config.rb +81 -0
- data/lib/rudy/cli/disks.rb +58 -0
- data/lib/rudy/cli/machines.rb +114 -0
- data/lib/rudy/cli/routines.rb +108 -0
- data/lib/rudy/config.rb +116 -0
- data/lib/rudy/config/objects.rb +148 -0
- data/lib/rudy/global.rb +130 -0
- data/lib/rudy/guidelines.rb +18 -0
- data/lib/rudy/huxtable.rb +373 -0
- data/lib/rudy/machines.rb +281 -0
- data/lib/rudy/metadata.rb +51 -0
- data/lib/rudy/metadata/backup.rb +113 -0
- data/lib/rudy/metadata/backups.rb +65 -0
- data/lib/rudy/metadata/disk.rb +177 -0
- data/lib/rudy/metadata/disks.rb +67 -0
- data/lib/rudy/metadata/objectbase.rb +104 -0
- data/lib/rudy/mixins.rb +2 -0
- data/lib/rudy/mixins/hash.rb +25 -0
- data/lib/rudy/routines.rb +318 -0
- data/lib/rudy/routines/helper.rb +55 -0
- data/lib/rudy/routines/helpers/dependshelper.rb +34 -0
- data/lib/rudy/routines/helpers/diskhelper.rb +331 -0
- data/lib/rudy/routines/helpers/scmhelper.rb +39 -0
- data/lib/rudy/routines/helpers/scripthelper.rb +198 -0
- data/lib/rudy/routines/helpers/userhelper.rb +37 -0
- data/lib/rudy/routines/passthrough.rb +38 -0
- data/lib/rudy/routines/reboot.rb +75 -0
- data/lib/rudy/routines/release.rb +50 -0
- data/lib/rudy/routines/shutdown.rb +33 -0
- data/lib/rudy/routines/startup.rb +36 -0
- data/lib/rudy/scm.rb +75 -0
- data/lib/rudy/scm/git.rb +217 -0
- data/lib/rudy/scm/svn.rb +110 -0
- data/lib/rudy/utils.rb +365 -0
- data/rudy.gemspec +151 -0
- data/support/mailtest +40 -0
- data/support/randomize-root-password +45 -0
- data/support/rudy-ec2-startup +200 -0
- data/support/update-ec2-ami-tools +20 -0
- data/test/01_mixins/10_hash_test.rb +25 -0
- data/test/10_config/00_setup_test.rb +20 -0
- data/test/10_config/30_machines_test.rb +69 -0
- data/test/15_scm/00_setup_test.rb +20 -0
- data/test/15_scm/20_git_test.rb +61 -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
- 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')
|
data/lib/rudy/scm/git.rb
ADDED
@@ -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
|
data/lib/rudy/scm/svn.rb
ADDED
@@ -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
|