ey_cloud_awareness 0.1.12 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -8,6 +8,18 @@ This gem makes it a little easier to live on the EngineYard cloud:
8
8
 
9
9
  We use it over at http://brighterplanet.com.
10
10
 
11
+ == Don't read this
12
+
13
+ This gem depends on
14
+
15
+ /etc/chef/dna.json
16
+
17
+ being READABLE and containing certain attributes as named by EngineYard. You might have to add:
18
+
19
+ `sudo chmod a+r /etc/chef/dna.json`
20
+
21
+ to your before_migrate.rb script.
22
+
11
23
  == Quick start
12
24
 
13
25
  Put this in <tt>config/environment.rb</tt>:
@@ -139,23 +151,50 @@ Or whatever you want:
139
151
  >> all_app_instances = EngineYardCloudInstance.app
140
152
  => [#<EngineYardCloudInstance:0xb5e12f70 @instance_id="i-50cf5838">, #<EngineYardCloudInstance:0xb5e12f5c @instance_id="i-dc9207b4">, #<EngineYardCloudInstance:0xb5e12f34 @instance_id="i-b2d84fda">]
141
153
  >> all_app_instances.first.dns_name
142
- => "ec2-67-202-43-40.compute-1.amazonaws.com"
154
+ => "ec2-67-201-47-30.compute-1.amazonaws.com"
143
155
  >> pp all_app_instances.first.to_hash
144
- {:dns_name=>"ec2-67-202-43-40.compute-1.amazonaws.com",
145
- :instance_role=>"app",
146
- :aws_groups=>["ey-my_production-1256085955-3205-13340"],
147
- :aws_instance_id=>"i-50cf5838",
148
- :private_dns_name=>"domU-12-31-39-01-99-D3.compute-1.internal",
149
- :aws_state=>"running"}
150
- => nil
151
-
152
- == A note on EngineYard dependence
153
-
154
- This gem depends on
155
-
156
- /etc/chef/dna.json
157
-
158
- being present and containing certain attributes as named by EngineYard. Please let me know if something changes.
156
+ => {:block_device_mapping=>
157
+ [{:ebs=>
158
+ {:status=>"attached",
159
+ :volumeId=>"vol-26172ee8",
160
+ :deleteOnTermination=>"false",
161
+ :attachTime=>"2010-04-07T21:09:37.000Z"},
162
+ :deviceName=>"/dev/sdz2"},
163
+ {:ebs=>
164
+ {:status=>"attached",
165
+ :volumeId=>"vol-26172ee8",
166
+ :deleteOnTermination=>"false",
167
+ :attachTime=>"2010-04-07T21:09:37.000Z"},
168
+ :deviceName=>"/dev/sdz1"}],
169
+ :launch_time=>"2010-04-07T21:08:10.000Z",
170
+ :instance_type=>"c1.medium",
171
+ :private_dns_name=>"domU-44-44-44-44-A4-02.compute-1.internal",
172
+ :instance_state=>{:code=>"16", :name=>"running"},
173
+ :ami_launch_index=>"0",
174
+ :users=>
175
+ [{:password=>"hoppAugEv",
176
+ :username=>"deploy",
177
+ :uid=>"1000",
178
+ :comment=>"",
179
+ :gid=>"1000"}],
180
+ :environment=>
181
+ {:framework_env=>"production",
182
+ :name=>"my_staging",
183
+ :stack=>"nginx_passenger"},
184
+ :instance_id=>"i-50cf5838",
185
+ :group_id=>"ey-my_staging-XXXXXXXXXXXXXXXXXXXX",
186
+ :root_device_type=>"instance-store",
187
+ :private_ip_address=>"10.201.102.201",
188
+ :kernel_id=>"aki-9b00e5f2",
189
+ :placement=>{:availabilityZone=>"us-east-1a"},
190
+ :product_codes=>nil,
191
+ :image_id=>"ami-7044a419",
192
+ :reason=>nil,
193
+ :dns_name=>"ec2-67-201-47-30.compute-1.amazonaws.com",
194
+ :ip_address=>"199.99.99.99",
195
+ :architecture=>"i386",
196
+ :instance_role=>"solo",
197
+ :monitoring=>{:state=>"disabled"}}
159
198
 
160
199
  == A note on caching and network needs
161
200
 
@@ -163,7 +202,7 @@ I tried to be smart about caching the results of network calls.
163
202
 
164
203
  Stuff like the current instance id, which is pulled from an EC2 metadata server, is stored in
165
204
 
166
- CURRENT_INSTANCE_ID_CACHE_PATH = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/config/engine_yard_cloud_instance_id" : '/etc/engine_yard_cloud_instance_id'
205
+ CURRENT_INSTANCE_ID_CACHE_PATH = File.expand_path '~/.ey_cloud_awareness/engine_yard_cloud_instance_id'
167
206
 
168
207
  Please let me know if this causes problems.
169
208
 
data/Rakefile CHANGED
@@ -6,14 +6,14 @@ begin
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "ey_cloud_awareness"
8
8
  gem.summary = %Q{Make your EngineYard cloud instances aware of each other.}
9
- gem.description = %Q{Make your EngineYard cloud instances aware of each other.}
9
+ gem.description = %Q{Pull metadata from EC2 and EngineYard so that your EngineYard Cloud instances know about each other.}
10
10
  gem.email = "seamus@abshere.net"
11
11
  gem.homepage = "http://github.com/seamusabshere/ey_cloud_awareness"
12
12
  gem.authors = ["Seamus Abshere"]
13
13
  # gem.rubyforge_project = "ey_cloud_awareness"
14
14
  gem.add_dependency 'json', '>=1.2.3'
15
15
  gem.add_dependency 'activesupport', '>=2.3.4'
16
- gem.add_dependency 'right_aws', '1.10.0' # static because of my hack
16
+ gem.add_dependency 'amazon-ec2', '>=0.9.10'
17
17
  gem.add_development_dependency "rspec", ">= 1.2.9"
18
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
19
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.12
1
+ 0.1.13
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ey_cloud_awareness}
8
- s.version = "0.1.12"
8
+ s.version = "0.1.13"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Seamus Abshere"]
12
- s.date = %q{2010-04-07}
13
- s.description = %q{Make your EngineYard cloud instances aware of each other.}
12
+ s.date = %q{2010-04-08}
13
+ s.description = %q{Pull metadata from EC2 and EngineYard so that your EngineYard Cloud instances know about each other.}
14
14
  s.email = %q{seamus@abshere.net}
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
@@ -49,18 +49,18 @@ Gem::Specification.new do |s|
49
49
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
50
  s.add_runtime_dependency(%q<json>, [">= 1.2.3"])
