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
@@ -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
|