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.
Files changed (99) hide show
  1. data/CHANGES.txt +8 -9
  2. data/README.rdoc +48 -7
  3. data/Rakefile +102 -7
  4. data/Rudyfile +28 -0
  5. data/bin/ird +162 -0
  6. data/bin/rudy +287 -93
  7. data/lib/annoy.rb +227 -0
  8. data/lib/aws_sdb/service.rb +1 -1
  9. data/lib/console.rb +20 -4
  10. data/lib/escape.rb +305 -0
  11. data/lib/rudy.rb +265 -125
  12. data/lib/rudy/aws.rb +61 -26
  13. data/lib/rudy/aws/ec2.rb +20 -296
  14. data/lib/rudy/aws/ec2/address.rb +121 -0
  15. data/lib/rudy/aws/ec2/group.rb +241 -0
  16. data/lib/rudy/aws/ec2/image.rb +46 -0
  17. data/lib/rudy/aws/ec2/instance.rb +407 -0
  18. data/lib/rudy/aws/ec2/keypair.rb +92 -0
  19. data/lib/rudy/aws/ec2/snapshot.rb +87 -0
  20. data/lib/rudy/aws/ec2/volume.rb +234 -0
  21. data/lib/rudy/aws/simpledb.rb +33 -15
  22. data/lib/rudy/cli.rb +142 -0
  23. data/lib/rudy/cli/addresses.rb +85 -0
  24. data/lib/rudy/cli/backups.rb +175 -0
  25. data/lib/rudy/{command → cli}/config.rb +18 -13
  26. data/lib/rudy/cli/deploy.rb +12 -0
  27. data/lib/rudy/cli/disks.rb +125 -0
  28. data/lib/rudy/cli/domains.rb +17 -0
  29. data/lib/rudy/cli/groups.rb +77 -0
  30. data/lib/rudy/{command → cli}/images.rb +18 -6
  31. data/lib/rudy/cli/instances.rb +142 -0
  32. data/lib/rudy/cli/keypairs.rb +47 -0
  33. data/lib/rudy/cli/manager.rb +51 -0
  34. data/lib/rudy/{command → cli}/release.rb +10 -10
  35. data/lib/rudy/cli/routines.rb +80 -0
  36. data/lib/rudy/cli/volumes.rb +121 -0
  37. data/lib/rudy/command/addresses.rb +62 -39
  38. data/lib/rudy/command/backups.rb +60 -170
  39. data/lib/rudy/command/disks-old.rb +322 -0
  40. data/lib/rudy/command/disks.rb +5 -209
  41. data/lib/rudy/command/domains.rb +34 -0
  42. data/lib/rudy/command/groups.rb +105 -48
  43. data/lib/rudy/command/instances.rb +263 -70
  44. data/lib/rudy/command/keypairs.rb +149 -0
  45. data/lib/rudy/command/manager.rb +65 -0
  46. data/lib/rudy/command/volumes.rb +110 -49
  47. data/lib/rudy/config.rb +90 -70
  48. data/lib/rudy/config/objects.rb +67 -0
  49. data/lib/rudy/huxtable.rb +253 -0
  50. data/lib/rudy/metadata/backup.rb +23 -48
  51. data/lib/rudy/metadata/disk.rb +79 -68
  52. data/lib/rudy/metadata/machine.rb +34 -0
  53. data/lib/rudy/routines.rb +54 -0
  54. data/lib/rudy/routines/disk_handler.rb +190 -0
  55. data/lib/rudy/routines/release.rb +15 -0
  56. data/lib/rudy/routines/script_runner.rb +65 -0
  57. data/lib/rudy/routines/shutdown.rb +42 -0
  58. data/lib/rudy/routines/startup.rb +48 -0
  59. data/lib/rudy/utils.rb +57 -2
  60. data/lib/storable.rb +11 -5
  61. data/lib/sysinfo.rb +274 -0
  62. data/rudy.gemspec +84 -20
  63. data/support/randomize-root-password +45 -0
  64. data/support/rudy-ec2-startup +5 -5
  65. data/support/update-ec2-ami-tools +20 -0
  66. data/test/05_config/00_setup_test.rb +24 -0
  67. data/test/05_config/30_machines_test.rb +69 -0
  68. data/test/20_sdb/00_setup_test.rb +31 -0
  69. data/test/20_sdb/10_domains_test.rb +113 -0
  70. data/test/25_ec2/00_setup_test.rb +34 -0
  71. data/test/25_ec2/10_keypairs_test.rb +33 -0
  72. data/test/25_ec2/20_groups_test.rb +139 -0
  73. data/test/25_ec2/30_addresses_test.rb +35 -0
  74. data/test/25_ec2/40_volumes_test.rb +46 -0
  75. data/test/25_ec2/50_snapshots_test.rb +69 -0
  76. data/test/26_ec2_instances/00_setup_test.rb +33 -0
  77. data/test/26_ec2_instances/10_instances_test.rb +81 -0
  78. data/test/26_ec2_instances/50_images_test.rb +13 -0
  79. data/test/30_sdb_metadata/00_setup_test.rb +28 -0
  80. data/test/30_sdb_metadata/10_disks_test.rb +99 -0
  81. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  82. data/test/50_commands/00_setup_test.rb +11 -0
  83. data/test/50_commands/10_keypairs_test.rb +79 -0
  84. data/test/50_commands/20_groups_test.rb +77 -0
  85. data/test/50_commands/40_volumes_test.rb +55 -0
  86. data/test/50_commands/50_instances_test.rb +110 -0
  87. data/test/coverage.txt +51 -0
  88. data/test/helper.rb +35 -0
  89. data/tryouts/disks.rb +55 -0
  90. data/tryouts/nested_methods.rb +36 -0
  91. data/tryouts/session_tryout.rb +48 -0
  92. metadata +94 -25
  93. data/bin/rudy-ec2 +0 -108
  94. data/lib/rudy/command/base.rb +0 -839
  95. data/lib/rudy/command/deploy.rb +0 -12
  96. data/lib/rudy/command/environment.rb +0 -74
  97. data/lib/rudy/command/machines.rb +0 -170
  98. data/lib/rudy/command/metadata.rb +0 -41
  99. data/lib/rudy/metadata.rb +0 -26