51
51
  s.add_runtime_dependency(%q<activesupport>, [">= 2.3.4"])
52
- s.add_runtime_dependency(%q<right_aws>, ["= 1.10.0"])
52
+ s.add_runtime_dependency(%q<amazon-ec2>, [">= 0.9.10"])
53
53
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
54
54
  else
55
55
  s.add_dependency(%q<json>, [">= 1.2.3"])
56
56
  s.add_dependency(%q<activesupport>, [">= 2.3.4"])
57
- s.add_dependency(%q<right_aws>, ["= 1.10.0"])
57
+ s.add_dependency(%q<amazon-ec2>, [">= 0.9.10"])
58
58
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
59
59
  end
60
60
  else
61
61
  s.add_dependency(%q<json>, [">= 1.2.3"])
62
62
  s.add_dependency(%q<activesupport>, [">= 2.3.4"])
63
- s.add_dependency(%q<right_aws>, ["= 1.10.0"])
63
+ s.add_dependency(%q<amazon-ec2>, [">= 0.9.10"])
64
64
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
65
65
  end
66
66
  end
@@ -1,7 +1,7 @@
1
1
  class EngineYardCloudInstance
2
2
  CURRENT_INSTANCE_ID_CACHE_PATH = File.expand_path '~/.ey_cloud_awareness/engine_yard_cloud_instance_id'
