rudy 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. data/CHANGES.txt +54 -30
  2. data/README.rdoc +100 -12
  3. data/Rakefile +103 -8
  4. data/Rudyfile +119 -0
  5. data/bin/ird +175 -0
  6. data/bin/rudy +259 -156
  7. data/bin/rudy-ec2 +228 -95
  8. data/bin/rudy-s3 +76 -0
  9. data/bin/rudy-sdb +67 -0
  10. data/lib/annoy.rb +270 -0
  11. data/lib/console.rb +30 -9
  12. data/lib/escape.rb +305 -0
  13. data/lib/rudy.rb +151 -182
  14. data/lib/rudy/aws.rb +56 -49
  15. data/lib/rudy/aws/ec2.rb +47 -292
  16. data/lib/rudy/aws/ec2/address.rb +157 -0
  17. data/lib/rudy/aws/ec2/group.rb +301 -0
  18. data/lib/rudy/aws/ec2/image.rb +168 -0
  19. data/lib/rudy/aws/ec2/instance.rb +434 -0
  20. data/lib/rudy/aws/ec2/keypair.rb +104 -0
  21. data/lib/rudy/aws/ec2/snapshot.rb +98 -0
  22. data/lib/rudy/aws/ec2/volume.rb +230 -0
  23. data/lib/rudy/aws/ec2/zone.rb +77 -0
  24. data/lib/rudy/aws/s3.rb +54 -0
  25. data/lib/rudy/aws/sdb.rb +298 -0
  26. data/lib/rudy/aws/sdb/error.rb +46 -0
  27. data/lib/rudy/{metadata/backup.rb → backup.rb} +26 -51
  28. data/lib/rudy/cli.rb +157 -0
  29. data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
  30. data/lib/rudy/cli/aws/ec2/candy.rb +208 -0
  31. data/lib/rudy/cli/aws/ec2/groups.rb +121 -0
  32. data/lib/rudy/cli/aws/ec2/images.rb +196 -0
  33. data/lib/rudy/cli/aws/ec2/instances.rb +194 -0
  34. data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
  35. data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
  36. data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
  37. data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
  38. data/lib/rudy/cli/aws/s3/buckets.rb +50 -0
  39. data/lib/rudy/cli/aws/s3/store.rb +22 -0
  40. data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
  41. data/lib/rudy/cli/candy.rb +8 -0
  42. data/lib/rudy/{command → cli}/config.rb +34 -24
  43. data/lib/rudy/cli/disks.rb +35 -0
  44. data/lib/rudy/cli/machines.rb +94 -0
  45. data/lib/rudy/cli/routines.rb +57 -0
  46. data/lib/rudy/config.rb +77 -72
  47. data/lib/rudy/config/objects.rb +29 -0
  48. data/lib/rudy/disks.rb +248 -0
  49. data/lib/rudy/global.rb +121 -0
  50. data/lib/rudy/huxtable.rb +340 -0
  51. data/lib/rudy/machines.rb +245 -0
  52. data/lib/rudy/metadata.rb +123 -13
  53. data/lib/rudy/routines.rb +47 -0
  54. data/lib/rudy/routines/helpers/diskhelper.rb +101 -0
  55. data/lib/rudy/routines/helpers/scripthelper.rb +91 -0
  56. data/lib/rudy/routines/release.rb +34 -0
  57. data/lib/rudy/routines/shutdown.rb +57 -0
  58. data/lib/rudy/routines/startup.rb +58 -0
  59. data/lib/rudy/scm/svn.rb +1 -1
  60. data/lib/rudy/utils.rb +322 -4
  61. data/lib/storable.rb +26 -17
  62. data/lib/sysinfo.rb +274 -0
  63. data/lib/tryouts.rb +6 -13
  64. data/rudy.gemspec +128 -42
  65. data/support/randomize-root-password +45 -0
  66. data/support/rudy-ec2-startup +9 -9
  67. data/support/update-ec2-ami-tools +20 -0
  68. data/test/05_config/00_setup_test.rb +20 -0
  69. data/test/05_config/30_machines_test.rb +69 -0
  70. data/test/20_sdb/00_setup_test.rb +16 -0
  71. data/test/20_sdb/10_domains_test.rb +115 -0
  72. data/test/25_ec2/00_setup_test.rb +29 -0
  73. data/test/25_ec2/10_keypairs_test.rb +41 -0
  74. data/test/25_ec2/20_groups_test.rb +131 -0
  75. data/test/25_ec2/30_addresses_test.rb +38 -0
  76. data/test/25_ec2/40_volumes_test.rb +49 -0
  77. data/test/25_ec2/50_snapshots_test.rb +74 -0
  78. data/test/26_ec2_instances/00_setup_test.rb +28 -0
  79. data/test/26_ec2_instances/10_instances_test.rb +83 -0
  80. data/test/26_ec2_instances/50_images_test.rb +13 -0
  81. data/test/30_sdb_metadata/00_setup_test.rb +21 -0
  82. data/test/30_sdb_metadata/10_disks_test.rb +109 -0
  83. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  84. data/test/coverage.txt +51 -0
  85. data/test/helper.rb +36 -0
  86. data/vendor/highline-1.5.1/CHANGELOG +222 -0
  87. data/vendor/highline-1.5.1/INSTALL +35 -0
  88. data/vendor/highline-1.5.1/LICENSE +7 -0
  89. data/vendor/highline-1.5.1/README +63 -0
  90. data/vendor/highline-1.5.1/Rakefile +82 -0
  91. data/vendor/highline-1.5.1/TODO +6 -0
  92. data/vendor/highline-1.5.1/examples/ansi_colors.rb +38 -0
  93. data/vendor/highline-1.5.1/examples/asking_for_arrays.rb +18 -0
  94. data/vendor/highline-1.5.1/examples/basic_usage.rb +75 -0
  95. data/vendor/highline-1.5.1/examples/color_scheme.rb +32 -0
  96. data/vendor/highline-1.5.1/examples/limit.rb +12 -0
  97. data/vendor/highline-1.5.1/examples/menus.rb +65 -0
  98. data/vendor/highline-1.5.1/examples/overwrite.rb +19 -0
  99. data/vendor/highline-1.5.1/examples/page_and_wrap.rb +322 -0
  100. data/vendor/highline-1.5.1/examples/password.rb +7 -0
  101. data/vendor/highline-1.5.1/examples/trapping_eof.rb +22 -0
  102. data/vendor/highline-1.5.1/examples/using_readline.rb +17 -0
  103. data/vendor/highline-1.5.1/lib/highline.rb +758 -0
  104. data/vendor/highline-1.5.1/lib/highline/color_scheme.rb +120 -0
  105. data/vendor/highline-1.5.1/lib/highline/compatibility.rb +17 -0
  106. data/vendor/highline-1.5.1/lib/highline/import.rb +43 -0
  107. data/vendor/highline-1.5.1/lib/highline/menu.rb +395 -0
  108. data/vendor/highline-1.5.1/lib/highline/question.rb +463 -0
  109. data/vendor/highline-1.5.1/lib/highline/system_extensions.rb +193 -0
  110. data/vendor/highline-1.5.1/setup.rb +1360 -0
  111. data/vendor/highline-1.5.1/test/tc_color_scheme.rb +56 -0
  112. data/vendor/highline-1.5.1/test/tc_highline.rb +823 -0
  113. data/vendor/highline-1.5.1/test/tc_import.rb +54 -0
  114. data/vendor/highline-1.5.1/test/tc_menu.rb +429 -0
  115. data/vendor/highline-1.5.1/test/ts_all.rb +15 -0
  116. metadata +141 -38
  117. data/lib/aws_sdb.rb +0 -3
  118. data/lib/aws_sdb/error.rb +0 -42
  119. data/lib/aws_sdb/service.rb +0 -215
  120. data/lib/rudy/aws/simpledb.rb +0 -53
  121. data/lib/rudy/command/addresses.rb +0 -46
  122. data/lib/rudy/command/backups.rb +0 -175
  123. data/lib/rudy/command/base.rb +0 -841
  124. data/lib/rudy/command/deploy.rb +0 -12
  125. data/lib/rudy/command/disks.rb +0 -213
  126. data/lib/rudy/command/environment.rb +0 -73
  127. data/lib/rudy/command/groups.rb +0 -61
  128. data/lib/rudy/command/images.rb +0 -91
  129. data/lib/rudy/command/instances.rb +0 -85
  130. data/lib/rudy/command/machines.rb +0 -161
  131. data/lib/rudy/command/metadata.rb +0 -41
  132. data/lib/rudy/command/release.rb +0 -174
  133. data/lib/rudy/command/volumes.rb +0 -66
  134. data/lib/rudy/metadata/disk.rb +0 -138
  135. data/tryouts/console_tryout.rb +0 -91
