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
@@ -0,0 +1,241 @@
1
+
2
+ module Rudy::AWS
3
+
4
+ class EC2::Group < Storable
5
+ class Rule < Storable
6
+ field :ports => Range # Port range
7
+ field :protocol => String
8
+ def to_s
9
+ if self.ports.first == self.ports.last
10
+ "%s(%s)" % [self.protocol, self.ports.last]
11
+ else
12
+ "%s(%s..%s)" % [self.protocol, self.ports.first, self.ports.last]
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class EC2::Group < Storable
19
+ field :name => String
20
+ field :description => String
21
+ field :owner_id => String
22
+ field :addresses => Hash # key: address/mask, value Array of Rule object
23
+ field :groups => Hash # key: group, value Array of Rule object
24
+
25
+ # Print info about a security group
26
+ #
27
+ # * +group+ is a Rudy::AWS::EC2::Group object
28
+ def to_s
29
+ lines = ["%12s: %s" % ['GROUP', self.name.bright]]
30
+
31
+ (self.addresses || {}).each_pair do |address,rules|
32
+ lines << "%6s %s: %s" % ['', address.to_s, rules.collect { |p| p.to_s}.join(', ')]
33
+ end
34
+
35
+ (self.groups || {}).each_pair do |group,rules|
36
+ lines << "%6s %s: %s" % ['', group, rules.collect { |p| p.to_s}.join(', ') ]
37
+ end
38
+
39
+ lines.join($/)
40
+ end
41
+
42
+ # * +ipaddress+ is a String, ipaddress/mask/protocol
43
+ # * +rule+ is a Rule object
44
+ def add_address(ipaddress, rule)
45
+ return false unless rule.is_a?(Rule)
46
+ @addresses ||= {}
47
+ (@addresses[ipaddress] ||= []) << rule
48
+ rule
49
+ end
50
+
51
+ # * +group+ is a String, accountnum:group
52
+ # * +rule+ is a Rule object
53
+ def add_group(group, rule)
54
+ return false unless rule.is_a?(Rule)
55
+ @groups ||= {}
56
+ (@groups[group] ||= []) << rule
57
+ end
58
+
59
+ end
60
+
61
+
62
+
63
+ class EC2
64
+ class Groups
65
+ include Rudy::AWS::ObjectBase
66
+
67
+ def list(group_names=[])
68
+ group_names ||= []
69
+ groups = list_as_hash(group_names)
70
+ groups &&= groups.values
71
+ groups
72
+ end
73
+
74
+ # * +group_names+ is a list of security group names to look for. If it's empty, all groups
75
+ # associated to the account will be returned.
76
+ #
77
+ # Returns an Array of Rudy::AWS::EC2::Group objects
78
+ def list_as_hash(group_names=[])
79
+ group_names ||= []
80
+ glist = @aws.describe_security_groups(:group_name => group_names) || {}
81
+ return unless glist['securityGroupInfo'].is_a?(Hash)
82
+ groups = {}
83
+ glist['securityGroupInfo']['item'].each do |oldg|
84
+ g = Groups.from_hash(oldg)
85
+ groups[g.name] = g
86
+ end
87
+ groups
88
+ end
89
+
90
+ def any?
91
+ groups = list || []
92
+ !groups.empty?
93
+ end
94
+
95
+ # Create a new EC2 security group
96
+ # Returns true/false whether successful
97
+ def create(name, desc=nil)
98
+ ret = @aws.create_security_group(:group_name => name, :group_description => desc || "Group #{name}")
99
+ return false unless (ret && ret['return'] == 'true')
100
+ get(name)
101
+ end
102
+
103
+ # Delete an EC2 security group
104
+ # Returns true/false whether successful
105
+ def destroy(name)
106
+ ret = @aws.delete_security_group(:group_name => name)
107
+ (ret && ret['return'] == 'true')
108
+ end
109
+
110
+ # * +name+ a string
111
+ def get(name)
112
+ (list([name]) || []).first
113
+ end
114
+
115
+ # +group+ a Rudy::AWS::EC2::Group object
116
+ #def save(group)
117
+ #
118
+ #end
119
+
120
+ def modify_rules(meth, name, from_port, to_port, protocol='tcp', ipa='0.0.0.0/0')
121
+ opts = {
122
+ :group_name => name,
123
+ :ip_protocol => protocol,
124
+ :from_port => from_port,
125
+ :to_port => to_port,
126
+ :cidr_ip => ipa
127
+ }
128
+
129
+ ret = @aws.send("#{meth}_security_group_ingress", opts)
130
+ (ret && ret['return'] == 'true')
131
+ end
132
+ private :modify_rules
133
+
134
+ def modify_group_rules(meth, name, gname=nil, gowner=nil)
135
+ opts = {
136
+ :group_name => name,
137
+ :source_security_group_name => gname,
138
+ :source_security_group_owner_id => gowner
139
+ }
140
+ ret = @aws.send("#{meth}_security_group_ingress", opts)
141
+ (ret && ret['return'] == 'true')
142
+ end
143
+ private :modify_group_rules
144
+
145
+ # Authorize a port/protocol for a specific IP address
146
+ def authorize(*args)
147
+ modify_rules(:authorize, *args)
148
+ end
149
+ alias :authorise :authorize
150
+
151
+ def authorize_group(*args)
152
+ modify_group_rules(:authorize, *args)
153
+ end
154
+ alias :authorise_group :authorize_group
155
+
156
+ def revoke_group(*args)
157
+ modify_group_rules(:revoke, *args)
158
+ end
159
+
160
+ # Revoke a port/protocol for a specific IP address
161
+ # Takes the same arguments as authorize
162
+ def revoke(*args)
163
+ modify_rules(:revoke, *args)
164
+ end
165
+
166
+
167
+ # Does the security group +name+ exist?
168
+ def exists?(name)
169
+ begin
170
+ g = list([name.to_s])
171
+ rescue ::EC2::InvalidGroupNotFound
172
+ return false
173
+ end
174
+
175
+ !g.empty?
176
+ end
177
+
178
+
179
+
180
+
181
+ # * +ghash+ is an EC2::Base Security Group Hash. This is the format
182
+ # returned by EC2::Base#describe_security_groups
183
+ #
184
+ # groupName: stage-app
185
+ # groupDescription:
186
+ # ownerId: "207436219441"
187
+ # ipPermissions:
188
+ # item:
189
+ # - ipRanges:
190
+ # item:
191
+ # - cidrIp: 216.19.182.83/32
192
+ # - cidrIp: 24.5.71.201/32
193
+ # - cidrIp: 75.157.176.202/32
194
+ # - cidrIp: 84.28.52.172/32
195
+ # - cidrIp: 87.212.145.201/32
196
+ # - cidrIp: 96.49.129.178/32
197
+ # groups:
198
+ # item:
199
+ # - groupName: default
200
+ # userId: "207436219441"
201
+ # - groupName: stage-app
202
+ # userId: "207436219441"
203
+ # fromPort: "22"
204
+ # toPort: "22"
205
+ # ipProtocol: tcp
206
+ #
207
+ # Returns a Rudy::AWS::EC2::Group object
208
+ def self.from_hash(ghash)
209
+ newg = Rudy::AWS::EC2::Group.new
210
+ newg.name = ghash['groupName']
211
+ newg.description = ghash['groupDescription']
212
+ newg.owner_id = ghash['ownerId']
213
+ newg.addresses = {}
214
+ newg.groups = {}
215
+
216
+ return newg unless ghash['ipPermissions'].is_a?(Hash)
217
+
218
+ ghash['ipPermissions']['item'].each do |oldp|
219
+ newp = Rudy::AWS::EC2::Group::Rule.new
220
+ newp.ports = Range.new(oldp['fromPort'], oldp['toPort'])
221
+ newp.protocol = oldp['ipProtocol']
222
+ if oldp['groups'].is_a?(Hash)
223
+ oldp['groups']['item'].each do |oldpg|
224
+ name = [oldpg['userId'], oldpg['groupName']].join(':') # account_num:name
225
+ newg.add_group(name, newp)
226
+ end
227
+ end
228
+ if oldp['ipRanges'].is_a?(Hash)
229
+ oldp['ipRanges']['item'].each do |olda|
230
+ name = "#{olda['cidrIp']}/#{oldp['ipProtocol']}"
231
+ newg.add_address(name, newp) # ipaddress/mask/protocol
232
+ end
233
+ end
234
+ end
235
+ newg
236
+ end
237
+
238
+ end
239
+ end
240
+
241
+ end
@@ -0,0 +1,46 @@
1
+
2
+
3
+ module Rudy::AWS
4
+ class EC2
5
+
6
+ class Images
7
+ include Rudy::AWS::ObjectBase
8
+
9
+
10
+ def list()
11
+ opts = {
12
+ :image_id => [],
13
+ :owner_id => [],
14
+ :executable_by => []
15
+ }
16
+ ret = @aws.describe_images_by_owner(opts) || []
17
+ puts ret.to_yaml
18
+ []
19
+ end
20
+
21
+ # +id+ AMI ID to deregister (ami-XXXXXXX)
22
+ # Returns true when successful. Otherwise throws an exception.
23
+ def deregister(id)
24
+ opts = {
25
+ :image_id => id
26
+ }
27
+ ret = @aws.deregister_image(opts)
28
+ puts ret.to_yaml
29
+ true
30
+ end
31
+
32
+ # +path+ the S3 path to the manifest (bucket/file.manifest.xml)
33
+ # Returns the AMI ID when successful, otherwise throws an exception.
34
+ def register(path)
35
+ opts = {
36
+ :image_location => path
37
+ }
38
+ ret = @aws.register_image(opts)
39
+ puts ret.to_yaml
40
+ true
41
+ end
42
+ end
43
+
44
+
45
+ end
46
+ end
@@ -0,0 +1,407 @@
1
+
2
+
3
+
4
+ module Rudy::AWS
5
+ class EC2::Instance < Storable
6
+ field :aki
7
+ field :ari
8
+ field :launch_index => Time
9
+ field :launch_time
10
+ field :keyname
11
+ field :instance_type
12
+ field :ami
13
+ field :dns_name_private
14
+ field :dns_name_public
15
+ field :awsid
16
+ field :state
17
+ field :zone
18
+ field :reason
19
+ field :groups => Array
20
+
21
+ def groups
22
+ @groups || []
23
+ end
24
+
25
+
26
+ def to_s
27
+ lines = []
28
+ field_names.each do |key|
29
+ next unless self.respond_to?(key)
30
+ val = self.send(key)
31
+ lines << sprintf(" %22s: %s", key, (val.is_a?(Array) ? val.join(', ') : val))
32
+ end
33
+ lines.join($/)
34
+ end
35
+
36
+ def running?
37
+ self.state && self.state == 'running'
38
+ end
39
+
40
+ def pending?
41
+ self.state && self.state == 'pending'
42
+ end
43
+
44
+ def terminated?
45
+ self.state && self.state == 'terminated'
46
+ end
47
+
48
+ def shutting_down?
49
+ self.state && self.state == 'shutting-down'
50
+ end
51
+
52
+ end
53
+
54
+
55
+ class EC2
56
+ class Instances
57
+ include Rudy::AWS::ObjectBase
58
+ unless defined?(KNOWN_STATES)
59
+ KNOWN_STATES = [:running, :pending, :shutting_down, :terminated].freeze
60
+ end
61
+
62
+ # Return an Array of Instance objects. Note: These objects will not have
63
+ # DNS data because they will still be in pending state. The DNS info becomes
64
+ # available once the instance enters the running state.
65
+ def create(ami, group='default', keypair_name=nil, user_data=nil, zone=nil)
66
+ opts = {
67
+ :image_id => ami.to_s,
68
+ :min_count => 1,
69
+ :max_count => 1,
70
+ :key_name => keypair_name.to_s,
71
+ :group_id => [group].flatten,
72
+ :user_data => user_data,
73
+ :availability_zone => zone.to_s,
74
+ :addressing_type => 'public',
75
+ :instance_type => 'm1.small',
76
+ :kernel_id => nil
77
+ }
78
+
79
+ response = execute_request({}) { @aws.run_instances(opts) }
80
+
81
+ # reservationId: r-f393149a
82
+ # groupSet:
83
+ # item:
84
+ # - groupId: default
85
+ # requestId: a4de33de-6da1-4f43-a3f5-f987f5f1f1cf
86
+ # instancesSet:
87
+ # item:
88
+ # ... # see Instances.from_hash
89
+ raise "The request failed to return instance data" unless response['instancesSet'].is_a?(Hash)
90
+ instances = response['instancesSet']['item'].collect do |inst|
91
+ self.class.from_hash(inst)
92
+ end
93
+
94
+ instances
95
+ end
96
+
97
+ def restart(inst_ids=[], skip_check=false)
98
+ unless skip_check
99
+ instances = list(:running, inst_ids) || []
100
+ raise "No matching running instances found" if instances.empty?
101
+ end
102
+
103
+ inst_ids = objects_to_instance_ids(inst_ids)
104
+
105
+ response = execute_request({}) {
106
+ @aws.reboot_instances(:instance_id => inst_ids)
107
+ }
108
+
109
+ response['return'] == 'true'
110
+ end
111
+
112
+ def destroy(inst_ids=[], skip_check=false)
113
+ unless skip_check
114
+ instances = list(:running, inst_ids) || []
115
+ raise "No matching running instances found" if instances.empty?
116
+ end
117
+
118
+ inst_ids = objects_to_instance_ids(inst_ids)
119
+
120
+ response = execute_request({}) {
121
+ @aws.terminate_instances(:instance_id => inst_ids)
122
+ }
123
+
124
+ #instancesSet:
125
+ # item:
126
+ # - instanceId: i-ebdcb882
127
+ # shutdownState:
128
+ # code: "48"
129
+ # name: terminated
130
+ # previousState:
131
+ # code: "48"
132
+ # name: terminated
133
+
134
+ raise "The request failed to return instance data" unless response['instancesSet'].is_a?(Hash)
135
+ instances_shutdown = []
136
+ response['instancesSet']['item'].collect do |inst|
137
+ next unless inst['shutdownState'].is_a?(Hash) && inst['shutdownState']['name'] == 'shutting-down'
138
+ instances_shutdown << inst['instanceId']
139
+ end
140
+ success = instances_shutdown.size == inst_ids.size
141
+ #puts "SUC: #{success} #{instances_shutdown.size} #{inst_ids.size}"
142
+ success
143
+ end
144
+
145
+ def restart_group(group)
146
+ instances = list_group(group, :running) || []
147
+ inst_ids = objects_to_instance_ids(instances)
148
+ restart(inst_ids, :skip_check)
149
+ end
150
+
151
+ def destroy_group(group)
152
+ instances = list_group(group, :running) || []
153
+ inst_ids = objects_to_instance_ids(instances)
154
+ destroy(inst_ids, :skip_check)
155
+ end
156
+
157
+ # * +state+ is an optional instance state. If specified, must be one of: running (default), pending, terminated.
158
+ # * +inst_ids+ is an Array of instance IDs.
159
+ # Returns an Array of Rudy::AWS::EC2::Instance objects.
160
+ def list(state=nil, inst_ids=[])
161
+ instances = list_as_hash(state, inst_ids)
162
+ instances &&= instances.values
163
+ instances = nil if instances && instances.empty? # Don't return an empty hash
164
+ instances
165
+ end
166
+
167
+ # * +group+ is a security group name.
168
+ # * +state+ is an optional instance state. If specified, must be one of: running (default), pending, terminated.
169
+ # * +inst_ids+ is an Array of instance IDs.
170
+ def list_group(group=nil, state=nil, inst_ids=[])
171
+ raise "No group specified" unless group
172
+ instances = list_group_as_hash(group, state, inst_ids)
173
+ instances &&= instances.values
174
+ instances = nil if instances && instances.empty? # Don't return an empty hash
175
+ instances
176
+ end
177
+
178
+
179
+ # * +group+ is a security group name.
180
+ # * +state+ is an optional instance state. If specified, must be one of: running (default), pending, terminated.
181
+ # * +inst_ids+ is an Array of instance IDs.
182
+ def list_group_as_hash(group=nil, state=nil, inst_ids=[])
183
+ raise "No group specified" unless group
184
+ instances = list_as_hash(state, inst_ids)
185
+ # Remove instances that are not in the specified group
186
+ instances &&= instances.reject { |id,inst| !inst.groups.member?(group) } if group
187
+ instances = nil if instances && instances.empty? # Don't return an empty hash
188
+ instances
189
+ end
190
+
191
+ # * +state+ is an optional instance state. If specified, must be
192
+ # one of: running (default), pending, terminated, any
193
+ # * +inst_ids+ is an Array of instance IDs or Rudy::AWS::EC2::Instance objects.
194
+ # Returns a Hash of Rudy::AWS::EC2::Instance objects. The key is the instance ID.
195
+ def list_as_hash(state=nil, inst_ids=[])
196
+ state &&= state.to_sym
197
+ state = nil if state == :any
198
+ raise "Unknown state: #{state}" if state && !Instances.known_state?(state)
199
+ state = :'shutting-down' if state == :shutting_down # EC2 uses a dash
200
+
201
+ # If we got Instance objects, we want just the IDs.
202
+ # This method always returns an Array.
203
+ inst_ids = objects_to_instance_ids(inst_ids)
204
+
205
+ response = execute_request({}) {
206
+ @aws.describe_instances(:instance_id => inst_ids)
207
+ }
208
+
209
+ # requestId: c16878ac-28e4-4859-9878-ef93af45789c
210
+ # reservationSet:
211
+ # item:
212
+ # - reservationId: r-e493148d
213
+ # groupSet:
214
+ # item:
215
+ # - groupId: default
216
+ # instancesSet:
217
+ # item:
218
+ return nil unless response['reservationSet'].is_a?(Hash) # No instances
219
+
220
+ resids = []
221
+ instances = {}
222
+ response['reservationSet']['item'].each do |res|
223
+ resids << res['reservationId']
224
+ groups = res['groupSet']['item'].collect { |g| g['groupId'] }
225
+ # And each reservation can have 1 or more instances
226
+ next unless res['instancesSet'].is_a?(Hash)
227
+ res['instancesSet']['item'].each do |props|
228
+ inst = self.class.from_hash(props)
229
+ next if state && inst.state != state.to_s
230
+ inst.groups = groups
231
+ #puts "STATE: #{inst.state} #{state}"
232
+ instances[inst.awsid] = inst
233
+ end
234
+ end
235
+
236
+ instances = nil if instances.empty? # Don't return an empty hash
237
+ instances
238
+ end
239
+
240
+ # System console output.
241
+ #
242
+ # * +inst_id+ instance ID (String) or Instance object.
243
+ #
244
+ # NOTE: Amazon sends the console outputs as a Base64 encoded string.
245
+ # This method DOES NOT decode in order to remain compliant with the
246
+ # data formats returned by Amazon.
247
+ #
248
+ # You can decode it like this:
249
+ #
250
+ # require 'base64'
251
+ # Base64.decode64(output)
252
+ #
253
+ def console_output(inst_id)
254
+ inst_ids = objects_to_instance_ids([inst_id])
255
+ response = execute_request({}) {
256
+ @aws.get_console_output(:instance_id => inst_ids.first)
257
+ }
258
+ response['output']
259
+ end
260
+
261
+ def attached_volume?(id, device)
262
+ list = volumes(id)
263
+ list.each do |v|
264
+ return true if v.device == device
265
+ end
266
+ false
267
+ end
268
+
269
+ def volumes(id)
270
+ list = Rudy::AWS::EC2::Volumes.new(@aws).list || []
271
+ list.select { |v| v.status != "deleting" && v.instid === id }
272
+ end
273
+
274
+ def device_volume(id, device)
275
+ volumes(id).select { |v| v.device === device }
276
+ end
277
+
278
+ # +inst_id+ is an instance ID
279
+ # Returns an Instance object
280
+ def get(inst_id)
281
+ inst_id = inst_id.awsid if inst_id.is_a?(Rudy::AWS::EC2::Instance)
282
+ inst = list(:any, inst_id)
283
+ raise "Unknown instance: #{inst_id}" unless inst
284
+ inst.first
285
+ end
286
+
287
+ def any?(state=:any, inst_ids=[])
288
+ !list(state, inst_ids).nil?
289
+ end
290
+
291
+ def exists?(inst_ids)
292
+ any?(:any, inst_ids)
293
+ end
294
+
295
+ def any_group?(group=nil, state=:any)
296
+ ret = list_group(group, state)
297
+ !ret.nil?
298
+ end
299
+
300
+ def running?(inst_ids)
301
+ compare_instance_lists(list(:running, inst_ids), inst_ids)
302
+ end
303
+ def pending?(inst_ids)
304
+ compare_instance_lists(list(:pending, inst_ids), inst_ids)
305
+ end
306
+ def terminated?(inst_ids)
307
+ compare_instance_lists(list(:terminated, inst_ids), inst_ids)
308
+ end
309
+ def shutting_down?(inst_ids)
310
+ compare_instance_lists(list(:shutting_down, inst_ids), inst_ids)
311
+ end
312
+
313
+ def unavailable?(inst_ids)
314
+ instances = list(:any, inst_ids) || []
315
+ instances.reject! { |inst|
316
+ (inst.state == "shutting-down" ||
317
+ inst.state == "pending" ||
318
+ inst.state == "terminated")
319
+ }
320
+ compare_instance_lists(instances, inst_ids)
321
+ end
322
+
323
+ #
324
+ # +h+ is a hash of instance properties in the format returned
325
+ # by EC2::Base#describe_instances:
326
+ #
327
+ # kernelId: aki-9b00e5f2
328
+ # amiLaunchIndex: "0"
329
+ # keyName: solutious-default
330
+ # launchTime: "2009-03-14T12:48:15.000Z"
331
+ # instanceType: m1.small
332
+ # imageId: ami-0734d36e
333
+ # privateDnsName:
334
+ # reason:
335
+ # placement:
336
+ # availabilityZone: us-east-1b
337
+ # dnsName:
338
+ # instanceId: i-cdaa34a4
339
+ # instanceState:
340
+ # name: pending
341
+ # code: "0"
342
+ #
343
+ # Returns an Instance object.
344
+ def self.from_hash(h)
345
+ inst = Rudy::AWS::EC2::Instance.new
346
+ inst.aki = h['kernelId']
347
+ inst.ami = h['imageId']
348
+ inst.launch_time = h['launchTime']
349
+ inst.keyname = h['keyName']
350
+ inst.launch_index = h['amiLaunchIndex']
351
+ inst.instance_type = h['instanceType']
352
+ inst.dns_name_private = h['privateDnsName']
353
+ inst.dns_name_public = h['dnsName']
354
+ inst.reason = h['reason']
355
+ inst.zone = h['placement']['availabilityZone']
356
+ inst.awsid = h['instanceId']
357
+ inst.state = h['instanceState']['name']
358
+ inst
359
+ end
360
+
361
+ # Is +state+ a known EC2 machine instance state? See: KNOWN_STATES
362
+ def self.known_state?(state)
363
+ return false unless state
364
+ state &&= state.to_sym
365
+ state = :shutting_down if state == :'shutting-down'
366
+ KNOWN_STATES.member?(state)
367
+ end
368
+
369
+ private
370
+
371
+
372
+ # Find out whether two lists of instance IDs (or Rudy::AWS::EC2::Instance objects)
373
+ # contain the same instances regardless of order.
374
+ #
375
+ # *+listA+ An Array of instance IDs (Strings) or Rudy::AWS::EC2::Instance objects
376
+ # *+listB+ Another Array of instance IDs (Strings) or Rudy::AWS::EC2::Instance objects
377
+ # Returns true if:
378
+ # * both listA and listB are Arrays
379
+ # * listA and listB contain the same number of items
380
+ # * all items in listA are in listB
381
+ # * all items in listB are in listA
382
+ def compare_instance_lists(listA, listB)
383
+ listA = objects_to_instance_ids(listA)
384
+ listB = objects_to_instance_ids(listB)
385
+ return false if listA.empty? || listB.empty?
386
+ return false unless listA.size == listB.size
387
+ (listA - listB).empty? && (listB - listA).empty?
388
+ end
389
+
390
+ # * +inst_ids+ an Array of instance IDs (Strings) or Instance objects.
391
+ # Note: This method removes nil values and always returns an Array.
392
+ # Returns an Array of instances IDs.
393
+ def objects_to_instance_ids(inst_ids)
394
+ inst_ids = [inst_ids].flatten # Make sure it's an Array
395
+ inst_ids = inst_ids.collect do |inst|
396
+ next if inst.nil? || inst.to_s.empty?
397
+ if !inst.is_a?(Rudy::AWS::EC2::Instance) && !Rudy.is_id?(:instance, inst)
398
+ raise %Q("#{inst}" is not an instance ID or object)
399
+ end
400
+ inst.is_a?(Rudy::AWS::EC2::Instance) ? inst.awsid : inst
401
+ end
402
+ inst_ids
403
+ end
404
+
405
+ end
406
+ end
407
+ end