3
- CURRENT_SECURITY_GROUPS_CACHE_PATH = File.expand_path '~/.ey_cloud_awareness/engine_yard_cloud_security_groups'
4
- INSTANCE_DESCRIPTIONS_CACHE_PATH = File.expand_path '~/.ey_cloud_awareness/engine_yard_cloud_instance_descriptions.yml'
3
+ CURRENT_SECURITY_GROUP_CACHE_PATH = File.expand_path '~/.ey_cloud_awareness/engine_yard_cloud_security_group'
4
+ INSTANCE_DESCRIPTIONS_CACHE_PATH = File.expand_path '~/.ey_cloud_awareness/engine_yard_cloud_ec2_instance_descriptions.yml'
5
5
  DNA_PATH = '/etc/chef/dna.json'
6
6
 
7
7
  attr_reader :instance_id
@@ -104,49 +104,52 @@ class EngineYardCloudInstance
104
104
  raise "[EY CLOUD AWARENESS GEM] Can't clear if we used from_hash" if self.proxy
105
105
  @_data = nil
106
106
  @_dna = nil
107
- cached_instance_descriptions true
108
- cached_current_security_groups true
107
+ cached_ec2_instance_descriptions true
108
+ cached_current_security_group true
109
109
  cached_current_instance_id true
110
110
  end
111
111
 
112
112
  def data
113
113
  return @_data if @_data
114
114
  raise "[EY CLOUD AWARENESS GEM] Can't calculate data if we used from_hash" if self.proxy
115
- hash = Hash.new
116
- cached_instance_descriptions.each do |instance_description|
117
- next unless Set.new(Array.wrap(cached_current_security_groups)).superset? Set.new(instance_description[:aws_groups])
118
- hash[instance_description[:aws_instance_id]] ||= Hash.new
119
- current = hash[instance_description[:aws_instance_id]]
120
- # using current as a pointer
115
+ @_data = Hash.new
116
+ cached_ec2_instance_descriptions.each do |ec2_instance_description|
117
+ @_data[ec2_instance_description['instanceId']] ||= Hash.new
118
+ member = @_data[ec2_instance_description['instanceId']]
119
+ # using instance as a pointer
121
120
  if dna[:instance_role] == 'solo'
122
- current[:instance_role] = 'solo'
123
- elsif dna[:db_host] == instance_description[:dns_name] or dna[:db_host] == instance_description[:private_dns_name]
124
- current[:instance_role] = 'db_master'
125
- elsif Array.wrap(dna[:db_slaves]).include? instance_description[:private_dns_name]
126
- current[:instance_role] = 'db_slave'
127
- elsif Array.wrap(dna[:utility_instances]).include? instance_description[:private_dns_name]
128
- current[:instance_role] = 'utility'
129
- elsif dna[:master_app_server][:private_dns_name] == instance_description[:private_dns_name]
130
- current[:instance_role] = 'app_master'
131
- elsif instance_description[:aws_state] == 'running'
132
- current[:instance_role] = 'app'
121
+ member[:instance_role] = 'solo'
122
+ elsif dna[:db_host] == ec2_instance_description['dnsName'] or dna[:db_host] == ec2_instance_description['privateDnsName']
123
+ member[:instance_role] = 'db_master'
124
+ elsif Array.wrap(dna[:db_slaves]).include? ec2_instance_description['privateDnsName']
125
+ member[:instance_role] = 'db_slave'
126
+ elsif Array.wrap(dna[:utility_instances]).include? ec2_instance_description['privateDnsName']
127
+ member[:instance_role] = 'utility'
128
+ elsif dna[:master_app_server][:private_dns_name] == ec2_instance_description['privateDnsName']
129
+ member[:instance_role] = 'app_master'
130
+ elsif ec2_instance_description['instanceState']['name'] == 'running'
131
+ member[:instance_role] = 'app'
133
132
  else
134
- current[:instance_role] = 'unknown'
133
+ member[:instance_role] = 'unknown'
135
134
  end