@@ -8,7 +8,7 @@ require 'openssl'
8
8
  require 'rexml/document'
9
9
  require 'rexml/xpath'
10
10
 
11
- module AwsSdb
11
+ module AwsSdb #:nodoc:all
12
12
 
13
13
  class Service
14
14
  def initialize(options={})
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
+ '&' => '&amp;',
252
+ '<' => '&lt;',
253
+ '>' => '&gt;',
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 '&amp;'
262
+ # * '<' to '&lt;'
263
+ # * '>' to '&gt;'
264
+ #
265
+ # Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
266
+ # Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a &amp; b &lt; c &gt; 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
+ '&' => '&amp;',
278
+ '<' => '&lt;',
279
+ '>' => '&gt;',
280
+ '"' => '&quot;',
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&amp;b">
292
+ # Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&amp;&lt;&gt;&quot;c">
293
+ # Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
294
+ #
295
+ # It escapes 4 characters:
296
+ # * '&' to '&amp;'
297
+ # * '<' to '&lt;'
298
+ # * '>' to '&gt;'
299
+ # * '"' to '&quot;'
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
- # No Ruby 1.9.1 support. Only 1.8.x for now :[
4
- unless RUBY_VERSION < "1.9"
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
- begin
11
- require 'digest/md5'
12
- require 'right_aws'
13
- require 'stringio'
14
- require 'ostruct'
15
- require 'yaml'
16
- require 'socket'
17
- require 'tempfile'
18
-
19
- require 'console'
20
- require 'storable'
21
-
22
- require 'net/ssh'
23
- require 'net/ssh/gateway'
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
- module Rudy #:nodoc:
35
- RUDY_DOMAIN = "rudy_state"
36
- RUDY_DELIM = '-'
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
- RUDY_CONFIG_DIR = File.join(ENV['HOME'] || ENV['USERPROFILE'], '.rudy')
39
- RUDY_CONFIG_FILE = File.join(RUDY_CONFIG_DIR, 'config')
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
- DEFAULT_REGION = 'us-east-1'
42
- DEFAULT_ZONE = 'us-east-1b'
43
- DEFAULT_ENVIRONMENT = 'stage'
44
- DEFAULT_ROLE = 'app'
45
- DEFAULT_POSITION = '01'
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
- DEFAULT_USER = 'rudy'
84
+ SUPPORTED_SCM_NAMES = [:svn, :git].freeze
48
85
 
49
- SUPPORTED_SCM_NAMES = [:svn, :git]
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
- MAJOR = 0.freeze unless defined? MAJOR
53
- MINOR = 4.freeze unless defined? MINOR
54
- TINY = 0.freeze unless defined? TINY
55
- def self.to_s
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 self.in_situ?
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/config'
76
- require 'rudy/metadata'
77
- require 'rudy/utils'
78
- require 'rudy/command/base'
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 Command, MetaData, and SCM classes
288
+ # Require MetaData, Routines, and SCM classes
81
289
  begin
82
290
  # TODO: Use autoload
83
- Dir.glob(File.join(RUDY_LIB, 'rudy', '{command,metadata,scm}', "*.rb")).each do |path|
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
- def write_to_file(filename, content, type)
117
- type = (type == :append) ? 'a' : 'w'
118
- f = File.open(filename,type)
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
-