@@ -0,0 +1,434 @@
1
+
2
+
3
+
4
+ module Rudy::AWS
5
+ class EC2::Instance < Storable
6
+ @@sformat = " -> %10s; %10s; %12s; %10s; groups: %s"
7
+ field :aki
8
+ field :ari
9
+ field :launch_index => Time
10
+ field :launch_time
11
+ field :keyname
12
+ field :size
13
+ field :ami
14
+ field :dns_private
15
+ field :dns_public
16
+ field :awsid
17
+ field :state
18
+ field :zone
19
+ field :reason
20
+ field :groups => Array
21
+
22
+ # Groups aren't returned when creating an instance so this
23
+ # method returns an empty Array if +@groups+ is not set.
24
+ def groups
25
+ @groups || []
26
+ end
27
+
28
+ def liner_note
29
+ info = self.running? ? self.dns_public : self.state
30
+ "%s %s" % [self.awsid.bright, info]
31
+ end
32
+
33
+ def to_s(with_title=false)
34
+ lines = []
35
+ lines << "%s (%s)" % [liner_note, @groups.join(', ')]
36
+ #if self.running?
37
+ # k, g = @keyname || 'no-keypair', self.groups.join(', ')
38
+ # lines << @@sformat % %w{zone size ami keyname groups} if with_title
39
+ # lines << @@sformat % [@zone, @size, @ami, k, g]
40
+ #end
41
+ lines.join($/)
42
+ end
43
+
44
+ def inspect
45
+ lines = []
46
+ lines << liner_note
47
+ field_names.each do |key|
48
+ next unless self.respond_to?(key)
49
+ val = self.send(key)
50
+ lines << sprintf(" %22s: %s", key, (val.is_a?(Array) ? val.join(', ') : val))
51
+ end
52
+ lines.join($/)
53
+ end
54
+
55
+ def running?; self.state && self.state == 'running'; end
56
+ def pending?; self.state && self.state == 'pending'; end
57
+ def terminated?; self.state && self.state == 'terminated'; end
58
+ def degraded?; self.state && self.state == 'degraded'; end
59
+ def shutting_down?; self.state && self.state == 'shutting-down'; end
60
+
61
+ end
62
+
63
+
64
+ module EC2
65
+ class Instances
66
+ include Rudy::AWS::ObjectBase
67
+ include Rudy::AWS::EC2::Base
68
+
69
+ unless defined?(KNOWN_STATES)
70
+ KNOWN_STATES = [:running, :pending, :shutting_down, :terminated, :degraded].freeze
71
+ end
72
+
73
+ # Return an Array of Instance objects. Note: These objects will not have
74
+ # DNS data because they will still be in pending state. The DNS info becomes
75
+ # available once the instance enters the running state.
76
+ #
77
+ # +opts+ supports the following parameters:
78
+ #
79
+ # * +:ami+
80
+ # * +:group+
81
+ # * +:size+
82
+ # * +:keypair+
83
+ # * +:address+
84
+ # * +:private+ true or false (default)
85
+ # * +:machine_data+
86
+ # * +:min+ count
87
+ # * +:max+ count
88
+ #
89
+ def create(opts={}, &each_inst)
90
+ raise NoAMI unless opts[:ami]
91
+ raise NoGroup unless opts[:group]
92
+
93
+ opts = {
94
+ :size => 'm1.small',
95
+ :min => 1,
96
+ :max => nil
97
+ }.merge(opts)
98
+
99
+ old_opts = {
100
+ :image_id => opts[:ami].to_s,
101
+ :min_count => opts[:min],
102
+ :max_count => opts[:max] || opts[:min],
103
+ :key_name => (opts[:keypair] || '').to_s,
104
+ :group_id => [opts[:group]].flatten.compact,
105
+ #:user_data => opts[:machine_data], # Error: Invalid BASE64 encoding of user data ??
106
+ :availability_zone => opts[:zone].to_s,
107
+ :addressing_type => opts[:private] ? 'private' : 'public',
108
+ :instance_type => opts[:size].to_s,
109
+ :kernel_id => nil
110
+ }
111
+ #p opts[:machine_data]
112
+ #exit
113
+
114
+ response = execute_request({}) { @ec2.run_instances(old_opts) }
115
+ return nil unless response['instancesSet'].is_a?(Hash)
116
+ instances = response['instancesSet']['item'].collect do |inst|
117
+ self.class.from_hash(inst)
118
+ end
119
+ instances.each { |inst|
120
+ each_inst.call(inst)
121
+ } if each_inst
122
+ instances
123
+ end
124
+
125
+ def restart(inst_ids=[], &each_inst)
126
+ instances = list(:running, inst_ids, &each_inst) || []
127
+ raise NoRunningInstances if instances.empty?
128
+ inst_ids = objects_to_instance_ids(inst_ids)
129
+ response = execute_request({}) {
130
+ @ec2.reboot_instances(:instance_id => inst_ids)
131
+ }
132
+ response['return'] == 'true'
133
+ end
134
+
135
+ def destroy(inst_ids=[], &each_inst)
136
+ instances = list(:running, inst_ids, &each_inst) || []
137
+ raise NoRunningInstances if instances.empty?
138
+
139
+ inst_ids = objects_to_instance_ids(inst_ids)
140
+
141
+ response = execute_request({}) {
142
+ @ec2.terminate_instances(:instance_id => inst_ids)
143
+ }
144
+
145
+ #instancesSet:
146
+ # item:
147
+ # - instanceId: i-ebdcb882
148
+ # shutdownState:
149
+ # code: "48"
150
+ # name: terminated
151
+ # previousState:
152
+ # code: "48"
153
+ # name: terminated
154
+
155
+ raise MalformedResponse unless response['instancesSet'].is_a?(Hash)
156
+ instances_shutdown = []
157
+ response['instancesSet']['item'].collect do |inst|
158
+ next unless inst['shutdownState'].is_a?(Hash) && inst['shutdownState']['name'] == 'shutting-down'
159
+ instances_shutdown << inst['instanceId']
160
+ end
161
+ success = instances_shutdown.size == inst_ids.size
162
+ success
163
+ end
164
+
165
+ def restart_group(group, &each_inst)
166
+ instances = list_group(group, :running, &each_inst) || []
167
+ inst_ids = objects_to_instance_ids(instances)
168
+ restart(inst_ids, :skip_check)
169
+ end
170
+
171
+ def destroy_group(group, &each_inst)
172
+ instances = list_group(group, :running, &each_inst) || []
173
+ inst_ids = objects_to_instance_ids(instances)
174
+ destroy(inst_ids, :skip_check)
175
+ end
176
+
177
+ # * +state+ is an optional instance state. If specified, must be one of: running (default), pending, terminated.
178
+ # * +inst_ids+ is an Array of instance IDs.
179
+ # Returns an Array of Rudy::AWS::EC2::Instance objects.
180
+ def list(state=nil, inst_ids=[], &each_inst)
181
+ instances = list_as_hash(state, inst_ids, &each_inst)
182
+ instances &&= instances.values
183
+ instances = nil if instances && instances.empty? # Don't return an empty hash
184
+ instances
185
+ end
186
+
187
+ # * +group+ is a security group name.
188
+ # * +state+ is an optional instance state. If specified, must be one of: running (default), pending, terminated.
189
+ # * +inst_ids+ is an Array of instance IDs.
190
+ def list_group(group=nil, state=nil, inst_ids=[], &each_inst)
191
+ instances = list_group_as_hash(group, state, inst_ids, &each_inst)
192
+ instances &&= instances.values
193
+ instances = nil if instances && instances.empty? # Don't return an empty hash
194
+ instances
195
+ end
196
+
197
+
198
+ # * +group+ is a security group name.
199
+ # * +state+ is an optional instance state. If specified, must be one of: running (default), pending, terminated.
200
+ # * +inst_ids+ is an Array of instance IDs.
201
+ def list_group_as_hash(group=nil, state=nil, inst_ids=[], &each_inst)
202
+ instances = list_as_hash(state, inst_ids)
203
+ # Remove instances that are not in the specified group
204
+ if instances
205
+ instances = instances.reject { |id,inst| !inst.groups.member?(group) } if group
206
+ instances.each_value { |inst| each_inst.call(inst) } if each_inst
207
+ end
208
+ instances = nil if instances && instances.empty? # Don't return an empty hash
209
+ instances
210
+ end
211
+
212
+ # * +state+ is an optional instance state. If specified, must be
213
+ # one of: running (default), pending, terminated, any
214
+ # * +inst_ids+ is an Array of instance IDs or Rudy::AWS::EC2::Instance objects.
215
+ # Returns a Hash of Rudy::AWS::EC2::Instance objects. The key is the instance ID.
216
+ # * +each_inst+ a block to execute for every instance in the list.
217
+ def list_as_hash(state=nil, inst_ids=[], &each_inst)
218
+ state &&= state.to_sym
219
+ state = nil if state == :any
220
+ raise "Unknown state: #{state}" if state && !Instances.known_state?(state)
221
+ state = :'shutting-down' if state == :shutting_down # EC2 uses a dash
222
+
223
+ # If we got Instance objects, we want just the IDs.
224
+ # This method always returns an Array.
225
+ inst_ids = objects_to_instance_ids(inst_ids)
226
+
227
+ response = execute_request({}) {
228
+ @ec2.describe_instances(:instance_id => inst_ids)
229
+ }
230
+
231
+ # requestId: c16878ac-28e4-4859-9878-ef93af45789c
232
+ # reservationSet:
233
+ # item:
234
+ # - reservationId: r-e493148d
235
+ # groupSet:
236
+ # item:
237
+ # - groupId: default
238
+ # instancesSet:
239
+ # item:
240
+ return nil unless response['reservationSet'].is_a?(Hash) # No instances
241
+
242
+ resids = []
243
+ instances = {}
244
+ response['reservationSet']['item'].each do |res|
245
+ resids << res['reservationId']
246
+ groups = res['groupSet']['item'].collect { |g| g['groupId'] }
247
+ # And each reservation can have 1 or more instances
248
+ next unless res['instancesSet'].is_a?(Hash)
249
+ res['instancesSet']['item'].each do |props|
250
+ inst = Instances.from_hash(props)
251
+ next if state && inst.state != state.to_s
252
+ inst.groups = groups
253
+ #puts "STATE: #{inst.state} #{state}"
254
+ instances[inst.awsid] = inst
255
+ end
256
+ end
257
+
258
+ instances.each_value { |inst| each_inst.call(inst) } if each_inst
259
+
260
+ instances = nil if instances.empty? # Don't return an empty hash
261
+ instances
262
+ end
263
+
264
+ # System console output.
265
+ #
266
+ # * +inst_id+ instance ID (String) or Instance object.
267
+ #
268
+ # NOTE: Amazon sends the console outputs as a Base64 encoded string.
269
+ # This method DOES NOT decode in order to remain compliant with the
270
+ # data formats returned by Amazon.
271
+ #
272
+ # You can decode it like this:
273
+ #
274
+ # require 'base64'
275
+ # Base64.decode64(output)
276
+ #
277
+ def console(inst_id, &each_inst)
278
+ inst_ids = objects_to_instance_ids([inst_id])
279
+ response = execute_request({}) {
280
+ @ec2.get_console_output(:instance_id => inst_ids.first)
281
+ }
282
+ response['output']
283
+ end
284
+
285
+ def attached_volume?(id, device)
286
+ list = volumes(id)
287
+ list.each do |v|
288
+ return true if v.device == device
289
+ end
290
+ false
291
+ end
292
+
293
+ def volumes(id)
294
+ rvol = Rudy::AWS::EC2::Volumes.new
295
+ rvol.ec2 = @ec2
296
+ rvol.list || []
297
+ list.select { |v| v.attached? && v.instid === id }
298
+ end
299
+
300
+ def device_volume(id, device)
301
+ volumes(id).select { |v| v.device === device }
302
+ end
303
+
304
+ # +inst_id+ is an instance ID
305
+ # Returns an Instance object
306
+ def get(inst_id)
307
+ inst_id = inst_id.awsid if inst_id.is_a?(Rudy::AWS::EC2::Instance)
308
+ inst = list(:any, inst_id)
309
+ inst &&= inst.first
310
+ inst
311
+ end
312
+
313
+ def any?(state=:any, inst_ids=[])
314
+ !list(state, inst_ids).nil?
315
+ end
316
+
317
+ def exists?(inst_ids)
318
+ any?(:any, inst_ids)
319
+ end
320
+
321
+ def any_group?(group=nil, state=:any)
322
+ ret = list_group(group, state)
323
+ !ret.nil?
324
+ end
325
+
326
+ def running?(inst_ids)
327
+ compare_instance_lists(list(:running, inst_ids), inst_ids)
328
+ end
329
+ def pending?(inst_ids)
330
+ compare_instance_lists(list(:pending, inst_ids), inst_ids)
331
+ end
332
+ def terminated?(inst_ids)
333
+ compare_instance_lists(list(:terminated, inst_ids), inst_ids)
334
+ end
335
+ def shutting_down?(inst_ids)
336
+ compare_instance_lists(list(:shutting_down, inst_ids), inst_ids)
337
+ end
338
+
339
+ def unavailable?(inst_ids)
340
+ instances = list(:any, inst_ids) || []
341
+ instances.reject! { |inst|
342
+ (inst.state == "shutting-down" ||
343
+ inst.state == "pending" ||
344
+ inst.state == "terminated")
345
+ }
346
+ compare_instance_lists(instances, inst_ids)
347
+ end
348
+
349
+ #
350
+ # +h+ is a hash of instance properties in the format returned
351
+ # by EC2::Base#describe_instances:
352
+ #
353
+ # kernelId: aki-9b00e5f2
354
+ # amiLaunchIndex: "0"
355
+ # keyName: solutious-default
356
+ # launchTime: "2009-03-14T12:48:15.000Z"
357
+ # instanceType: m1.small
358
+ # imageId: ami-0734d36e
359
+ # privateDnsName:
360
+ # reason:
361
+ # placement:
362
+ # availabilityZone: us-east-1b
363
+ # dnsName:
364
+ # instanceId: i-cdaa34a4
365
+ # instanceState:
366
+ # name: pending
367
+ # code: "0"
368
+ #
369
+ # Returns an Instance object.
370
+ def self.from_hash(h)
371
+ inst = Rudy::AWS::EC2::Instance.new
372
+ inst.aki = h['kernelId']
373
+ inst.ami = h['imageId']
374
+ inst.launch_time = h['launchTime']
375
+ inst.keyname = h['keyName']
376
+ inst.launch_index = h['amiLaunchIndex']
377
+ inst.size = h['instanceType']
378
+ inst.dns_private = h['privateDnsName']
379
+ inst.dns_public = h['dnsName']
380
+ inst.reason = h['reason']
381
+ inst.zone = h['placement']['availabilityZone']
382
+ inst.awsid = h['instanceId']
383
+ inst.state = h['instanceState']['name']
384
+ inst
385
+ end
386
+
387
+ # Is +state+ a known EC2 machine instance state? See: KNOWN_STATES
388
+ def self.known_state?(state)
389
+ return false unless state
390
+ state &&= state.to_sym
391
+ state = :shutting_down if state == :'shutting-down'
392
+ KNOWN_STATES.member?(state)
393
+ end
394
+
395
+ private
396
+
397
+
398
+ # Find out whether two lists of instance IDs (or Rudy::AWS::EC2::Instance objects)
399
+ # contain the same instances regardless of order.
400
+ #
401
+ # *+listA+ An Array of instance IDs (Strings) or Rudy::AWS::EC2::Instance objects
402
+ # *+listB+ Another Array of instance IDs (Strings) or Rudy::AWS::EC2::Instance objects
403
+ # Returns true if:
404
+ # * both listA and listB are Arrays
405
+ # * listA and listB contain the same number of items
406
+ # * all items in listA are in listB
407
+ # * all items in listB are in listA
408
+ def compare_instance_lists(listA, listB)
409
+ listA = objects_to_instance_ids(listA)
410
+ listB = objects_to_instance_ids(listB)
411
+ return false if listA.empty? || listB.empty?
412
+ return false unless listA.size == listB.size
413
+ (listA - listB).empty? && (listB - listA).empty?
414
+ end
415
+
416
+ # * +inst_ids+ an Array of instance IDs (Strings) or Instance objects.
417
+ # Note: This method removes nil values and always returns an Array.
418
+ # Returns an Array of instances IDs.
419
+ def objects_to_instance_ids(inst_ids)
420
+ inst_ids = [inst_ids].flatten # Make sure it's an Array
421
+ inst_ids = inst_ids.collect do |inst|
422
+ next if inst.nil? || inst.to_s.empty?
423
+ if !inst.is_a?(Rudy::AWS::EC2::Instance) && !Rudy::Utils.is_id?(:instance, inst)
424
+ raise %Q("#{inst}" is not an instance ID or object)
425
+ end
426
+ inst.is_a?(Rudy::AWS::EC2::Instance) ? inst.awsid : inst
427
+ end
428
+ inst_ids
429
+ end
430
+
431
+ end
432
+ end
433
+ end
434
+
@@ -0,0 +1,104 @@
1
+
2
+ module Rudy::AWS
3
+ module EC2
4
+
5
+ class KeyPair < Storable
6
+
7
+ field :name
8
+ field :fingerprint
9
+ field :private_key
10
+
11
+ def liner_note
12
+ "%-20s %s" % [self.name.bright, self.fingerprint]
13
+ end
14
+
15
+ def to_s(titles=false)
16
+ str = titles ? "%-20s %s#{$/}" % ['name', 'fingerprint'] : ""
17
+ str << liner_note
18
+ end
19
+
20
+ def public_key
21
+ return unless @private_key
22
+ k = Rye::Key.new(@private_key)
23
+ k.public_key.to_ssh2
24
+ end
25
+
26
+ end
27
+
28
+ class KeyPairs
29
+ include Rudy::AWS::ObjectBase
30
+ include Rudy::AWS::EC2::Base
31
+
32
+ def create(name)
33
+ raise "No name provided" unless name
34
+ ret = @ec2.create_keypair(:key_name => name)
35
+ self.class.from_hash(ret)
36
+ end
37
+
38
+ def destroy(name)
39
+ name = name.name if name.is_a?(Rudy::AWS::EC2::KeyPair)
40
+ raise "No name provided" unless name.is_a?(String)
41
+ ret = @ec2.delete_keypair(:key_name => name)
42
+ (ret && ret['return'] == 'true') # BUG? Always returns true
43
+ end
44
+
45
+ def list(*names)
46
+ keypairs = list_as_hash(names)
47
+ keypairs &&= keypairs.values
48
+ keypairs
49
+ end
50
+
51
+ def list_as_hash(*names)
52
+ names = names.flatten
53
+ klist = @ec2.describe_keypairs(:key_name => names)
54
+ return unless klist['keySet'].is_a?(Hash)
55
+ keypairs = {}
56
+ klist['keySet']['item'].each do |oldkp|
57
+ kp = self.class.from_hash(oldkp)
58
+ keypairs[kp.name] = kp
59
+ end
60
+ keypairs
61
+ end
62
+
63
+ def self.from_hash(h)
64
+ # keyName: test-c5g4v3pe
65
+ # keyFingerprint: 65:d0:ce:e7:6a:b0:88:4a:9c:c7:2d:b8:33:0c:fd:3b:c8:0f:0a:3c
66
+ # keyMaterial: |-
67
+ # -----BEGIN RSA PRIVATE KEY-----
68
+ #
69
+ keypair = Rudy::AWS::EC2::KeyPair.new
70
+ keypair.fingerprint = h['keyFingerprint']
71
+ keypair.name = h['keyName']
72
+ keypair.private_key = h['keyMaterial']
73
+ keypair
74
+ end
75
+
76
+ def any?
77
+ keypairs = list || []
78
+ !keypairs.empty?
79
+ end
80
+
81
+ def get(name)
82
+ keypairs = list(name) || []
83
+ return if keypairs.empty?
84
+ keypairs.first
85
+ end
86
+
87
+ def exists?(name)
88
+ return false unless name
89
+ kp = get(name) rescue nil
90
+ !kp.nil?
91
+ end
92
+
93
+ end
94
+
95
+ class Keypairs
96
+ def initialize(*args)
97
+ raise "Oops! The correct class uses a capital 'P': Rudy::AWS::EC2::KeyPairs"
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+