136
- current[:private_dns_name] = instance_description[:private_dns_name]
137
- current[:dns_name] = instance_description[:dns_name]
138
- current[:aws_state] = instance_description[:aws_state]
139
- current[:aws_groups] = instance_description[:aws_groups]
140
- current[:aws_instance_id] = instance_description[:aws_instance_id]
141
- current[:users] = dna[:users]
142
- current[:environment] = dna[:environment]
135
+ member[:group_id] = cached_current_security_group
136
+ member[:users] = dna[:users]
137
+ member[:environment] = dna[:environment]
143
138
  @_environment ||= dna[:environment]
139
+
140
+ ec2_instance_description.each do |raw_k, raw_v|
141
+ k = raw_k.underscore.to_sym
142
+ next if member.keys.include? k
143
+ member[k] = raw_v
144
+ end
144
145
  end
145
- @_data = hash.recursive_symbolize_keys!
146
+ @_data.recursive_symbolize_keys!
147
+ @_data
146
148
  end
147
149
 
148
150
  def dna
149
151
  raise "[EY CLOUD AWARENESS GEM] Can't see DNA if we used from_hash" if self.proxy
152
+ raise "[EY CLOUD AWARENESS GEM] Can't read DNA from #{DNA_PATH}! You should put 'sudo chmod a+r /etc/chef/dna.json' your your before_migrate.rb!" unless File.readable?(DNA_PATH)
150
153
  @_dna ||= JSON.load(IO.read(DNA_PATH)).recursive_symbolize_keys!
151
154
  end
152
155
 
@@ -167,33 +170,37 @@ class EngineYardCloudInstance
167
170
  end
168
171
 
169
172
 
170
- def cached_current_security_groups(refresh = false)
171
- raise "[EY CLOUD AWARENESS GEM] Can't call current_security_groups if we used from_hash" if self.proxy
172
- if refresh or !File.readable?(CURRENT_SECURITY_GROUPS_CACHE_PATH)
173
- @_cached_current_security_groups = open("http://169.254.169.254/latest/meta-data/security-groups").gets
173
+ def cached_current_security_group(refresh = false)
174
+ raise "[EY CLOUD AWARENESS GEM] Can't call current_security_group if we used from_hash" if self.proxy
175
+ if refresh or !File.readable?(CURRENT_SECURITY_GROUP_CACHE_PATH)
176
+ @_cached_current_security_group = open('http://169.254.169.254/latest/meta-data/security-groups').gets
177
+ raise "[EY CLOUD AWARENESS GEM] Don't know how to deal with (possibly) multiple security group: #{@_cached_current_security_group}" if @_cached_current_security_group =~ /,;/
174
178
  begin
175
- FileUtils.mkdir_p File.dirname(CURRENT_SECURITY_GROUPS_CACHE_PATH)
176
- File.open(CURRENT_SECURITY_GROUPS_CACHE_PATH, 'w') { |f| f.write @_cached_current_security_groups }
179
+ FileUtils.mkdir_p File.dirname(CURRENT_SECURITY_GROUP_CACHE_PATH)
180
+ File.open(CURRENT_SECURITY_GROUP_CACHE_PATH, 'w') { |f| f.write @_cached_current_security_group }
177
181
  rescue Errno::EACCES
178
- $stderr.puts "[EY CLOUD AWARENESS GEM] Not caching current security groups because #{CURRENT_SECURITY_GROUPS_CACHE_PATH} can't be written to"
182
+ $stderr.puts "[EY CLOUD AWARENESS GEM] Not caching current security group because #{CURRENT_SECURITY_GROUP_CACHE_PATH} can't be written to"
179
183
  end
180
184
  end
181
- @_cached_current_security_groups ||= IO.read(CURRENT_SECURITY_GROUPS_CACHE_PATH)
185
+ @_cached_current_security_group ||= IO.read(CURRENT_SECURITY_GROUP_CACHE_PATH)
182
186
  end
183
187
 
