solutious-rudy 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +8 -9
- data/README.rdoc +48 -7
- data/Rakefile +102 -7
- data/Rudyfile +28 -0
- data/bin/ird +162 -0
- data/bin/rudy +287 -93
- data/lib/annoy.rb +227 -0
- data/lib/aws_sdb/service.rb +1 -1
- data/lib/console.rb +20 -4
- data/lib/escape.rb +305 -0
- data/lib/rudy.rb +265 -125
- data/lib/rudy/aws.rb +61 -26
- data/lib/rudy/aws/ec2.rb +20 -296
- data/lib/rudy/aws/ec2/address.rb +121 -0
- data/lib/rudy/aws/ec2/group.rb +241 -0
- data/lib/rudy/aws/ec2/image.rb +46 -0
- data/lib/rudy/aws/ec2/instance.rb +407 -0
- data/lib/rudy/aws/ec2/keypair.rb +92 -0
- data/lib/rudy/aws/ec2/snapshot.rb +87 -0
- data/lib/rudy/aws/ec2/volume.rb +234 -0
- data/lib/rudy/aws/simpledb.rb +33 -15
- data/lib/rudy/cli.rb +142 -0
- data/lib/rudy/cli/addresses.rb +85 -0
- data/lib/rudy/cli/backups.rb +175 -0
- data/lib/rudy/{command → cli}/config.rb +18 -13
- data/lib/rudy/cli/deploy.rb +12 -0
- data/lib/rudy/cli/disks.rb +125 -0
- data/lib/rudy/cli/domains.rb +17 -0
- data/lib/rudy/cli/groups.rb +77 -0
- data/lib/rudy/{command → cli}/images.rb +18 -6
- data/lib/rudy/cli/instances.rb +142 -0
- data/lib/rudy/cli/keypairs.rb +47 -0
- data/lib/rudy/cli/manager.rb +51 -0
- data/lib/rudy/{command → cli}/release.rb +10 -10
- data/lib/rudy/cli/routines.rb +80 -0
- data/lib/rudy/cli/volumes.rb +121 -0
- data/lib/rudy/command/addresses.rb +62 -39
- data/lib/rudy/command/backups.rb +60 -170
- data/lib/rudy/command/disks-old.rb +322 -0
- data/lib/rudy/command/disks.rb +5 -209
- data/lib/rudy/command/domains.rb +34 -0
- data/lib/rudy/command/groups.rb +105 -48
- data/lib/rudy/command/instances.rb +263 -70
- data/lib/rudy/command/keypairs.rb +149 -0
- data/lib/rudy/command/manager.rb +65 -0
- data/lib/rudy/command/volumes.rb +110 -49
- data/lib/rudy/config.rb +90 -70
- data/lib/rudy/config/objects.rb +67 -0
- data/lib/rudy/huxtable.rb +253 -0
- data/lib/rudy/metadata/backup.rb +23 -48
- data/lib/rudy/metadata/disk.rb +79 -68
- data/lib/rudy/metadata/machine.rb +34 -0
- data/lib/rudy/routines.rb +54 -0
- data/lib/rudy/routines/disk_handler.rb +190 -0
- data/lib/rudy/routines/release.rb +15 -0
- data/lib/rudy/routines/script_runner.rb +65 -0
- data/lib/rudy/routines/shutdown.rb +42 -0
- data/lib/rudy/routines/startup.rb +48 -0
- data/lib/rudy/utils.rb +57 -2
- data/lib/storable.rb +11 -5
- data/lib/sysinfo.rb +274 -0
- data/rudy.gemspec +84 -20
- data/support/randomize-root-password +45 -0
- data/support/rudy-ec2-startup +5 -5
- data/support/update-ec2-ami-tools +20 -0
- data/test/05_config/00_setup_test.rb +24 -0
- data/test/05_config/30_machines_test.rb +69 -0
- data/test/20_sdb/00_setup_test.rb +31 -0
- data/test/20_sdb/10_domains_test.rb +113 -0
- data/test/25_ec2/00_setup_test.rb +34 -0
- data/test/25_ec2/10_keypairs_test.rb +33 -0
- data/test/25_ec2/20_groups_test.rb +139 -0
- data/test/25_ec2/30_addresses_test.rb +35 -0
- data/test/25_ec2/40_volumes_test.rb +46 -0
- data/test/25_ec2/50_snapshots_test.rb +69 -0
- data/test/26_ec2_instances/00_setup_test.rb +33 -0
- data/test/26_ec2_instances/10_instances_test.rb +81 -0
- data/test/26_ec2_instances/50_images_test.rb +13 -0
- data/test/30_sdb_metadata/00_setup_test.rb +28 -0
- data/test/30_sdb_metadata/10_disks_test.rb +99 -0
- data/test/30_sdb_metadata/20_backups_test.rb +102 -0
- data/test/50_commands/00_setup_test.rb +11 -0
- data/test/50_commands/10_keypairs_test.rb +79 -0
- data/test/50_commands/20_groups_test.rb +77 -0
- data/test/50_commands/40_volumes_test.rb +55 -0
- data/test/50_commands/50_instances_test.rb +110 -0
- data/test/coverage.txt +51 -0
- data/test/helper.rb +35 -0
- data/tryouts/disks.rb +55 -0
- data/tryouts/nested_methods.rb +36 -0
- data/tryouts/session_tryout.rb +48 -0
- metadata +94 -25
- data/bin/rudy-ec2 +0 -108
- data/lib/rudy/command/base.rb +0 -839
- data/lib/rudy/command/deploy.rb +0 -12
- data/lib/rudy/command/environment.rb +0 -74
- data/lib/rudy/command/machines.rb +0 -170
- data/lib/rudy/command/metadata.rb +0 -41
- data/lib/rudy/metadata.rb +0 -26
data/lib/aws_sdb/service.rb
CHANGED
data/lib/console.rb
CHANGED
@@ -6,6 +6,11 @@
|
|
6
6
|
|
7
7
|
|
8
8
|
class String
|
9
|
+
@@print_with_attributes = true
|
10
|
+
def String.disable_colour; @@print_with_attributes = false; end
|
11
|
+
def String.disable_color; @@print_with_attributes = false; end
|
12
|
+
def String.enable_colour; @@print_with_attributes = true; end
|
13
|
+
def String.enable_color; @@print_with_attributes = true; end
|
9
14
|
|
10
15
|
# +col+, +bgcol+, and +attribute+ are symbols corresponding
|
11
16
|
# to Console::COLOURS, Console::BGCOLOURS, and Console::ATTRIBUTES.
|
@@ -14,6 +19,7 @@ class String
|
|
14
19
|
# "MONKEY_JUNK".colour(:blue, :white, :blink) # => "\e[34;47;5mMONKEY_JUNK\e[39;49;0m"
|
15
20
|
#
|
16
21
|
def colour(col, bgcol = nil, attribute = nil)
|
22
|
+
return self unless @@print_with_attributes
|
17
23
|
Console.style(col, bgcol, attribute) +
|
18
24
|
self +
|
19
25
|
Console.style(:default, :default, :default)
|
@@ -22,6 +28,7 @@ class String
|
|
22
28
|
|
23
29
|
# See colour
|
24
30
|
def bgcolour(bgcol = :default)
|
31
|
+
return self unless @@print_with_attributes
|
25
32
|
Console.style(nil, bgcol, nil) +
|
26
33
|
self +
|
27
34
|
Console.style(nil, :default, nil)
|
@@ -30,11 +37,16 @@ class String
|
|
30
37
|
|
31
38
|
# See colour
|
32
39
|
def att(a = :default)
|
40
|
+
return self unless @@print_with_attributes
|
33
41
|
Console.style(nil, nil, a) +
|
34
42
|
self +
|
35
43
|
Console.style(nil, nil, :default)
|
36
44
|
end
|
37
45
|
|
46
|
+
# Shortcut for att(:bright)
|
47
|
+
def bright
|
48
|
+
att(:bright)
|
49
|
+
end
|
38
50
|
|
39
51
|
# Print the string at +x+ +y+. When +minus+ is any true value
|
40
52
|
# the length of the string is subtracted from the value of x
|
@@ -58,7 +70,7 @@ class String
|
|
58
70
|
end
|
59
71
|
end
|
60
72
|
|
61
|
-
class Object
|
73
|
+
class Object #:nodoc:all
|
62
74
|
|
63
75
|
# Executes tput +capnam+ with +args+. Returns true if tcap gives
|
64
76
|
# 0 exit status and false otherwise.
|
@@ -82,7 +94,7 @@ end
|
|
82
94
|
|
83
95
|
|
84
96
|
|
85
|
-
module Console
|
97
|
+
module Console #:nodoc:all
|
86
98
|
extend self
|
87
99
|
require 'timeout'
|
88
100
|
require 'thread'
|
@@ -127,6 +139,10 @@ module Console
|
|
127
139
|
:random => 40 + rand(10).to_i
|
128
140
|
}.freeze unless defined? BGCOLOURS
|
129
141
|
|
142
|
+
def valid_colour?(colour)
|
143
|
+
COLOURS.has_key? colour
|
144
|
+
end
|
145
|
+
alias :valid_color? :valid_colour?
|
130
146
|
|
131
147
|
def print_left(str, props={})
|
132
148
|
props[:x] ||= 0
|
@@ -196,7 +212,7 @@ module Console
|
|
196
212
|
end
|
197
213
|
end
|
198
214
|
|
199
|
-
module Cursor
|
215
|
+
module Cursor #:nodoc:all
|
200
216
|
extend self
|
201
217
|
|
202
218
|
# Returns [x,y] for the current cursor position.
|
@@ -291,7 +307,7 @@ module Cursor
|
|
291
307
|
|
292
308
|
end
|
293
309
|
|
294
|
-
class Window
|
310
|
+
class Window #:nodoc:all
|
295
311
|
attr_accessor :row, :col, :width, :height, :text, :fg, :bg
|
296
312
|
attr_reader :threads
|
297
313
|
|
data/lib/escape.rb
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
# escape.rb - escape/unescape library for several formats
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote products
|
14
|
+
# derived from this software without specific prior written permission.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
17
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
18
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
19
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
20
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
21
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
22
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
23
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
|
+
# OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
# Escape module provides several escape functions.
|
28
|
+
# * URI
|
29
|
+
# * HTML
|
30
|
+
# * shell command
|
31
|
+
module Escape #:nodoc:all
|
32
|
+
module_function
|
33
|
+
|
34
|
+
class StringWrapper
|
35
|
+
class << self
|
36
|
+
alias new_no_dup new
|
37
|
+
def new(str)
|
38
|
+
new_no_dup(str.dup)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(str)
|
43
|
+
@str = str
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
@str.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
"\#<#{self.class}: #{@str}>"
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
other.class == self.class && @str == other.instance_variable_get(:@str)
|
56
|
+
end
|
57
|
+
alias eql? ==
|
58
|
+
|
59
|
+
def hash
|
60
|
+
@str.hash
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ShellEscaped < StringWrapper
|
65
|
+
end
|
66
|
+
|
67
|
+
# Escape.shell_command composes
|
68
|
+
# a sequence of words to
|
69
|
+
# a single shell command line.
|
70
|
+
# All shell meta characters are quoted and
|
71
|
+
# the words are concatenated with interleaving space.
|
72
|
+
# It returns an instance of ShellEscaped.
|
73
|
+
#
|
74
|
+
# Escape.shell_command(["ls", "/"]) #=> #<Escape::ShellEscaped: ls />
|
75
|
+
# Escape.shell_command(["echo", "*"]) #=> #<Escape::ShellEscaped: echo '*'>
|
76
|
+
#
|
77
|
+
# Note that system(*command) and
|
78
|
+
# system(Escape.shell_command(command)) is roughly same.
|
79
|
+
# There are two exception as follows.
|
80
|
+
# * The first is that the later may invokes /bin/sh.
|
81
|
+
# * The second is an interpretation of an array with only one element:
|
82
|
+
# the element is parsed by the shell with the former but
|
83
|
+
# it is recognized as single word with the later.
|
84
|
+
# For example, system(*["echo foo"]) invokes echo command with an argument "foo".
|
85
|
+
# But system(Escape.shell_command(["echo foo"])) invokes "echo foo" command without arguments (and it probably fails).
|
86
|
+
def shell_command(*command)
|
87
|
+
command = [command].flatten.compact # Delano
|
88
|
+
s = command.map {|word| shell_single_word(word) }.join(' ')
|
89
|
+
ShellEscaped.new_no_dup(s)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Escape.shell_single_word quotes shell meta characters.
|
93
|
+
# It returns an instance of ShellEscaped.
|
94
|
+
#
|
95
|
+
# The result string is always single shell word, even if
|
96
|
+
# the argument is "".
|
97
|
+
# Escape.shell_single_word("") returns #<Escape::ShellEscaped: ''>.
|
98
|
+
#
|
99
|
+
# Escape.shell_single_word("") #=> #<Escape::ShellEscaped: ''>
|
100
|
+
# Escape.shell_single_word("foo") #=> #<Escape::ShellEscaped: foo>
|
101
|
+
# Escape.shell_single_word("*") #=> #<Escape::ShellEscaped: '*'>
|
102
|
+
def shell_single_word(str)
|
103
|
+
return unless str
|
104
|
+
str &&= str.to_s # Delano fix
|
105
|
+
if str.empty?
|
106
|
+
ShellEscaped.new_no_dup("''")
|
107
|
+
elsif %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
|
108
|
+
ShellEscaped.new(str)
|
109
|
+
else
|
110
|
+
result = ''
|
111
|
+
str.scan(/('+)|[^']+/) {
|
112
|
+
if $1
|
113
|
+
result << %q{\'} * $1.length
|
114
|
+
else
|
115
|
+
result << "'#{$&}'"
|
116
|
+
end
|
117
|
+
}
|
118
|
+
ShellEscaped.new_no_dup(result)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class PercentEncoded < StringWrapper
|
123
|
+
end
|
124
|
+
|
125
|
+
# Escape.uri_segment escapes URI segment using percent-encoding.
|
126
|
+
# It returns an instance of PercentEncoded.
|
127
|
+
#
|
128
|
+
# Escape.uri_segment("a/b") #=> #<Escape::PercentEncoded: a%2Fb>
|
129
|
+
#
|
130
|
+
# The segment is "/"-splitted element after authority before query in URI, as follows.
|
131
|
+
#
|
132
|
+
# scheme://authority/segment1/segment2/.../segmentN?query#fragment
|
133
|
+
#
|
134
|
+
# See RFC 3986 for details of URI.
|
135
|
+
def uri_segment(str)
|
136
|
+
# pchar - pct-encoded = unreserved / sub-delims / ":" / "@"
|
137
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
138
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
139
|
+
s = str.gsub(%r{[^A-Za-z0-9\-._~!$&'()*+,;=:@]}n) {
|
140
|
+
'%' + $&.unpack("H2")[0].upcase
|
141
|
+
}
|
142
|
+
PercentEncoded.new_no_dup(s)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Escape.uri_path escapes URI path using percent-encoding.
|
146
|
+
# The given path should be a sequence of (non-escaped) segments separated by "/".
|
147
|
+
# The segments cannot contains "/".
|
148
|
+
# It returns an instance of PercentEncoded.
|
149
|
+
#
|
150
|
+
# Escape.uri_path("a/b/c") #=> #<Escape::PercentEncoded: a/b/c>
|
151
|
+
# Escape.uri_path("a?b/c?d/e?f") #=> #<Escape::PercentEncoded: a%3Fb/c%3Fd/e%3Ff>
|
152
|
+
#
|
153
|
+
# The path is the part after authority before query in URI, as follows.
|
154
|
+
#
|
155
|
+
# scheme://authority/path#fragment
|
156
|
+
#
|
157
|
+
# See RFC 3986 for details of URI.
|
158
|
+
#
|
159
|
+
# Note that this function is not appropriate to convert OS path to URI.
|
160
|
+
def uri_path(str)
|
161
|
+
s = str.gsub(%r{[^/]+}n) { uri_segment($&) }
|
162
|
+
PercentEncoded.new_no_dup(s)
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def html_form_fast(pairs, sep='&')
|
167
|
+
s = pairs.map {|k, v|
|
168
|
+
# query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
|
169
|
+
# unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
|
170
|
+
# query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
|
171
|
+
# query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
|
172
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
173
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
174
|
+
# x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
|
175
|
+
k = k.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
|
176
|
+
'%' + $&.unpack("H2")[0].upcase
|
177
|
+
}
|
178
|
+
v = v.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
|
179
|
+
'%' + $&.unpack("H2")[0].upcase
|
180
|
+
}
|
181
|
+
"#{k}=#{v}"
|
182
|
+
}.join(sep)
|
183
|
+
PercentEncoded.new_no_dup(s)
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Escape.html_form composes HTML form key-value pairs as a x-www-form-urlencoded encoded string.
|
188
|
+
# It returns an instance of PercentEncoded.
|
189
|
+
#
|
190
|
+
# Escape.html_form takes an array of pair of strings or
|
191
|
+
# an hash from string to string.
|
192
|
+
#
|
193
|
+
# Escape.html_form([["a","b"], ["c","d"]]) #=> #<Escape::PercentEncoded: a=b&c=d>
|
194
|
+
# Escape.html_form({"a"=>"b", "c"=>"d"}) #=> #<Escape::PercentEncoded: a=b&c=d>
|
195
|
+
#
|
196
|
+
# In the array form, it is possible to use same key more than once.
|
197
|
+
# (It is required for a HTML form which contains
|
198
|
+
# checkboxes and select element with multiple attribute.)
|
199
|
+
#
|
200
|
+
# Escape.html_form([["k","1"], ["k","2"]]) #=> #<Escape::PercentEncoded: k=1&k=2>
|
201
|
+
#
|
202
|
+
# If the strings contains characters which must be escaped in x-www-form-urlencoded,
|
203
|
+
# they are escaped using %-encoding.
|
204
|
+
#
|
205
|
+
# Escape.html_form([["k=","&;="]]) #=> #<Escape::PercentEncoded: k%3D=%26%3B%3D>
|
206
|
+
#
|
207
|
+
# The separator can be specified by the optional second argument.
|
208
|
+
#
|
209
|
+
# Escape.html_form([["a","b"], ["c","d"]], ";") #=> #<Escape::PercentEncoded: a=b;c=d>
|
210
|
+
#
|
211
|
+
# See HTML 4.01 for details.
|
212
|
+
def html_form(pairs, sep='&')
|
213
|
+
r = ''
|
214
|
+
first = true
|
215
|
+
pairs.each {|k, v|
|
216
|
+
# query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
|
217
|
+
# unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
|
218
|
+
# query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
|
219
|
+
# query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
|
220
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
221
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
222
|
+
# x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
|
223
|
+
r << sep if !first
|
224
|
+
first = false
|
225
|
+
k.each_byte {|byte|
|
226
|
+
ch = byte.chr
|
227
|
+
if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
|
228
|
+
r << "%" << ch.unpack("H2")[0].upcase
|
229
|
+
else
|
230
|
+
r << ch
|
231
|
+
end
|
232
|
+
}
|
233
|
+
r << '='
|
234
|
+
v.each_byte {|byte|
|
235
|
+
ch = byte.chr
|
236
|
+
if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
|
237
|
+
r << "%" << ch.unpack("H2")[0].upcase
|
238
|
+
else
|
239
|
+
r << ch
|
240
|
+
end
|
241
|
+
}
|
242
|
+
}
|
243
|
+
PercentEncoded.new_no_dup(r)
|
244
|
+
end
|
245
|
+
|
246
|
+
class HTMLEscaped < StringWrapper
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
HTML_TEXT_ESCAPE_HASH = {
|
251
|
+
'&' => '&',
|
252
|
+
'<' => '<',
|
253
|
+
'>' => '>',
|
254
|
+
}
|
255
|
+
|
256
|
+
|
257
|
+
# Escape.html_text escapes a string appropriate for HTML text using character references.
|
258
|
+
# It returns an instance of HTMLEscaped.
|
259
|
+
#
|
260
|
+
# It escapes 3 characters:
|
261
|
+
# * '&' to '&'
|
262
|
+
# * '<' to '<'
|
263
|
+
# * '>' to '>'
|
264
|
+
#
|
265
|
+
# Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
|
266
|
+
# Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a & b < c > d>
|
267
|
+
#
|
268
|
+
# This function is not appropriate for escaping HTML element attribute
|
269
|
+
# because quotes are not escaped.
|
270
|
+
def html_text(str)
|
271
|
+
s = str.gsub(/[&<>]/) {|ch| HTML_TEXT_ESCAPE_HASH[ch] }
|
272
|
+
HTMLEscaped.new_no_dup(s)
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
HTML_ATTR_ESCAPE_HASH = {
|
277
|
+
'&' => '&',
|
278
|
+
'<' => '<',
|
279
|
+
'>' => '>',
|
280
|
+
'"' => '"',
|
281
|
+
}
|
282
|
+
|
283
|
+
|
284
|
+
class HTMLAttrValue < StringWrapper
|
285
|
+
end
|
286
|
+
|
287
|
+
# Escape.html_attr_value encodes a string as a double-quoted HTML attribute using character references.
|
288
|
+
# It returns an instance of HTMLAttrValue.
|
289
|
+
#
|
290
|
+
# Escape.html_attr_value("abc") #=> #<Escape::HTMLAttrValue: "abc">
|
291
|
+
# Escape.html_attr_value("a&b") #=> #<Escape::HTMLAttrValue: "a&b">
|
292
|
+
# Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&<>"c">
|
293
|
+
# Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
|
294
|
+
#
|
295
|
+
# It escapes 4 characters:
|
296
|
+
# * '&' to '&'
|
297
|
+
# * '<' to '<'
|
298
|
+
# * '>' to '>'
|
299
|
+
# * '"' to '"'
|
300
|
+
#
|
301
|
+
def html_attr_value(str)
|
302
|
+
s = '"' + str.gsub(/[&<>"]/) {|ch| HTML_ATTR_ESCAPE_HASH[ch] } + '"'
|
303
|
+
HTMLAttrValue.new_no_dup(s)
|
304
|
+
end
|
305
|
+
end
|
data/lib/rudy.rb
CHANGED
@@ -1,64 +1,123 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
puts "Sorry! We're using the right_aws gem and it doesn't support Ruby 1.9 (md5 error)."
|
6
|
-
exit 1
|
2
|
+
unless defined?(RUDY_HOME)
|
3
|
+
RUDY_HOME = File.join(File.dirname(__FILE__), '..')
|
4
|
+
RUDY_LIB = File.join(File.dirname(__FILE__), '..', 'lib')
|
7
5
|
end
|
8
6
|
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
require 'net/ssh/multi'
|
25
|
-
require 'net/scp'
|
26
|
-
|
27
|
-
rescue LoadError => ex
|
28
|
-
puts "Problem requiring: #{ex.message}"
|
29
|
-
exit 1
|
30
|
-
end
|
8
|
+
require 'digest/md5'
|
9
|
+
require 'stringio'
|
10
|
+
require 'ostruct'
|
11
|
+
require 'yaml'
|
12
|
+
require 'socket'
|
13
|
+
require 'timeout'
|
14
|
+
require 'tempfile'
|
15
|
+
|
16
|
+
require 'storable'
|
17
|
+
require 'console'
|
18
|
+
require 'sysinfo'
|
19
|
+
require 'annoy'
|
20
|
+
|
21
|
+
require 'rye'
|
31
22
|
|
23
|
+
require 'net/ssh'
|
24
|
+
require 'net/scp'
|
25
|
+
require 'net/ssh/multi'
|
26
|
+
require 'net/ssh/gateway'
|
32
27
|
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
29
|
+
|
30
|
+
# = Rudy
|
31
|
+
#
|
32
|
+
# == About
|
33
|
+
#
|
34
|
+
# Rudy is a development and deployment tool for the Amazon Elastic Compute Cloud
|
35
|
+
# (EC2). There are two interfaces: a command-line executable and a Ruby library.
|
36
|
+
# You can use Rudy as a development tool to simply the management of instances,
|
37
|
+
# security groups, etc... on an ad-hoc basic. You can also define complex machine
|
38
|
+
# environments using a simple domain specific language (DSL) and use Rudy to build
|
39
|
+
# and deploy these environments.
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# == Status: Alpha
|
43
|
+
#
|
44
|
+
# The current release (0.5) is not ready for general production use. Use it for
|
45
|
+
# exploring EC2 and operating your development / ad-hoc instances. We've put in
|
46
|
+
# a lot of effort to make sure Rudy plays safe, but it's possible we missed
|
47
|
+
# something. That's why we consider it alpha code.
|
48
|
+
#
|
49
|
+
# To get started right away, try:
|
50
|
+
#
|
51
|
+
# $ rudy -h
|
52
|
+
# $ rudy show-commands
|
53
|
+
#
|
54
|
+
# And if you're feeling particularly saucey, try Rudy's REPL interface:
|
55
|
+
#
|
56
|
+
# $ ird
|
57
|
+
#
|
58
|
+
# == Next Release (0.6): May 2009.
|
59
|
+
#
|
60
|
+
# $ rudy slogan
|
61
|
+
# Rudy: Not your grandparent's deployment tool!
|
62
|
+
#
|
63
|
+
#
|
64
|
+
module Rudy
|
65
|
+
extend self
|
66
|
+
|
67
|
+
unless defined?(RUDY_DOMAIN) # We can assume all constants are defined
|
68
|
+
# SimpleDB accepts dashes in the domain name on creation and with the query syntax.
|
69
|
+
# However, with select syntax it says: "The specified query expression syntax is not valid"
|
70
|
+
RUDY_DOMAIN = "rudy_state".freeze
|
71
|
+
RUDY_DELIM = '-'.freeze
|
37
72
|
|
38
|
-
|
39
|
-
|
73
|
+
RUDY_CONFIG_DIR = File.join(ENV['HOME'] || ENV['USERPROFILE'], '.rudy').freeze
|
74
|
+
RUDY_CONFIG_FILE = File.join(RUDY_CONFIG_DIR, 'config').freeze
|
40
75
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
76
|
+
DEFAULT_REGION = 'us-east-1'.freeze
|
77
|
+
DEFAULT_ZONE = 'us-east-1b'.freeze
|
78
|
+
DEFAULT_ENVIRONMENT = 'stage'.freeze
|
79
|
+
DEFAULT_ROLE = 'app'.freeze
|
80
|
+
DEFAULT_POSITION = '01'.freeze
|
81
|
+
|
82
|
+
DEFAULT_USER = 'rudy'.freeze
|
46
83
|
|
47
|
-
|
84
|
+
SUPPORTED_SCM_NAMES = [:svn, :git].freeze
|
48
85
|
|
49
|
-
|
86
|
+
ID_MAP = {
|
87
|
+
:instance => 'i',
|
88
|
+
:disk => 'disk',
|
89
|
+
:backup => 'back',
|
90
|
+
:volume => 'vol',
|
91
|
+
:snapshot => 'snap',
|
92
|
+
:kernel => 'aki',
|
93
|
+
:image => 'ami',
|
94
|
+
:ram => 'ari',
|
95
|
+
:log => 'log',
|
96
|
+
:key => 'key',
|
97
|
+
:pk => 'pk',
|
98
|
+
:cert => 'cert',
|
99
|
+
:reservation => 'r'
|
100
|
+
}.freeze
|
101
|
+
|
102
|
+
@@sysinfo = SystemInfo.new.freeze
|
103
|
+
end
|
50
104
|
|
51
105
|
module VERSION #:nodoc:
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
[MAJOR, MINOR, TINY].join('.')
|
57
|
-
end
|
58
|
-
def self.to_f
|
59
|
-
self.to_s.to_f
|
106
|
+
unless defined?(MAJOR)
|
107
|
+
MAJOR = 0.freeze
|
108
|
+
MINOR = 5.freeze
|
109
|
+
TINY = 0.freeze
|
60
110
|
end
|
111
|
+
def self.to_s; [MAJOR, MINOR, TINY].join('.'); end
|
112
|
+
def self.to_f; self.to_s.to_f; end
|
61
113
|
end
|
114
|
+
|
115
|
+
@@quiet = false
|
116
|
+
def Rudy.enable_quiet; @@quiet = true; end
|
117
|
+
def Rudy.disable_quiet; @@quiet = false; end
|
118
|
+
|
119
|
+
def Rudy.sysinfo; @@sysinfo; end
|
120
|
+
def sysinfo; @@sysinfo; end
|
62
121
|
|
63
122
|
# Determine if we're running directly on EC2 or
|
64
123
|
# "some other machine". We do this by checking if
|
@@ -66,21 +125,170 @@ module Rudy #:nodoc:
|
|
66
125
|
# file is written by /etc/init.d/rudy-ec2-startup.
|
67
126
|
# NOTE: Is there a way to know definitively that this is EC2?
|
68
127
|
# We could make a request to the metadata IP addresses.
|
69
|
-
def
|
128
|
+
def Rudy.in_situ?
|
70
129
|
File.exists?('/etc/ec2/instance-id')
|
71
130
|
end
|
131
|
+
|
132
|
+
|
133
|
+
# Wait for something to happen.
|
134
|
+
# * +duration+ seconds to wait between tries (default: 2).
|
135
|
+
# * +max+ maximum time to wait (default: 120). Throws an exception when exceeded.
|
136
|
+
# * +logger+ IO object to print +dot+ to.
|
137
|
+
# * +dot+ the character to print after each attempt (default: .).
|
138
|
+
# Set to nil or false to keep the waiter silent.
|
139
|
+
# The block must return false while waiting. Once it returns true
|
140
|
+
# the waiter will return true too.
|
141
|
+
def Rudy.waiter(duration=2, max=120, logger=STDOUT, dot='.', &check)
|
142
|
+
# TODO: Move to Drydock
|
143
|
+
raise "The waiter needs a block!" unless check
|
144
|
+
duration = 1 if duration < 1
|
145
|
+
max = duration*2 if max < duration
|
146
|
+
success = false
|
147
|
+
begin
|
148
|
+
success = Timeout::timeout(max) do
|
149
|
+
while !check.call
|
150
|
+
logger.print dot if dot && logger.respond_to?(:print)
|
151
|
+
logger.flush if logger.respond_to?(:flush)
|
152
|
+
sleep duration
|
153
|
+
end
|
154
|
+
end
|
155
|
+
rescue Timeout::Error => ex
|
156
|
+
retry if Annoy.pose_question(" Keep waiting?\a ", /yes|y|ya|sure|you bet!/i, logger)
|
157
|
+
raise ex # We won't get here unless the question fails
|
158
|
+
end
|
159
|
+
success
|
160
|
+
end
|
161
|
+
|
162
|
+
# Make a terminal bell chime
|
163
|
+
def Rudy.bell(chimes=1, logger=nil)
|
164
|
+
return if @@quiet
|
165
|
+
chimed = chimes.to_i
|
166
|
+
logger.print "\a"*chimes if logger
|
167
|
+
true # be like Rudy.bug()
|
168
|
+
end
|
169
|
+
|
170
|
+
# Have you seen that episode of The Cosby Show where Dizzy Gillespie... ah nevermind.
|
171
|
+
def Rudy.bug(bugid, logger=STDERR)
|
172
|
+
logger.puts "You have found a bug! If you want, you can email".color(:red)
|
173
|
+
logger.puts 'rudy@solutious.com'.color(:red).bright << " about it. It's bug ##{bugid}.".color(:red)
|
174
|
+
logger.puts "Continuing...".color(:red)
|
175
|
+
true # so we can string it together like: bug('1') && next if ...
|
176
|
+
end
|
177
|
+
|
178
|
+
# Is the given string +str+ an ID of type +identifier+?
|
179
|
+
# * +identifier+ is expected to be a key from ID_MAP
|
180
|
+
# * +str+ is a string you're investigating
|
181
|
+
def Rudy.is_id?(identifier, str)
|
182
|
+
return false unless identifier && str && Rudy.known_type?(identifier)
|
183
|
+
identifier &&= identifier.to_sym
|
184
|
+
str &&= str.to_s.strip
|
185
|
+
str.split('-').first == Rudy::ID_MAP[identifier].to_s
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns the object type associated to +str+ or nil if unknown.
|
189
|
+
# * +str+ is a string you're investigating
|
190
|
+
def Rudy.id_type(str)
|
191
|
+
return false unless str
|
192
|
+
str &&= str.to_s.strip
|
193
|
+
(Rudy::ID_MAP.detect { |n,v| v == str.split('-').first } || []).first
|
194
|
+
end
|
195
|
+
|
196
|
+
# Is the given +key+ a known type of object?
|
197
|
+
def Rudy.known_type?(key)
|
198
|
+
return false unless key
|
199
|
+
key &&= key.to_s.to_sym
|
200
|
+
Rudy::ID_MAP.has_key?(key)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns the string identifier associated to this +key+
|
204
|
+
def Rudy.identifier(key)
|
205
|
+
key &&= key.to_sym
|
206
|
+
return unless Rudy::ID_MAP.has_key?(key)
|
207
|
+
Rudy::ID_MAP[key]
|
208
|
+
end
|
209
|
+
|
210
|
+
# Return a string ID without the identifier. i.e. key-stage-app-root => stage-app-root
|
211
|
+
def Rudy.strip_identifier(str)
|
212
|
+
el = str.split('-')
|
213
|
+
el.shift
|
214
|
+
el.join('-')
|
215
|
+
end
|
216
|
+
|
217
|
+
# +msg+ The message to return as a banner
|
218
|
+
# +size+ One of: :normal (default), :huge
|
219
|
+
# +colour+ a valid
|
220
|
+
# Returns a string with styling applying
|
221
|
+
def Rudy.banner(msg, size = :normal, colour = :black)
|
222
|
+
return unless msg
|
223
|
+
banners = {
|
224
|
+
:huge => Rudy::Utils.without_indent(%Q(
|
225
|
+
=======================================================
|
226
|
+
=======================================================
|
227
|
+
!!!!!!!!! %s !!!!!!!!!
|
228
|
+
=======================================================
|
229
|
+
=======================================================)),
|
230
|
+
:normal => %Q(============ %s ============)
|
231
|
+
}
|
232
|
+
size = :normal unless banners.has_key?(size)
|
233
|
+
colour = :black unless Console.valid_colour?(colour)
|
234
|
+
size, colour = size.to_sym, colour.to_sym
|
235
|
+
sprintf(banners[size], msg).colour(colour).bgcolour(:white).bright
|
236
|
+
end
|
237
|
+
|
238
|
+
# Run a block and trap common, known errors.
|
239
|
+
# * +default+ A default response value
|
240
|
+
# * +request+ A block which contains the AWS request
|
241
|
+
# Returns the return value from the request is returned untouched
|
242
|
+
# or the default value on error or if the request returned nil.
|
243
|
+
def trap_known_errors(default=nil, &request)
|
244
|
+
raise "No block provided" unless request
|
245
|
+
response = nil
|
246
|
+
begin
|
247
|
+
response = request.call
|
248
|
+
rescue => ex
|
249
|
+
STDERR.puts ex.message
|
250
|
+
ensure
|
251
|
+
response ||= default
|
252
|
+
end
|
253
|
+
response
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
class NoConfig < RuntimeError
|
258
|
+
def message
|
259
|
+
"No AWS credentials. Check your configs!"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
class NoMachineImage < RuntimeError
|
263
|
+
def initialize(group)
|
264
|
+
@group = group
|
265
|
+
end
|
266
|
+
def message
|
267
|
+
"There is no AMI configured for #{@group}"
|
268
|
+
end
|
269
|
+
end
|
72
270
|
end
|
73
271
|
|
74
272
|
require 'rudy/aws'
|
75
|
-
require 'rudy/
|
76
|
-
require 'rudy/
|
77
|
-
require 'rudy/
|
78
|
-
require 'rudy/command/
|
273
|
+
require 'rudy/utils' # The
|
274
|
+
require 'rudy/config' # order
|
275
|
+
require 'rudy/huxtable' # of
|
276
|
+
require 'rudy/command/addresses' # require
|
277
|
+
require 'rudy/command/keypairs'
|
278
|
+
require 'rudy/command/instances' # statements
|
279
|
+
require 'rudy/command/manager' # is
|
280
|
+
require 'rudy/command/domains'
|
281
|
+
require 'rudy/command/backups' # important.
|
282
|
+
require 'rudy/command/volumes'
|
283
|
+
require 'rudy/command/groups'
|
284
|
+
require 'rudy/command/disks'
|
285
|
+
require 'rudy/routines'
|
286
|
+
|
79
287
|
|
80
|
-
# Require
|
288
|
+
# Require MetaData, Routines, and SCM classes
|
81
289
|
begin
|
82
290
|
# TODO: Use autoload
|
83
|
-
Dir.glob(File.join(RUDY_LIB, 'rudy', '{
|
291
|
+
Dir.glob(File.join(RUDY_LIB, 'rudy', '{metadata,routines,scm}', "*.rb")).each do |path|
|
84
292
|
require path
|
85
293
|
end
|
86
294
|
rescue LoadError => ex
|
@@ -89,68 +297,10 @@ rescue LoadError => ex
|
|
89
297
|
end
|
90
298
|
|
91
299
|
|
92
|
-
# Capture STDOUT or STDERR to prevent it from being printed.
|
93
|
-
#
|
94
|
-
# capture(:stdout) do
|
95
|
-
# ...
|
96
|
-
# end
|
97
|
-
#
|
98
|
-
def capture(stream)
|
99
|
-
#raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
|
100
|
-
|
101
|
-
# I'm using this to trap the annoying right_aws "peer certificate" warning.
|
102
|
-
# TODO: discover source of annoying right_aws warning and give it a hiding.
|
103
|
-
begin
|
104
|
-
stream = stream.to_s
|
105
|
-
eval "$#{stream} = StringIO.new"
|
106
|
-
yield
|
107
|
-
result = eval("$#{stream}").read
|
108
|
-
ensure
|
109
|
-
eval("$#{stream} = #{stream.upcase}")
|
110
|
-
end
|
111
|
-
|
112
|
-
result
|
113
|
-
end
|
114
|
-
|
115
300
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
f.puts content
|
120
|
-
f.close
|
121
|
-
end
|
122
|
-
|
123
|
-
def are_you_sure?(len=3)
|
124
|
-
if Drydock.debug?
|
125
|
-
puts 'DEBUG: skipping "are you sure" check'
|
126
|
-
return true
|
127
|
-
end
|
128
|
-
|
129
|
-
if STDIN.tty? # Only ask a question if there's a human
|
130
|
-
challenge = strand len
|
131
|
-
STDOUT.print "Are you sure? To continue type \"#{challenge}\": "
|
132
|
-
STDOUT.flush
|
133
|
-
if ((STDIN.gets || "").gsub(/["']/, '') =~ /^#{challenge}$/)
|
134
|
-
true
|
135
|
-
else
|
136
|
-
puts "Nothing changed"
|
137
|
-
exit 0
|
138
|
-
end
|
139
|
-
else
|
140
|
-
true
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
#
|
145
|
-
# Generates a string of random alphanumeric characters
|
146
|
-
# These are used as IDs throughout the system
|
147
|
-
def strand( len=8, safe=true )
|
148
|
-
chars = ("a".."z").to_a + ("0".."9").to_a
|
149
|
-
chars = [("a".."h").to_a, "j", "k", "m", "n", ("p".."z").to_a, ("2".."9").to_a].flatten if safe
|
150
|
-
newpass = ""
|
151
|
-
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
|
152
|
-
newpass
|
153
|
-
end
|
301
|
+
# ---
|
302
|
+
# TODO: Find a home for these poor guys:
|
303
|
+
# +++
|
154
304
|
|
155
305
|
def sh(command, chdir=false, verbose=false)
|
156
306
|
prevdir = Dir.pwd
|
@@ -160,6 +310,7 @@ def sh(command, chdir=false, verbose=false)
|
|
160
310
|
Dir.chdir prevdir if chdir
|
161
311
|
end
|
162
312
|
|
313
|
+
|
163
314
|
def ssh_command(host, keypair, user, command=false, printonly=false, verbose=false)
|
164
315
|
#puts "CONNECTING TO #{host}..."
|
165
316
|
cmd = "ssh -i #{keypair} #{user}@#{host} "
|
@@ -197,14 +348,3 @@ def scp_command(host, keypair, user, paths, to_path, to_local=false, verbose=fal
|
|
197
348
|
printonly ? (puts cmd) : system(cmd)
|
198
349
|
end
|
199
350
|
|
200
|
-
|
201
|
-
# Returns +str+ with the average leading indentation removed.
|
202
|
-
# Useful for keeping inline codeblocks spaced with code.
|
203
|
-
def without_indent(str)
|
204
|
-
lines = str.split($/)
|
205
|
-
lspaces = (lines.inject(0) {|total,line| total += (line.scan(/^\s+/).first || '').size } / lines.size) + 1
|
206
|
-
lines.collect { |line| line.gsub(/^\s{#{lspaces}}/, '') }.join($/)
|
207
|
-
end
|
208
|
-
|
209
|
-
|
210
|
-
|