ey_cloud_awareness 0.1.14 → 0.2.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.
- data/README.rdoc +69 -96
- data/Rakefile +20 -19
- data/VERSION +1 -1
- data/bin/ey_cloud_awareness +6 -0
- data/ey_cloud_awareness.gemspec +16 -16
- data/lib/ey_cloud_awareness.rb +5 -55
- data/lib/ey_cloud_awareness/capistrano_tasks.rb +80 -0
- data/lib/ey_cloud_awareness/engine_yard_cloud_instance.rb +233 -0
- data/lib/ey_cloud_awareness/hash_ext.rb +60 -0
- metadata +29 -31
- data/lib/engine_yard_cloud_instance.rb +0 -207
- data/lib/tasks/capistrano_tasks.rb +0 -111
- data/lib/tasks/ey_cloud_awareness.rake +0 -7
data/README.rdoc
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
This gem makes it a little easier to live on the EngineYard cloud:
|
4
4
|
|
5
|
-
*
|
5
|
+
* allow your instances to get information about the app_master, db_master, utility workers, etc. in its EngineYard environment
|
6
|
+
* automatically map cap tasks to all your instances
|
6
7
|
* automatically update your ssh aliases
|
7
|
-
* allow your app to get information about the cluster (aka "environment") it's running in
|
8
8
|
|
9
9
|
We use it over at http://brighterplanet.com.
|
10
10
|
|
11
|
-
==
|
11
|
+
== /etc/chef/dna.json must be readable
|
12
12
|
|
13
13
|
This gem depends on
|
14
14
|
|
@@ -16,42 +16,41 @@ This gem depends on
|
|
16
16
|
|
17
17
|
being READABLE and containing certain attributes as named by EngineYard. You might have to add:
|
18
18
|
|
19
|
-
|
19
|
+
sudo 'chmod a+r /etc/chef/dna.json'
|
20
20
|
|
21
|
-
to your
|
21
|
+
to your before_restart.rb hook.
|
22
22
|
|
23
|
-
==
|
23
|
+
== Using the commandline tool directly
|
24
24
|
|
25
|
-
|
25
|
+
Running this command only works from EngineYard AppCloud Amazon EC2 instances.
|
26
26
|
|
27
|
-
|
27
|
+
* SSH into one of your EngineYard AppCloud Amazon EC2 instances.
|
28
|
+
* Run <tt>sudo gem install ey_cloud_awareness --no-rdoc --no-ri</tt> to make sure the gem binary is installed somewhere in your path.
|
29
|
+
* Run <tt>sudo chmod a+r /etc/chef/dna.json</tt> to make sure everybody can read the EngineYard DNA information.
|
30
|
+
* Run <tt>ey_cloud_awareness</tt> and you should get back a JSON-encoded hash of instance, environment, app, and other metadata.
|
28
31
|
|
29
|
-
|
32
|
+
It returns a cleaned-up combination of Amazon EC2 and EngineYard metadata.
|
33
|
+
|
34
|
+
== Capistrano
|
35
|
+
|
36
|
+
Put this in your <tt>config/deploy.rb</tt> (or your Capfile):
|
30
37
|
|
31
38
|
# don't miss this line just because it's at the top
|
32
|
-
load "#{Gem.searcher.find('ey_cloud_awareness').full_gem_path}/lib/
|
39
|
+
load "#{Gem.searcher.find('ey_cloud_awareness').full_gem_path}/lib/ey_cloud_awareness/capistrano_tasks.rb"
|
33
40
|
|
34
41
|
task :my_production do
|
35
|
-
role :app_master, 'my_app.com'
|
36
|
-
|
37
|
-
set :deploy_to, '/data/my_app' # required
|
38
|
-
find_and_execute_task 'eyc_setup' # required
|
42
|
+
role :app_master, 'my_app.com'
|
43
|
+
find_and_execute_task 'eyc_setup' # gets a fresh list of your environment's instances and sets roles
|
39
44
|
end
|
40
45
|
|
41
46
|
task :my_staging do
|
42
|
-
role :app_master, 'staging.my_app.com'
|
43
|
-
|
44
|
-
set :deploy_to, '/data/my_app' # required
|
45
|
-
find_and_execute_task 'eyc_setup' # required
|
47
|
+
role :app_master, 'staging.my_app.com'
|
48
|
+
find_and_execute_task 'eyc_setup' # gets a fresh list of your environment's instances and sets roles
|
46
49
|
end
|
47
50
|
|
48
51
|
# add more tasks if you have more cloud environments
|
49
52
|
|
50
|
-
|
51
|
-
|
52
|
-
== Running capistrano tasks on your instances
|
53
|
-
|
54
|
-
Commands like these will work:
|
53
|
+
If you want to use EngineYard tasks like
|
55
54
|
|
56
55
|
cap my_production monit:status
|
57
56
|
cap my_production deploy:web:disable
|
@@ -59,100 +58,74 @@ Commands like these will work:
|
|
59
58
|
cap my_production passenger:restart
|
60
59
|
cap my_production nginx:restart
|
61
60
|
|
62
|
-
|
61
|
+
then you should make sure the tasks look like
|
63
62
|
|
63
|
+
[...]
|
64
|
+
require 'eycap/recipes'
|
65
|
+
ssh_options[:compression] = false
|
66
|
+
set :user, 'deploy'
|
67
|
+
set :runner, 'deploy'
|
68
|
+
[...]
|
69
|
+
|
70
|
+
task :my_production do
|
71
|
+
role :app_master, 'my_app.com'
|
72
|
+
find_and_execute_task 'eyc_setup'
|
73
|
+
set :rails_env, 'production' # required for eycap
|
74
|
+
set :deploy_to, '/data/my_app' # required for eycap too
|
75
|
+
end
|
76
|
+
|
77
|
+
task :my_staging do
|
78
|
+
role :app_master, 'staging.my_app.com'
|
79
|
+
find_and_execute_task 'eyc_setup'
|
80
|
+
set :rails_env, 'production' # required for eycap
|
81
|
+
set :deploy_to, '/data/my_app' # required for eycap too
|
82
|
+
end
|
83
|
+
|
64
84
|
== SSH into your instances
|
65
85
|
|
66
|
-
|
86
|
+
You can easily create SSH aliases like...
|
67
87
|
|
68
88
|
ssh my_production-app_master
|
69
89
|
|
70
|
-
|
90
|
+
using the following task:
|
71
91
|
|
72
92
|
cap my_production eyc:ssh
|
73
|
-
|
74
|
-
That will magically add or update a block like
|
75
|
-
|
76
|
-
# START StringReplacer my_production -- DO NOT MODIFY
|
77
|
-
|
78
|
-
# db_master
|
79
|
-
Host my_production-db_master
|
80
|
-
Hostname ec2-222-222-222-59.compute-1.amazonaws.com
|
81
|
-
User my_user
|
82
|
-
StrictHostKeyChecking no
|
83
|
-
|
84
|
-
# utility (1)
|
85
|
-
Host my_production-utility1
|
86
|
-
Hostname ec2-222-222-53-222.compute-1.amazonaws.com
|
87
|
-
User my_user
|
88
|
-
StrictHostKeyChecking no
|
89
|
-
|
90
|
-
# app_master
|
91
|
-
Host my_production-app_master
|
92
|
-
Hostname ec2-222-222-23-222.compute-1.amazonaws.com
|
93
|
-
User my_user
|
94
|
-
StrictHostKeyChecking no
|
95
|
-
|
96
|
-
# END StringReplacer my_production -- DO NOT MODIFY
|
97
|
-
|
98
|
-
Run that again as
|
99
|
-
|
100
93
|
cap my_staging eyc:ssh
|
101
94
|
|
102
|
-
|
103
|
-
|
104
|
-
# START StringReplacer my_staging -- DO NOT MODIFY
|
105
|
-
|
106
|
-
# app_master
|
107
|
-
Host my_staging-app_master
|
108
|
-
Hostname ec2-222-222-7-210.compute-1.amazonaws.com
|
109
|
-
User my_user
|
110
|
-
StrictHostKeyChecking no
|
111
|
-
|
112
|
-
# db_master
|
113
|
-
Host my_staging-db_master
|
114
|
-
Hostname ec2-222-222-52-8.compute-1.amazonaws.com
|
115
|
-
User my_user
|
116
|
-
StrictHostKeyChecking no
|
117
|
-
|
118
|
-
# END StringReplacer my_staging -- DO NOT MODIFY
|
119
|
-
|
120
|
-
This leaves you with lots of useful aliases:
|
95
|
+
That will magically update <tt>~/.ssh/config</tt> with aliases like
|
121
96
|
|
122
97
|
ssh my_production-db_master
|
123
98
|
ssh my_production-app_master
|
124
|
-
ssh my_production-
|
99
|
+
ssh my_production-app0
|
100
|
+
ssh my_production-app1
|
101
|
+
ssh my_production-app2
|
102
|
+
ssh my_production-util0
|
103
|
+
ssh my_production-util1
|
104
|
+
|
125
105
|
ssh my_staging-app_master
|
126
106
|
ssh my_staging-db_master
|
127
107
|
|
128
|
-
|
129
|
-
|
130
|
-
== Just dumping information about your instances
|
108
|
+
Unfortunately, the counters are arbitrary and so the assignment of util0 versus util1, etc. may change every time you update your <tt>.ssh/config</tt> file with this task.
|
131
109
|
|
132
110
|
Once you've done the quickstart, try:
|
133
111
|
|
134
|
-
cap my_production eyc:
|
135
|
-
cap my_production eyc:
|
136
|
-
cap my_production eyc:
|
112
|
+
cap my_production eyc:app_servers # gets a list of your app instances, including app_master
|
113
|
+
cap my_production eyc:utilities # ditto for utility instances
|
114
|
+
cap my_production eyc:db_servers # ditto for db instances
|
137
115
|
cap my_production eyc:all # gets a list of all your instances
|
138
116
|
|
139
|
-
==
|
117
|
+
== Making your instances aware of each other
|
140
118
|
|
141
|
-
|
119
|
+
You can use EngineYardCloudInstance to pull up information on other instances in an environment:
|
142
120
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
121
|
+
>> db_master = EngineYardCloudInstance.db_master
|
122
|
+
=> #<EngineYardCloudInstance:0xb5e12f70 @instance_id="i-50cf5838">
|
123
|
+
>> db_master.dns_name
|
124
|
+
=> "ec2-67-201-47-30.compute-1.amazonaws.com"
|
148
125
|
|
149
|
-
|
126
|
+
You can also inspect the whole set of metadata itself. This may change over time:
|
150
127
|
|
151
|
-
>>
|
152
|
-
=> [#<EngineYardCloudInstance:0xb5e12f70 @instance_id="i-50cf5838">, #<EngineYardCloudInstance:0xb5e12f5c @instance_id="i-dc9207b4">, #<EngineYardCloudInstance:0xb5e12f34 @instance_id="i-b2d84fda">]
|
153
|
-
>> all_app_instances.first.dns_name
|
154
|
-
=> "ec2-67-201-47-30.compute-1.amazonaws.com"
|
155
|
-
>> pp all_app_instances.first.to_hash
|
128
|
+
>> pp db_master.data
|
156
129
|
=> {:block_device_mapping=>
|
157
130
|
[{:ebs=>
|
158
131
|
{:status=>"attached",
|
@@ -193,19 +166,19 @@ Or whatever you want:
|
|
193
166
|
:dns_name=>"ec2-67-201-47-30.compute-1.amazonaws.com",
|
194
167
|
:ip_address=>"199.99.99.99",
|
195
168
|
:architecture=>"i386",
|
196
|
-
:
|
169
|
+
:role=>"db_master",
|
197
170
|
:monitoring=>{:state=>"disabled"}}
|
198
171
|
|
199
172
|
== A note on caching and network needs
|
200
173
|
|
201
174
|
I tried to be smart about caching the results of network calls.
|
202
175
|
|
203
|
-
Stuff like the
|
176
|
+
Stuff like the present instance id, which is pulled from an EC2 metadata server, is stored in
|
204
177
|
|
205
|
-
|
178
|
+
~/.ey_cloud_awareness/engine_yard_cloud_instance_id
|
206
179
|
|
207
180
|
Please let me know if this causes problems.
|
208
181
|
|
209
182
|
== Copyright
|
210
183
|
|
211
|
-
Copyright (c) 2009 Seamus Abshere. See LICENSE for details.
|
184
|
+
Copyright (c) 2009, 2010 Seamus Abshere. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -10,11 +10,11 @@ begin
|
|
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
|
-
|
14
|
-
gem.add_dependency '
|
13
|
+
gem.executables = 'ey_cloud_awareness'
|
14
|
+
gem.add_dependency 'string_replacer', '>=0.0.1'
|
15
15
|
gem.add_dependency 'activesupport', '>=2.3.4'
|
16
|
-
gem.add_dependency 'amazon-ec2', '>=0.9.
|
17
|
-
gem.add_development_dependency "rspec", ">= 1.2.9"
|
16
|
+
gem.add_dependency 'amazon-ec2', '>=0.9.15'
|
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
|
20
20
|
Jeweler::GemcutterTasks.new
|
@@ -25,21 +25,22 @@ rescue LoadError
|
|
25
25
|
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
28
|
+
# sabshere 9/21/10 disable until we actually have tests
|
29
|
+
# require 'spec/rake/spectask'
|
30
|
+
# Spec::Rake::SpecTask.new(:spec) do |spec|
|
31
|
+
# spec.libs << 'lib' << 'spec'
|
32
|
+
# spec.spec_files = FileList['spec/**/*_spec.rb']
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Spec::Rake::SpecTask.new(:rcov) do |spec|
|
36
|
+
# spec.libs << 'lib' << 'spec'
|
37
|
+
# spec.pattern = 'spec/**/*_spec.rb'
|
38
|
+
# spec.rcov = true
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# task :spec => :check_dependencies
|
42
|
+
#
|
43
|
+
# task :default => :spec
|
43
44
|
|
44
45
|
require 'rake/rdoctask'
|
45
46
|
Rake::RDocTask.new do |rdoc|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/ey_cloud_awareness.gemspec
CHANGED
@@ -5,13 +5,15 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ey_cloud_awareness}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
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-
|
12
|
+
s.date = %q{2010-09-22}
|
13
|
+
s.default_executable = %q{ey_cloud_awareness}
|
13
14
|
s.description = %q{Pull metadata from EC2 and EngineYard so that your EngineYard Cloud instances know about each other.}
|
14
15
|
s.email = %q{seamus@abshere.net}
|
16
|
+
s.executables = ["ey_cloud_awareness"]
|
15
17
|
s.extra_rdoc_files = [
|
16
18
|
"LICENSE",
|
17
19
|
"README.rdoc"
|
@@ -23,11 +25,12 @@ Gem::Specification.new do |s|
|
|
23
25
|
"README.rdoc",
|
24
26
|
"Rakefile",
|
25
27
|
"VERSION",
|
28
|
+
"bin/ey_cloud_awareness",
|
26
29
|
"ey_cloud_awareness.gemspec",
|
27
|
-
"lib/engine_yard_cloud_instance.rb",
|
28
30
|
"lib/ey_cloud_awareness.rb",
|
29
|
-
"lib/
|
30
|
-
"lib/
|
31
|
+
"lib/ey_cloud_awareness/capistrano_tasks.rb",
|
32
|
+
"lib/ey_cloud_awareness/engine_yard_cloud_instance.rb",
|
33
|
+
"lib/ey_cloud_awareness/hash_ext.rb",
|
31
34
|
"spec/ey_cloud_awareness_spec.rb",
|
32
35
|
"spec/spec.opts",
|
33
36
|
"spec/spec_helper.rb"
|
@@ -35,7 +38,7 @@ Gem::Specification.new do |s|
|
|
35
38
|
s.homepage = %q{http://github.com/seamusabshere/ey_cloud_awareness}
|
36
39
|
s.rdoc_options = ["--charset=UTF-8"]
|
37
40
|
s.require_paths = ["lib"]
|
38
|
-
s.rubygems_version = %q{1.3.
|
41
|
+
s.rubygems_version = %q{1.3.7}
|
39
42
|
s.summary = %q{Make your EngineYard cloud instances aware of each other.}
|
40
43
|
s.test_files = [
|
41
44
|
"spec/ey_cloud_awareness_spec.rb",
|
@@ -46,22 +49,19 @@ Gem::Specification.new do |s|
|
|
46
49
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
50
|
s.specification_version = 3
|
48
51
|
|
49
|
-
if Gem::Version.new(Gem::
|
50
|
-
s.add_runtime_dependency(%q<
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<string_replacer>, [">= 0.0.1"])
|
51
54
|
s.add_runtime_dependency(%q<activesupport>, [">= 2.3.4"])
|
52
|
-
s.add_runtime_dependency(%q<amazon-ec2>, [">= 0.9.
|
53
|
-
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
55
|
+
s.add_runtime_dependency(%q<amazon-ec2>, [">= 0.9.15"])
|
54
56
|
else
|
55
|
-
s.add_dependency(%q<
|
57
|
+
s.add_dependency(%q<string_replacer>, [">= 0.0.1"])
|
56
58
|
s.add_dependency(%q<activesupport>, [">= 2.3.4"])
|
57
|
-
s.add_dependency(%q<amazon-ec2>, [">= 0.9.
|
58
|
-
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
59
|
+
s.add_dependency(%q<amazon-ec2>, [">= 0.9.15"])
|
59
60
|
end
|
60
61
|
else
|
61
|
-
s.add_dependency(%q<
|
62
|
+
s.add_dependency(%q<string_replacer>, [">= 0.0.1"])
|
62
63
|
s.add_dependency(%q<activesupport>, [">= 2.3.4"])
|
63
|
-
s.add_dependency(%q<amazon-ec2>, [">= 0.9.
|
64
|
-
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
64
|
+
s.add_dependency(%q<amazon-ec2>, [">= 0.9.15"])
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
data/lib/ey_cloud_awareness.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'open-uri'
|
2
2
|
require 'set'
|
3
3
|
require 'fileutils'
|
4
|
-
require 'json'
|
5
|
-
require 'yaml'
|
6
4
|
require 'AWS' # aka amazon-ec2
|
7
5
|
require 'etc'
|
8
6
|
require 'active_support'
|
9
7
|
require 'active_support/version'
|
10
8
|
%w{
|
9
|
+
active_support/json
|
11
10
|
active_support/core_ext/string
|
12
|
-
active_support/core_ext/class/
|
11
|
+
active_support/core_ext/class/attribute_accessors
|
13
12
|
active_support/inflector/inflections
|
14
13
|
active_support/core_ext/string/inflections
|
15
14
|
active_support/core_ext/hash/keys
|
@@ -18,57 +17,8 @@ require 'active_support/version'
|
|
18
17
|
require active_support_3_requirement
|
19
18
|
end if ActiveSupport::VERSION::MAJOR == 3
|
20
19
|
|
21
|
-
require 'engine_yard_cloud_instance'
|
20
|
+
require 'ey_cloud_awareness/engine_yard_cloud_instance'
|
21
|
+
require 'ey_cloud_awareness/hash_ext'
|
22
22
|
|
23
|
-
|
24
|
-
# http://pragmatig.wordpress.com/2009/04/14/recursive-symbolize_keys/
|
25
|
-
def recursive_symbolize_keys!
|
26
|
-
symbolize_keys!
|
27
|
-
values.select { |v| v.is_a?(Hash) }.each do |hsh|
|
28
|
-
hsh.recursive_symbolize_keys!
|
29
|
-
end
|
30
|
-
# burst thru at least one level of arrays
|
31
|
-
values.select { |v| v.is_a?(Array) }.each do |ary|
|
32
|
-
ary.each do |v|
|
33
|
-
v.recursive_symbolize_keys! if v.is_a?(Hash)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
self
|
37
|
-
end
|
38
|
-
|
39
|
-
XML_ITEM_KEYS = [ :item, 'item' ]
|
40
|
-
|
41
|
-
# :sam => { :item => [{ :foo => :bar }] }
|
42
|
-
# into
|
43
|
-
# :sam => [{:foo => :bar}]
|
44
|
-
def kill_xml_item_keys!
|
45
|
-
if keys.length == 1 and XML_ITEM_KEYS.include?(keys.first)
|
46
|
-
raise ArgumentError, "You need to call kill_xml_item_keys! on { :foo => { :items => [...] } } not on { :items => [...] }"
|
47
|
-
end
|
48
|
-
keys.each do |key|
|
49
|
-
if self[key].is_a?(Hash) and self[key].keys.length == 1 and XML_ITEM_KEYS.include?(self[key].keys.first)
|
50
|
-
# self[:sam] = self[:sam]["item"] (using values.first because we don't know if it's :item or "item")
|
51
|
-
self[key] = delete(key).values.first
|
52
|
-
end
|
53
|
-
end
|
54
|
-
self
|
55
|
-
end
|
56
|
-
|
57
|
-
def recursive_kill_xml_item_keys!
|
58
|
-
kill_xml_item_keys!
|
59
|
-
values.select { |v| v.is_a?(Hash) }.each do |hsh|
|
60
|
-
hsh.recursive_kill_xml_item_keys!
|
61
|
-
end
|
62
|
-
# burst thru at least one level of arrays
|
63
|
-
values.select { |v| v.is_a?(Array) }.each do |ary|
|
64
|
-
ary.each do |v|
|
65
|
-
v.recursive_kill_xml_item_keys! if v.is_a?(Hash)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
def deep_copy
|
72
|
-
Marshal.load Marshal.dump(self)
|
73
|
-
end
|
23
|
+
module EyCloudAwareness
|
74
24
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'ey_cloud_awareness'
|
2
|
+
|
3
|
+
task :eyc_setup, :roles => :app_master do
|
4
|
+
# pull JSON-encoded metadata
|
5
|
+
output = capture('ey_cloud_awareness').gsub /\s+/, ' '
|
6
|
+
if /(\{.*\})/.match(output)
|
7
|
+
json_str = $1
|
8
|
+
begin
|
9
|
+
set :eyc_proxy, EngineYardCloudInstance.from_json(json_str)
|
10
|
+
rescue
|
11
|
+
$stderr.puts "[EY CLOUD AWARENESS GEM] Couldn't parse JSON, so just dumping what we got"
|
12
|
+
$stderr.puts json_str
|
13
|
+
raise $!
|
14
|
+
end
|
15
|
+
else
|
16
|
+
$stderr.puts "[EY CLOUD AWARENESS GEM] Didn't get JSON we recognized back, just dumping what we got"
|
17
|
+
$stderr.puts output
|
18
|
+
raise
|
19
|
+
end
|
20
|
+
|
21
|
+
# now set up roles
|
22
|
+
# role :app_master is already set
|
23
|
+
role :db_master, eyc_proxy.db_master.dns_name
|
24
|
+
eyc_proxy.app_servers.each do |i|
|
25
|
+
role :app, i.dns_name
|
26
|
+
role :web, i.dns_name
|
27
|
+
end
|
28
|
+
eyc_proxy.db_servers.each do |i|
|
29
|
+
role :db, i.dns_name
|
30
|
+
end
|
31
|
+
eyc_proxy.utilities.each do |i|
|
32
|
+
role :util, i.dns_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
namespace :eyc do
|
37
|
+
%w{ app_servers db_servers utilities all }.each do |name|
|
38
|
+
task name, :roles => :app_master do
|
39
|
+
require 'pp'
|
40
|
+
pp eyc_proxy.send(name).map(&:to_hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task :ssh, :roles => :app_master do
|
45
|
+
require 'string_replacer'
|
46
|
+
replacement = []
|
47
|
+
counters = Hash.new(0)
|
48
|
+
eyc_proxy.with_roles.each do |instance|
|
49
|
+
case instance.role
|
50
|
+
when 'db_master'
|
51
|
+
explanation = ''
|
52
|
+
shorthand = 'db_master'
|
53
|
+
when 'app_master'
|
54
|
+
explanation = ''
|
55
|
+
shorthand = 'app_master'
|
56
|
+
when 'solo'
|
57
|
+
explanation = ''
|
58
|
+
shorthand = 'solo'
|
59
|
+
else
|
60
|
+
explanation = " (#{counters[instance.role]})"
|
61
|
+
shorthand = "#{instance.role}#{counters[instance.role]}"
|
62
|
+
counters[instance.role] += 1
|
63
|
+
end
|
64
|
+
replacement << %{
|
65
|
+
# #{instance.role}#{explanation}
|
66
|
+
Host #{eyc_proxy.environment['name']}-#{shorthand}
|
67
|
+
Hostname #{instance.dns_name}
|
68
|
+
User #{instance.user['username']}
|
69
|
+
StrictHostKeyChecking no
|
70
|
+
}
|
71
|
+
end
|
72
|
+
string = replacement.join
|
73
|
+
ssh_config_path = File.expand_path("~/.ssh/config")
|
74
|
+
r = StringReplacer.new ssh_config_path
|
75
|
+
r.replace! string, eyc_proxy.environment['name'], nil
|
76
|
+
|
77
|
+
$stderr.puts "[EY CLOUD AWARENESS GEM] Added this to #{ssh_config_path}"
|
78
|
+
$stderr.puts string
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
class EngineYardCloudInstance
|
2
|
+
HOMEDIR = File.expand_path "~#{Etc.getpwuid.name}"
|
3
|
+
PRESENT_INSTANCE_ID_CACHE_PATH = "#{HOMEDIR}/.ey_cloud_awareness/engine_yard_cloud_instance_id"
|
4
|
+
PRESENT_SECURITY_GROUP_CACHE_PATH = "#{HOMEDIR}/.ey_cloud_awareness/engine_yard_cloud_security_group"
|
5
|
+
INSTANCES_CACHE_PATH = "#{HOMEDIR}/.ey_cloud_awareness/engine_yard_cloud_ec2_instances.json"
|
6
|
+
DNA_PATH = '/etc/chef/dna.json'
|
7
|
+
|
8
|
+
attr_reader :instance_id
|
9
|
+
def initialize(instance_id)
|
10
|
+
@instance_id = instance_id.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear
|
14
|
+
self.class.clear
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
data.merge 'environment' => environment,
|
19
|
+
'apps' => apps,
|
20
|
+
'users' => users
|
21
|
+
end
|
22
|
+
|
23
|
+
def app
|
24
|
+
raise "[EY CLOUD AWARENESS GEM] There is more than one app on this instance, so you can't use the #app method. Use #users.first (etc.) instead." if apps.length > 1
|
25
|
+
apps.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def user
|
29
|
+
raise "[EY CLOUD AWARENESS GEM] There is more than one user on this instance, so you can't use the #user method. Use #users.first (etc.) instead." if users.length > 1
|
30
|
+
users.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def as_json(*)
|
34
|
+
to_hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def data
|
38
|
+
self.class.data['instances'][instance_id]
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(name, *args, &block)
|
42
|
+
if data and data.has_key?(name.to_s)
|
43
|
+
data[name.to_s]
|
44
|
+
elsif self.class.data and self.class.data.has_key?(name.to_s)
|
45
|
+
self.class.data[name.to_s]
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ==(other)
|
52
|
+
self.instance_id == other.instance_id
|
53
|
+
end
|
54
|
+
|
55
|
+
cattr_accessor :proxy
|
56
|
+
cattr_accessor :data_cache
|
57
|
+
cattr_accessor :dna_cache
|
58
|
+
|
59
|
+
class << self
|
60
|
+
# sabshere 9/21/10 from engineyard gem cli.rb
|
61
|
+
# def ssh_host_filter(opts)
|
62
|
+
# return lambda {|instance| true } if opts[:all]
|
63
|
+
# return lambda {|instance| %w(solo app app_master ).include?(instance.role) } if opts[:app_servers]
|
64
|
+
# return lambda {|instance| %w(solo db_master db_slave).include?(instance.role) } if opts[:db_servers ]
|
65
|
+
# return lambda {|instance| %w(solo db_master ).include?(instance.role) } if opts[:db_master ]
|
66
|
+
# return lambda {|instance| %w(db_slave ).include?(instance.role) } if opts[:db_slaves ]
|
67
|
+
# return lambda {|instance| %w(util ).include?(instance.role) &&
|
68
|
+
# opts[:utilities].include?(instance.name) } if opts[:utilities ]
|
69
|
+
# return lambda {|instance| %w(solo app_master ).include?(instance.role) }
|
70
|
+
# end
|
71
|
+
|
72
|
+
def all
|
73
|
+
data['instances'].map { |instance_id, _| new instance_id }
|
74
|
+
end
|
75
|
+
|
76
|
+
def app_servers
|
77
|
+
find_all_by_instance_roles 'app', 'app_master', 'solo'
|
78
|
+
end
|
79
|
+
|
80
|
+
def db_servers
|
81
|
+
find_all_by_instance_roles 'db_master', 'db_slave', 'solo'
|
82
|
+
end
|
83
|
+
|
84
|
+
def db_master
|
85
|
+
find_all_by_instance_roles('db_master').first || find_all_by_instance_roles('solo').first
|
86
|
+
end
|
87
|
+
|
88
|
+
def db_slaves
|
89
|
+
find_all_by_instance_roles 'db_slave'
|
90
|
+
end
|
91
|
+
|
92
|
+
def utilities
|
93
|
+
find_all_by_instance_roles 'util'
|
94
|
+
end
|
95
|
+
|
96
|
+
def app_master
|
97
|
+
find_all_by_instance_roles('app_master').first || find_all_by_instance_roles('solo').first
|
98
|
+
end
|
99
|
+
|
100
|
+
def with_roles
|
101
|
+
all.select { |i| i.role.present? }
|
102
|
+
end
|
103
|
+
|
104
|
+
def present
|
105
|
+
new present_instance_id
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_by_instance_id(instance_id)
|
109
|
+
new instance_id
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_all_by_instance_roles(*args)
|
113
|
+
data['instances'].select { |_, instance| Array.wrap(args).map(&:to_s).include? instance['role'] }.map { |instance_id, _| new instance_id }
|
114
|
+
end
|
115
|
+
|
116
|
+
def clear
|
117
|
+
raise "[EY CLOUD AWARENESS GEM] Can't clear if we used from_hash" if proxy?
|
118
|
+
self.data_cache = nil
|
119
|
+
self.dna_cache = nil
|
120
|
+
ec2_instances true
|
121
|
+
present_security_group true
|
122
|
+
present_instance_id true
|
123
|
+
end
|
124
|
+
|
125
|
+
def data
|
126
|
+
return data_cache if data_cache.is_a? Hash
|
127
|
+
raise "[EY CLOUD AWARENESS GEM] Can't calculate data if we used from_hash" if proxy?
|
128
|
+
self.data_cache = Hash.new
|
129
|
+
data_cache['instances'] = mixed_instances
|
130
|
+
data_cache['environment'] = dna['engineyard']['environment'].except('instances', 'apps')
|
131
|
+
data_cache['apps'] = dna['engineyard']['environment']['apps']
|
132
|
+
data_cache['users'] = dna['users']
|
133
|
+
data_cache['group_id'] = present_security_group
|
134
|
+
data_cache
|
135
|
+
end
|
136
|
+
|
137
|
+
def proxy?
|
138
|
+
!!proxy
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_hash
|
142
|
+
data
|
143
|
+
end
|
144
|
+
|
145
|
+
def as_json(*)
|
146
|
+
to_hash
|
147
|
+
end
|
148
|
+
|
149
|
+
def from_json(str)
|
150
|
+
from_hash ActiveSupport::JSON.decode(str)
|
151
|
+
end
|
152
|
+
|
153
|
+
def from_hash(hash)
|
154
|
+
self.proxy = true
|
155
|
+
self.data_cache = hash
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
def method_missing(name, *args, &block)
|
160
|
+
if data and data.has_key?(name.to_s)
|
161
|
+
data[name.to_s]
|
162
|
+
else
|
163
|
+
super
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def mixed_instances
|
168
|
+
ec2_instances.inject(Hash.new) do |memo, ec2_instance|
|
169
|
+
instance_id = ec2_instance['instance_id']
|
170
|
+
mixed_instance = dna_instances.detect { |dna_instance| dna_instance['id'] == instance_id }.merge ec2_instance
|
171
|
+
memo[instance_id] = mixed_instance
|
172
|
+
memo
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def dna_instances
|
177
|
+
dna['engineyard']['environment']['instances']
|
178
|
+
end
|
179
|
+
|
180
|
+
def dna
|
181
|
+
raise "[EY CLOUD AWARENESS GEM] Can't see DNA if we used from_hash" if proxy?
|
182
|
+
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)
|
183
|
+
self.dna_cache ||= ActiveSupport::JSON.decode(IO.read(DNA_PATH))
|
184
|
+
end
|
185
|
+
|
186
|
+
def present_instance_id(refresh = false)
|
187
|
+
raise "[EY CLOUD AWARENESS GEM] Can't call present_instance_id if we used from_hash" if proxy?
|
188
|
+
if refresh or !File.readable?(PRESENT_INSTANCE_ID_CACHE_PATH)
|
189
|
+
@_present_instance_id = open("http://169.254.169.254/latest/meta-data/instance-id").gets
|
190
|
+
begin
|
191
|
+
FileUtils.mkdir_p File.dirname(PRESENT_INSTANCE_ID_CACHE_PATH)
|
192
|
+
File.open(PRESENT_INSTANCE_ID_CACHE_PATH, 'w') { |f| f.write @_present_instance_id }
|
193
|
+
rescue Errno::EACCES
|
194
|
+
$stderr.puts "[EY CLOUD AWARENESS GEM] Not caching present instance because #{PRESENT_INSTANCE_ID_CACHE_PATH} can't be written to"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
@_present_instance_id ||= IO.read(PRESENT_INSTANCE_ID_CACHE_PATH)
|
198
|
+
end
|
199
|
+
|
200
|
+
def present_security_group(refresh = false)
|
201
|
+
raise "[EY CLOUD AWARENESS GEM] Can't call present_security_group if we used from_hash" if proxy?
|
202
|
+
if refresh or !File.readable?(PRESENT_SECURITY_GROUP_CACHE_PATH)
|
203
|
+
@_present_security_group = open('http://169.254.169.254/latest/meta-data/security-groups').gets
|
204
|
+
raise "[EY CLOUD AWARENESS GEM] Don't know how to deal with (possibly) multiple security group: #{@_present_security_group}" if @_present_security_group =~ /,;/
|
205
|
+
begin
|
206
|
+
FileUtils.mkdir_p File.dirname(PRESENT_SECURITY_GROUP_CACHE_PATH)
|
207
|
+
File.open(PRESENT_SECURITY_GROUP_CACHE_PATH, 'w') { |f| f.write @_present_security_group }
|
208
|
+
rescue Errno::EACCES
|
209
|
+
$stderr.puts "[EY CLOUD AWARENESS GEM] Not caching present security group because #{PRESENT_SECURITY_GROUP_CACHE_PATH} can't be written to"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
@_present_security_group ||= IO.read(PRESENT_SECURITY_GROUP_CACHE_PATH)
|
213
|
+
end
|
214
|
+
|
215
|
+
def ec2_instances(refresh = false)
|
216
|
+
raise "[EY CLOUD AWARENESS GEM] Can't call ec2_instances if we used from_hash" if proxy?
|
217
|
+
if refresh or !File.readable?(INSTANCES_CACHE_PATH)
|
218
|
+
ec2 = AWS::EC2::Base.new :access_key_id => dna['aws_secret_id'], :secret_access_key => dna['aws_secret_key']
|
219
|
+
@_ec2_instances = ec2.describe_instances
|
220
|
+
@_ec2_instances.recursive_kill_xml_item_keys!
|
221
|
+
@_ec2_instances = @_ec2_instances['reservationSet'].select { |hash| present_security_group.include? hash['groupSet'].first['groupId'] }
|
222
|
+
@_ec2_instances.map! { |hash| hash['instancesSet'].first.recursive_underscore_keys! }
|
223
|
+
begin
|
224
|
+
FileUtils.mkdir_p File.dirname(INSTANCES_CACHE_PATH)
|
225
|
+
File.open(INSTANCES_CACHE_PATH, 'w') { |f| f.write @_ec2_instances.to_json }
|
226
|
+
rescue Errno::EACCES
|
227
|
+
$stderr.puts "[EY CLOUD AWARENESS GEM] Not caching instance data because #{INSTANCES_CACHE_PATH} can't be written to"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
@_ec2_instances ||= ActiveSupport::JSON.decode(IO.read(INSTANCES_CACHE_PATH))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module EyCloudAwareness
|
2
|
+
module HashExt
|
3
|
+
# http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Keys.html
|
4
|
+
def underscore_keys!
|
5
|
+
keys.each do |key|
|
6
|
+
self[key.to_s.underscore] = delete(key)
|
7
|
+
end
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
# http://pragmatig.wordpress.com/2009/04/14/recursive-symbolize_keys/
|
12
|
+
def recursive_underscore_keys!
|
13
|
+
underscore_keys!
|
14
|
+
values.select { |v| v.is_a?(Hash) }.each do |hsh|
|
15
|
+
hsh.recursive_underscore_keys!
|
16
|
+
end
|
17
|
+
# burst thru at least one level of arrays
|
18
|
+
values.select { |v| v.is_a?(Array) }.each do |ary|
|
19
|
+
ary.each do |v|
|
20
|
+
v.recursive_underscore_keys! if v.is_a?(Hash)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
XML_ITEM_KEYS = [ :item, 'item' ]
|
27
|
+
|
28
|
+
# :sam => { :item => [{ :foo => :bar }] }
|
29
|
+
# into
|
30
|
+
# :sam => [{:foo => :bar}]
|
31
|
+
def kill_xml_item_keys!
|
32
|
+
if keys.length == 1 and XML_ITEM_KEYS.include?(keys.first)
|
33
|
+
raise ArgumentError, "You need to call kill_xml_item_keys! on { :foo => { :items => [...] } } not on { :items => [...] }"
|
34
|
+
end
|
35
|
+
keys.each do |key|
|
36
|
+
if self[key].is_a?(Hash) and self[key].keys.length == 1 and XML_ITEM_KEYS.include?(self[key].keys.first)
|
37
|
+
# self[:sam] = self[:sam]["item"] (using values.first because we don't know if it's :item or "item")
|
38
|
+
self[key] = delete(key).values.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def recursive_kill_xml_item_keys!
|
45
|
+
kill_xml_item_keys!
|
46
|
+
values.select { |v| v.is_a?(Hash) }.each do |hsh|
|
47
|
+
hsh.recursive_kill_xml_item_keys!
|
48
|
+
end
|
49
|
+
# burst thru at least one level of arrays
|
50
|
+
values.select { |v| v.is_a?(Array) }.each do |ary|
|
51
|
+
ary.each do |v|
|
52
|
+
v.recursive_kill_xml_item_keys! if v.is_a?(Hash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Hash.send :include, EyCloudAwareness::HashExt
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ey_cloud_awareness
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Seamus Abshere
|
@@ -14,30 +15,34 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
-
default_executable:
|
18
|
+
date: 2010-09-22 00:00:00 -05:00
|
19
|
+
default_executable: ey_cloud_awareness
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
|
-
name:
|
22
|
+
name: string_replacer
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 29
|
27
30
|
segments:
|
31
|
+
- 0
|
32
|
+
- 0
|
28
33
|
- 1
|
29
|
-
|
30
|
-
- 3
|
31
|
-
version: 1.2.3
|
34
|
+
version: 0.0.1
|
32
35
|
type: :runtime
|
33
36
|
version_requirements: *id001
|
34
37
|
- !ruby/object:Gem::Dependency
|
35
38
|
name: activesupport
|
36
39
|
prerelease: false
|
37
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
38
42
|
requirements:
|
39
43
|
- - ">="
|
40
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 11
|
41
46
|
segments:
|
42
47
|
- 2
|
43
48
|
- 3
|
@@ -49,34 +54,22 @@ dependencies:
|
|
49
54
|
name: amazon-ec2
|
50
55
|
prerelease: false
|
51
56
|
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
52
58
|
requirements:
|
53
59
|
- - ">="
|
54
60
|
- !ruby/object:Gem::Version
|
61
|
+
hash: 37
|
55
62
|
segments:
|
56
63
|
- 0
|
57
64
|
- 9
|
58
|
-
-
|
59
|
-
version: 0.9.
|
65
|
+
- 15
|
66
|
+
version: 0.9.15
|
60
67
|
type: :runtime
|
61
68
|
version_requirements: *id003
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: rspec
|
64
|
-
prerelease: false
|
65
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
segments:
|
70
|
-
- 1
|
71
|
-
- 2
|
72
|
-
- 9
|
73
|
-
version: 1.2.9
|
74
|
-
type: :development
|
75
|
-
version_requirements: *id004
|
76
69
|
description: Pull metadata from EC2 and EngineYard so that your EngineYard Cloud instances know about each other.
|
77
70
|
email: seamus@abshere.net
|
78
|
-
executables:
|
79
|
-
|
71
|
+
executables:
|
72
|
+
- ey_cloud_awareness
|
80
73
|
extensions: []
|
81
74
|
|
82
75
|
extra_rdoc_files:
|
@@ -89,11 +82,12 @@ files:
|
|
89
82
|
- README.rdoc
|
90
83
|
- Rakefile
|
91
84
|
- VERSION
|
85
|
+
- bin/ey_cloud_awareness
|
92
86
|
- ey_cloud_awareness.gemspec
|
93
|
-
- lib/engine_yard_cloud_instance.rb
|
94
87
|
- lib/ey_cloud_awareness.rb
|
95
|
-
- lib/
|
96
|
-
- lib/
|
88
|
+
- lib/ey_cloud_awareness/capistrano_tasks.rb
|
89
|
+
- lib/ey_cloud_awareness/engine_yard_cloud_instance.rb
|
90
|
+
- lib/ey_cloud_awareness/hash_ext.rb
|
97
91
|
- spec/ey_cloud_awareness_spec.rb
|
98
92
|
- spec/spec.opts
|
99
93
|
- spec/spec_helper.rb
|
@@ -107,23 +101,27 @@ rdoc_options:
|
|
107
101
|
require_paths:
|
108
102
|
- lib
|
109
103
|
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
110
105
|
requirements:
|
111
106
|
- - ">="
|
112
107
|
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
113
109
|
segments:
|
114
110
|
- 0
|
115
111
|
version: "0"
|
116
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
117
114
|
requirements:
|
118
115
|
- - ">="
|
119
116
|
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
120
118
|
segments:
|
121
119
|
- 0
|
122
120
|
version: "0"
|
123
121
|
requirements: []
|
124
122
|
|
125
123
|
rubyforge_project:
|
126
|
-
rubygems_version: 1.3.
|
124
|
+
rubygems_version: 1.3.7
|
127
125
|
signing_key:
|
128
126
|
specification_version: 3
|
129
127
|
summary: Make your EngineYard cloud instances aware of each other.
|
@@ -1,207 +0,0 @@
|
|
1
|
-
class EngineYardCloudInstance
|
2
|
-
homedir = File.expand_path "~#{Etc.getpwuid.name}"
|
3
|
-
CURRENT_INSTANCE_ID_CACHE_PATH = "#{homedir}/.ey_cloud_awareness/engine_yard_cloud_instance_id"
|
4
|
-
CURRENT_SECURITY_GROUP_CACHE_PATH = "#{homedir}/.ey_cloud_awareness/engine_yard_cloud_security_group"
|
5
|
-
INSTANCE_DESCRIPTIONS_CACHE_PATH = "#{homedir}/.ey_cloud_awareness/engine_yard_cloud_ec2_instance_descriptions.yml"
|
6
|
-
DNA_PATH = '/etc/chef/dna.json'
|
7
|
-
|
8
|
-
attr_reader :instance_id
|
9
|
-
def initialize(instance_id)
|
10
|
-
@instance_id = instance_id.to_s
|
11
|
-
end
|
12
|
-
|
13
|
-
def valid?
|
14
|
-
data.present?
|
15
|
-
end
|
16
|
-
|
17
|
-
def clear
|
18
|
-
self.class.clear
|
19
|
-
end
|
20
|
-
|
21
|
-
def to_hash
|
22
|
-
data.deep_copy
|
23
|
-
end
|
24
|
-
|
25
|
-
def data
|
26
|
-
self.class.data[instance_id.to_sym]
|
27
|
-
end
|
28
|
-
|
29
|
-
def method_missing(name, *args, &block)
|
30
|
-
name = name.to_sym
|
31
|
-
if data and data.has_key?(name)
|
32
|
-
data[name]
|
33
|
-
else
|
34
|
-
super
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def ==(other)
|
39
|
-
self.instance_id == other.instance_id
|
40
|
-
end
|
41
|
-
|
42
|
-
class_inheritable_accessor :proxy
|
43
|
-
|
44
|
-
class << self
|
45
|
-
def to_hash
|
46
|
-
clear
|
47
|
-
data.deep_copy
|
48
|
-
end
|
49
|
-
|
50
|
-
def from_hash(hash)
|
51
|
-
self.proxy = true
|
52
|
-
@_data = hash.recursive_symbolize_keys!
|
53
|
-
self
|
54
|
-
end
|
55
|
-
|
56
|
-
def environment
|
57
|
-
@_environment ||= first.environment
|
58
|
-
end
|
59
|
-
|
60
|
-
def app_master
|
61
|
-
find_all_by_instance_roles(:app_master).first || find_all_by_instance_roles(:solo).first
|
62
|
-
end
|
63
|
-
|
64
|
-
def db_master
|
65
|
-
find_all_by_instance_roles(:db_master).first || find_all_by_instance_roles(:solo).first
|
66
|
-
end
|
67
|
-
|
68
|
-
def app
|
69
|
-
find_all_by_instance_roles :app, :app_master, :solo
|
70
|
-
end
|
71
|
-
|
72
|
-
def db
|
73
|
-
find_all_by_instance_roles :db_master, :db_slave, :solo
|
74
|
-
end
|
75
|
-
|
76
|
-
def utility
|
77
|
-
find_all_by_instance_roles :utility
|
78
|
-
end
|
79
|
-
|
80
|
-
def all
|
81
|
-
data.map { |k, _| new k }
|
82
|
-
end
|
83
|
-
|
84
|
-
def with_roles
|
85
|
-
all.reject { |i| i.instance_role == 'unknown' }
|
86
|
-
end
|
87
|
-
|
88
|
-
def current
|
89
|
-
new cached_current_instance_id
|
90
|
-
end
|
91
|
-
|
92
|
-
def first
|
93
|
-
new data.to_a.first.first
|
94
|
-
end
|
95
|
-
|
96
|
-
def find_by_instance_id(instance_id)
|
97
|
-
new instance_id
|
98
|
-
end
|
99
|
-
|
100
|
-
def find_all_by_instance_roles(*args)
|
101
|
-
data.select { |_, v| Array.wrap(args).map(&:to_s).include? v[:instance_role] }.map { |k, _| new k }
|
102
|
-
end
|
103
|
-
|
104
|
-
def clear
|
105
|
-
raise "[EY CLOUD AWARENESS GEM] Can't clear if we used from_hash" if self.proxy
|
106
|
-
@_data = nil
|
107
|
-
@_dna = nil
|
108
|
-
cached_ec2_instance_descriptions true
|
109
|
-
cached_current_security_group true
|
110
|
-
cached_current_instance_id true
|
111
|
-
end
|
112
|
-
|
113
|
-
def data
|
114
|
-
return @_data if @_data
|
115
|
-
raise "[EY CLOUD AWARENESS GEM] Can't calculate data if we used from_hash" if self.proxy
|
116
|
-
@_data = Hash.new
|
117
|
-
cached_ec2_instance_descriptions.each do |ec2_instance_description|
|
118
|
-
@_data[ec2_instance_description['instanceId']] ||= Hash.new
|
119
|
-
member = @_data[ec2_instance_description['instanceId']]
|
120
|
-
# using instance as a pointer
|
121
|
-
if dna[:instance_role] == 'solo'
|
122
|
-
member[:instance_role] = 'solo'
|
123
|
-
elsif dna[:db_host] == ec2_instance_description['dnsName'] or dna[:db_host] == ec2_instance_description['privateDnsName']
|
124
|
-
member[:instance_role] = 'db_master'
|
125
|
-
elsif Array.wrap(dna[:db_slaves]).include? ec2_instance_description['privateDnsName']
|
126
|
-
member[:instance_role] = 'db_slave'
|
127
|
-
elsif Array.wrap(dna[:utility_instances]).include? ec2_instance_description['privateDnsName']
|
128
|
-
member[:instance_role] = 'utility'
|
129
|
-
elsif dna[:master_app_server][:private_dns_name] == ec2_instance_description['privateDnsName']
|
130
|
-
member[:instance_role] = 'app_master'
|
131
|
-
elsif ec2_instance_description['instanceState']['name'] == 'running'
|
132
|
-
member[:instance_role] = 'app'
|
133
|
-
else
|
134
|
-
member[:instance_role] = 'unknown'
|
135
|
-
end
|
136
|
-
member[:group_id] = cached_current_security_group
|
137
|
-
member[:users] = dna[:users]
|
138
|
-
member[:environment] = dna[:environment]
|
139
|
-
@_environment ||= dna[:environment]
|
140
|
-
|
141
|
-
ec2_instance_description.each do |raw_k, raw_v|
|
142
|
-
k = raw_k.underscore.to_sym
|
143
|
-
next if member.keys.include? k
|
144
|
-
member[k] = raw_v
|
145
|
-
end
|
146
|
-
end
|
147
|
-
@_data.recursive_symbolize_keys!
|
148
|
-
@_data
|
149
|
-
end
|
150
|
-
|
151
|
-
def dna
|
152
|
-
raise "[EY CLOUD AWARENESS GEM] Can't see DNA if we used from_hash" if self.proxy
|
153
|
-
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)
|
154
|
-
@_dna ||= JSON.load(IO.read(DNA_PATH)).recursive_symbolize_keys!
|
155
|
-
end
|
156
|
-
|
157
|
-
private
|
158
|
-
|
159
|
-
def cached_current_instance_id(refresh = false)
|
160
|
-
raise "[EY CLOUD AWARENESS GEM] Can't call current_instance_id if we used from_hash" if self.proxy
|
161
|
-
if refresh or !File.readable?(CURRENT_INSTANCE_ID_CACHE_PATH)
|
162
|
-
@_cached_current_instance_id = open("http://169.254.169.254/latest/meta-data/instance-id").gets
|
163
|
-
begin
|
164
|
-
FileUtils.mkdir_p File.dirname(CURRENT_INSTANCE_ID_CACHE_PATH)
|
165
|
-
File.open(CURRENT_INSTANCE_ID_CACHE_PATH, 'w') { |f| f.write @_cached_current_instance_id }
|
166
|
-
rescue Errno::EACCES
|
167
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] Not caching current instance because #{CURRENT_INSTANCE_ID_CACHE_PATH} can't be written to"
|
168
|
-
end
|
169
|
-
end
|
170
|
-
@_cached_current_instance_id ||= IO.read(CURRENT_INSTANCE_ID_CACHE_PATH)
|
171
|
-
end
|
172
|
-
|
173
|
-
|
174
|
-
def cached_current_security_group(refresh = false)
|
175
|
-
raise "[EY CLOUD AWARENESS GEM] Can't call current_security_group if we used from_hash" if self.proxy
|
176
|
-
if refresh or !File.readable?(CURRENT_SECURITY_GROUP_CACHE_PATH)
|
177
|
-
@_cached_current_security_group = open('http://169.254.169.254/latest/meta-data/security-groups').gets
|
178
|
-
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 =~ /,;/
|
179
|
-
begin
|
180
|
-
FileUtils.mkdir_p File.dirname(CURRENT_SECURITY_GROUP_CACHE_PATH)
|
181
|
-
File.open(CURRENT_SECURITY_GROUP_CACHE_PATH, 'w') { |f| f.write @_cached_current_security_group }
|
182
|
-
rescue Errno::EACCES
|
183
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] Not caching current security group because #{CURRENT_SECURITY_GROUP_CACHE_PATH} can't be written to"
|
184
|
-
end
|
185
|
-
end
|
186
|
-
@_cached_current_security_group ||= IO.read(CURRENT_SECURITY_GROUP_CACHE_PATH)
|
187
|
-
end
|
188
|
-
|
189
|
-
def cached_ec2_instance_descriptions(refresh = false)
|
190
|
-
raise "[EY CLOUD AWARENESS GEM] Can't call cached_ec2_instance_descriptions if we used from_hash" if self.proxy
|
191
|
-
if refresh or !File.readable?(INSTANCE_DESCRIPTIONS_CACHE_PATH)
|
192
|
-
ec2 = AWS::EC2::Base.new :access_key_id => dna[:aws_secret_id], :secret_access_key => dna[:aws_secret_key]
|
193
|
-
@_cached_ec2_instance_descriptions = ec2.describe_instances
|
194
|
-
@_cached_ec2_instance_descriptions.recursive_kill_xml_item_keys!
|
195
|
-
@_cached_ec2_instance_descriptions = @_cached_ec2_instance_descriptions['reservationSet'].select { |hash| cached_current_security_group.include? hash['groupSet'].first['groupId'] }
|
196
|
-
@_cached_ec2_instance_descriptions.map! { |hash| hash['instancesSet'].first }
|
197
|
-
begin
|
198
|
-
FileUtils.mkdir_p File.dirname(INSTANCE_DESCRIPTIONS_CACHE_PATH)
|
199
|
-
File.open(INSTANCE_DESCRIPTIONS_CACHE_PATH, 'w') { |f| f.write @_cached_ec2_instance_descriptions.to_yaml }
|
200
|
-
rescue Errno::EACCES
|
201
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] Not caching instance data because #{INSTANCE_DESCRIPTIONS_CACHE_PATH} can't be written to"
|
202
|
-
end
|
203
|
-
end
|
204
|
-
@_cached_ec2_instance_descriptions ||= YAML.load(IO.read(INSTANCE_DESCRIPTIONS_CACHE_PATH))
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
@@ -1,111 +0,0 @@
|
|
1
|
-
require 'ey_cloud_awareness'
|
2
|
-
require 'pp'
|
3
|
-
|
4
|
-
task :eyc_setup, :roles => :app_master do
|
5
|
-
version = Gem.searcher.find('ey_cloud_awareness').version.to_s
|
6
|
-
begin
|
7
|
-
run "gem list ey_cloud_awareness --installed --version #{version}"
|
8
|
-
rescue
|
9
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] app_master doesn't have ey_cloud_awareness --version #{version} installed. You need to have the exact same version installed."
|
10
|
-
raise $!
|
11
|
-
end
|
12
|
-
|
13
|
-
upload File.expand_path(File.join(File.dirname(__FILE__), 'ey_cloud_awareness.rake')), "#{deploy_to}/current/lib/tasks/ey_cloud_awareness.rake"
|
14
|
-
|
15
|
-
output = capture("cd #{deploy_to}/current && rake --silent eyc:to_json RAILS_ENV=#{rails_env}").gsub(/\s+/, ' ')
|
16
|
-
if /(\{.*\})/.match(output)
|
17
|
-
begin
|
18
|
-
set :eyc_proxy, EngineYardCloudInstance.from_hash(ActiveSupport::JSON.decode($1))
|
19
|
-
rescue
|
20
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] Couldn't parse JSON, so just dumping what we got"
|
21
|
-
$stderr.puts $1
|
22
|
-
raise $!
|
23
|
-
end
|
24
|
-
else
|
25
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] Didn't get JSON we recognized back, just dumping what we got"
|
26
|
-
$stderr.puts output
|
27
|
-
raise
|
28
|
-
end
|
29
|
-
role :db_master, eyc_proxy.db_master.dns_name
|
30
|
-
eyc_proxy.app.each do |i|
|
31
|
-
role :app, i.dns_name
|
32
|
-
role :web, i.dns_name # assuming that you've got nginx on all of your instances
|
33
|
-
end
|
34
|
-
eyc_proxy.db.each { |i| role :db, i.dns_name }
|
35
|
-
end
|
36
|
-
|
37
|
-
namespace :eyc do
|
38
|
-
%w{ app db utility all first }.each do |name|
|
39
|
-
task name, :roles => :app_master do
|
40
|
-
pp eyc_proxy.send(name).map(&:to_hash)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Used by the eyc:ssh cap task to insert host information into ~/.ssh/config
|
46
|
-
class StringReplacer
|
47
|
-
NEWLINE = "AijQA6tD1wkWqgvLzXD"
|
48
|
-
START_MARKER = '# START StringReplacer %s -- DO NOT MODIFY'
|
49
|
-
END_MARKER = "# END StringReplacer %s -- DO NOT MODIFY#{NEWLINE}"
|
50
|
-
|
51
|
-
attr_accessor :path
|
52
|
-
def initialize(path)
|
53
|
-
@path = path
|
54
|
-
end
|
55
|
-
|
56
|
-
def replace!(replacement, id = 1)
|
57
|
-
new_path = "#{path}.new"
|
58
|
-
backup_path = "#{path}.bak"
|
59
|
-
current_start_marker = START_MARKER % id.to_s
|
60
|
-
current_end_marker = END_MARKER % id.to_s
|
61
|
-
replacement_with_markers = current_start_marker + NEWLINE + replacement + NEWLINE + current_end_marker
|
62
|
-
text = IO.read(path).gsub("\n", NEWLINE)
|
63
|
-
if text.include? current_start_marker
|
64
|
-
text.gsub! /#{Regexp.escape current_start_marker}.*#{Regexp.escape current_end_marker}/, replacement_with_markers
|
65
|
-
else
|
66
|
-
text << NEWLINE << replacement_with_markers
|
67
|
-
end
|
68
|
-
text.gsub! NEWLINE, "\n"
|
69
|
-
File.open(new_path, 'w') { |f| f.write text }
|
70
|
-
FileUtils.mv path, backup_path
|
71
|
-
FileUtils.mv new_path, path
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
namespace :eyc do
|
76
|
-
task :ssh, :roles => :app_master do
|
77
|
-
replacement = []
|
78
|
-
counter = 0
|
79
|
-
eyc_proxy.with_roles.each do |instance|
|
80
|
-
case instance.instance_role
|
81
|
-
when 'db_master'
|
82
|
-
explanation = ''
|
83
|
-
shorthand = 'db_master'
|
84
|
-
when 'app_master'
|
85
|
-
explanation = ''
|
86
|
-
shorthand = 'app_master'
|
87
|
-
when 'solo'
|
88
|
-
explanation = ''
|
89
|
-
shorthand = 'solo'
|
90
|
-
else
|
91
|
-
explanation = " (#{counter})"
|
92
|
-
shorthand = "#{instance.instance_role}#{counter}"
|
93
|
-
counter += 1
|
94
|
-
end
|
95
|
-
replacement << %{
|
96
|
-
# #{instance.instance_role}#{explanation}
|
97
|
-
Host #{eyc_proxy.environment[:name]}-#{shorthand}
|
98
|
-
Hostname #{instance.dns_name}
|
99
|
-
User #{instance.users.first[:username]}
|
100
|
-
StrictHostKeyChecking no
|
101
|
-
}
|
102
|
-
end
|
103
|
-
replacement = replacement.join
|
104
|
-
ssh_config_path = File.expand_path("~/.ssh/config")
|
105
|
-
r = StringReplacer.new ssh_config_path
|
106
|
-
r.replace! replacement, eyc_proxy.environment[:name]
|
107
|
-
|
108
|
-
$stderr.puts "[EY CLOUD AWARENESS GEM] Added this to #{ssh_config_path}"
|
109
|
-
$stderr.puts replacement
|
110
|
-
end
|
111
|
-
end
|