184
- def cached_instance_descriptions(refresh = false)
185
- raise "[EY CLOUD AWARENESS GEM] Can't call cached_instance_descriptions if we used from_hash" if self.proxy
188
+ def cached_ec2_instance_descriptions(refresh = false)
189
+ raise "[EY CLOUD AWARENESS GEM] Can't call cached_ec2_instance_descriptions if we used from_hash" if self.proxy
186
190
  if refresh or !File.readable?(INSTANCE_DESCRIPTIONS_CACHE_PATH)
187
- ec2 = RightAws::Ec2.new dna[:aws_secret_id], dna[:aws_secret_key]
188
- @_cached_instance_descriptions = ec2.describe_instances.map(&:recursive_symbolize_keys!)
191
+ ec2 = AWS::EC2::Base.new :access_key_id => dna[:aws_secret_id], :secret_access_key => dna[:aws_secret_key]
192
+ @_cached_ec2_instance_descriptions = ec2.describe_instances
193
+ @_cached_ec2_instance_descriptions.recursive_kill_xml_item_keys!
194
+ @_cached_ec2_instance_descriptions = @_cached_ec2_instance_descriptions['reservationSet'].select { |hash| cached_current_security_group.include? hash['groupSet'].first['groupId'] }
195
+ @_cached_ec2_instance_descriptions.map! { |hash| hash['instancesSet'].first }
189
196
  begin
190
197
  FileUtils.mkdir_p File.dirname(INSTANCE_DESCRIPTIONS_CACHE_PATH)
191
- File.open(INSTANCE_DESCRIPTIONS_CACHE_PATH, 'w') { |f| f.write @_cached_instance_descriptions.to_yaml }
198
+ File.open(INSTANCE_DESCRIPTIONS_CACHE_PATH, 'w') { |f| f.write @_cached_ec2_instance_descriptions.to_yaml }
192
199
  rescue Errno::EACCES
193
200
  $stderr.puts "[EY CLOUD AWARENESS GEM] Not caching instance data because #{INSTANCE_DESCRIPTIONS_CACHE_PATH} can't be written to"
194
201
  end
195
202
  end
196
- @_cached_instance_descriptions ||= YAML.load(IO.read(INSTANCE_DESCRIPTIONS_CACHE_PATH))
203
+ @_cached_ec2_instance_descriptions ||= YAML.load(IO.read(INSTANCE_DESCRIPTIONS_CACHE_PATH))
197
204
  end
198
205
  end
199
206
  end
@@ -3,13 +3,20 @@ require 'set'
3
3
  require 'fileutils'
4
4
  require 'json'
5
5
  require 'yaml'
6
- require 'right_aws' # See aws-s3 compatibility hack below
6
+ require 'AWS' # aka amazon-ec2
7
7
  require 'active_support'
8
- begin; require 'active_support/core_ext/class/inheritable_attributes'; rescue MissingSourceFile; end
9
- begin; require 'active_support/inflector/inflections'; rescue MissingSourceFile; end
10
- begin; require 'active_support/core_ext/string/inflections'; rescue MissingSourceFile; end
11
- begin; require 'active_support/core_ext/hash/keys'; rescue MissingSourceFile; end
12
- begin; require 'active_support/core_ext/array/wrap'; rescue MissingSourceFile; end
8
+ require 'active_support/version'
9
+ %w{
10
+ active_support/core_ext/string
11
+ active_support/core_ext/class/inheritable_attributes
12
+ active_support/inflector/inflections
13
+ active_support/core_ext/string/inflections
14
+ active_support/core_ext/hash/keys
15
+ active_support/core_ext/array/wrap
16
+ }.each do |active_support_3_requirement|
17
+ require active_support_3_requirement
18
+ end if ActiveSupport::VERSION::MAJOR == 3
19
+
13
20
  require 'engine_yard_cloud_instance'
14
21
 
15
22
  class Hash
@@ -28,36 +35,39 @@ class Hash
28
35
  self
29
36
  end
30
37
 
31
- def deep_copy
32
- Marshal.load Marshal.dump(self)
38
+ XML_ITEM_KEYS = [ :item, 'item' ]
39
+
40
+ # :sam => { :item => [{ :foo => :bar }] }
41
+ # into
42
+ # :sam => [{:foo => :bar}]
43
+ def kill_xml_item_keys!
44
+ if keys.length == 1 and XML_ITEM_KEYS.include?(keys.first)
45
+ raise ArgumentError, "You need to call kill_xml_item_keys! on { :foo => { :items => [...] } } not on { :items => [...] }"
46
+ end
47
+ keys.each do |key|
48
+ if self[key].is_a?(Hash) and self[key].keys.length == 1 and XML_ITEM_KEYS.include?(self[key].keys.first)
49
+ # self[:sam] = self[:sam]["item"] (using values.first because we don't know if it's :item or "item")
50
+ self[key] = delete(key).values.first
51
+ end
52
+ end
53
+ self
33
54
  end
34
- end
35
-
36
- # sabshere 11/05/09
37
- # Compatibility hack for aws-s3/right_aws
38
- # Apologies for rescuing instead of directly checking arity
39
- # I couldn't figure out how to do that because we don't have Module#instance_method
40
- # ... and the class has overridden Object#method (=> "GET"/"POST"/etc)
41
- module Net
42
- class HTTPGenericRequest
43
- def exec(sock, ver, path, send_only = nil) #:nodoc: internal use only
44
- if @body
45
- begin
46
- send_request_with_body sock, ver, path, @body, send_only
47
- rescue ArgumentError
48
- $stderr.puts "[EY CLOUD AWARENESS GEM] Rescued from #{$!} because we thought it might have to do with aws-s3/right_aws incompatibility"
49
- send_request_with_body sock, ver, path, @body
50
- end
51
- elsif @body_stream
52
- begin
53
- send_request_with_body_stream sock, ver, path, @body_stream, send_only
54
- rescue ArgumentError
55
- $stderr.puts "[EY CLOUD AWARENESS GEM] Rescued from #{$!} because we thought it might have to do with aws-s3/right_aws incompatibility"
56
- send_request_with_body_stream sock, ver, path, @body_stream
57
- end
58
- else
59
- write_header sock, ver, path
55
+
56
+ def recursive_kill_xml_item_keys!
57
+ kill_xml_item_keys!
58
+ values.select { |v| v.is_a?(Hash) }.each do |hsh|
59
+ hsh.recursive_kill_xml_item_keys!
60
+ end
61
+ # burst thru at least one level of arrays
62
+ values.select { |v| v.is_a?(Array) }.each do |ary|
63
+ ary.each do |v|
64
+ v.recursive_kill_xml_item_keys! if v.is_a?(Hash)
60
65
  end
61
66
  end
67
+ self
68
+ end
69
+
70
+ def deep_copy
71
+ Marshal.load Marshal.dump(self)
62
72
  end
63
73
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 12
9
- version: 0.1.12
8
+ - 13
9
+ version: 0.1.13
10
10
  platform: ruby
11
11
  authors:
12
12
  - Seamus Abshere
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-07 00:00:00 -04:00
17
+ date: 2010-04-08 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -46,17 +46,17 @@ dependencies:
46
46
  type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
49
- name: right_aws
49
+ name: amazon-ec2
50
50
  prerelease: false
51
51
  requirement: &id003 !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "="
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  segments:
56
- - 1
57
- - 10
58
56
  - 0
59
- version: 1.10.0
57
+ - 9
58
+ - 10
59
+ version: 0.9.10
60
60
  type: :runtime
61
61
  version_requirements: *id003
62
62
  - !ruby/object:Gem::Dependency
@@ -73,7 +73,7 @@ dependencies:
73
73
  version: 1.2.9
74
74
  type: :development
75
75
  version_requirements: *id004
76
- description: Make your EngineYard cloud instances aware of each other.
76
+ description: Pull metadata from EC2 and EngineYard so that your EngineYard Cloud instances know about each other.
77
77
  email: seamus@abshere.net
78
78
  executables: []
79
79