ey_cloud_awareness 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,17 +1,92 @@
1
1
  = ey_cloud_awareness
2
2
 
3
- Description goes here.
4
-
5
- == Note on Patches/Pull Requests
6
-
7
- * Fork the project.
8
- * Make your feature addition or bug fix.
9
- * Add tests for it. This is important so I don't break it in a
10
- future version unintentionally.
11
- * Commit, do not mess with rakefile, version, or history.
12
- (if you want to have your own version, that is fine but
13
- bump version in a commit by itself I can ignore when I pull)
14
- * Send me a pull request. Bonus points for topic branches.
3
+ Make your EngineYard cloud instances aware of each other.
4
+
5
+ Never download a new <tt>deploy.rb</tt> again.
6
+
7
+ == Quick start
8
+
9
+ Put this in <tt>config/environment.rb</tt>:
10
+
11
+ config.gem 'ey_cloud_awareness', :version => '[WHATEVER THE CURRENT GEM VERSION IS]', :lib => false, :source => 'http://gemcutter.org'
12
+
13
+ Put this in your <tt>config/deploy.rb</tt> (or wherever your deploy-related Capfile is):
14
+
15
+ load "#{Gem.searcher.find('ey_cloud_awareness').full_gem_path}/lib/tasks/capistrano_tasks.rb"
16
+
17
+ task :my_app_production do
18
+ role :app_master, 'my_app.com' # or you can use its Elastic IP
19
+ set :rails_env, 'production' # required
20
+ set :deploy_to, '/data/my_app' # required
21
+ find_and_execute_task 'eyc_setup' # note that we don't use eyc: namespace
22
+ end
23
+
24
+ task :my_app_staging do
25
+ role :app_master, 'staging.my_app.com' # or you can use its Elastic IP
26
+ set :rails_env, 'production' # required
27
+ set :deploy_to, '/data/my_app' # required
28
+ find_and_execute_task 'eyc_setup' # note that we don't use eyc: namespace
29
+ end
30
+
31
+ # add more tasks if you have more cloud environments
32
+
33
+ Now you should be able to eycap stuff like:
34
+
35
+ cap my_app_production monit:status
36
+
37
+ ...and <b>capistrano will always have a fresh list of your environment's instances</b>. Roles like <tt>:app</tt>, <tt>:db</tt>, and <tt>:utility</tt> are set properly.
38
+
39
+ == Just dumping information about your instances
40
+
41
+ Once you've done the quickstart, try:
42
+
43
+ cap my_app_production eyc:app # gets a list of your app instances, including app_master
44
+ cap my_app_production eyc:utility # ditto for utility instances
45
+ cap my_app_production eyc:db # gets your master db instance (FIXME: I don't think it will find slaves)
46
+ cap my_app_production eyc:all # gets a list of all your instances
47
+
48
+ == Using the EngineYardCloudInstance class inside Rails
49
+
50
+ I run a memcached server on every app instance, so I have this in <tt>config/environment.rb</tt>:
51
+
52
+ Rails::Initializer.run do |config|
53
+ [...]
54
+ config.cache_store = :mem_cache_store, EngineYardCloudInstance.app.map { |i| "#{i.private_dns_name}:11211" }
55
+ [...]
56
+ end
57
+
58
+ Or whatever you want:
59
+
60
+ >> all_app_instances = EngineYardCloudInstance.app
61
+ => [#<EngineYardCloudInstance:0xb5e12f70 @instance_id="i-50cf5838">, #<EngineYardCloudInstance:0xb5e12f5c @instance_id="i-dc9207b4">, #<EngineYardCloudInstance:0xb5e12f34 @instance_id="i-b2d84fda">]
62
+ >> all_app_instances.first.dns_name
63
+ => "ec2-67-202-43-40.compute-1.amazonaws.com"
64
+ >> pp all_app_instances.first.to_hash
65
+ {:dns_name=>"ec2-67-202-43-40.compute-1.amazonaws.com",
66
+ :instance_role=>"app",
67
+ :aws_groups=>["ey-app1_production-1256085955-3205-13340"],
68
+ :aws_instance_id=>"i-50cf5838",
69
+ :private_dns_name=>"domU-12-31-39-01-99-D3.compute-1.internal",
70
+ :aws_state=>"running"}
71
+ => nil
72
+
73
+ == A note on EngineYard dependence
74
+
75
+ This gem depends on
76
+
77
+ /etc/chef/dna.json
78
+
79
+ being present and containing certain attributes as named by EngineYard. Please let me know if something changes.
80
+
81
+ == A note on caching and network needs
82
+
83
+ I tried to be smart about caching the results of network calls.
84
+
85
+ Stuff like the current instance id, which is pulled from an EC2 metadata server, is stored in
86
+
87
+ CURRENT_INSTANCE_ID_CACHE_PATH = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/config/engine_yard_cloud_instance_id" : '/etc/engine_yard_cloud_instance_id'
88
+
89
+ Please let me know if this causes problems.
15
90
 
16
91
  == Copyright
17
92
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ey_cloud_awareness}
8
- s.version = "0.1.1"
8
+ s.version = "0.1.2"
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{2009-11-03}
12
+ s.date = %q{2009-11-04}
13
13
  s.description = %q{Make your EngineYard cloud instances aware of each other.}
14
14
  s.email = %q{seamus@abshere.net}
15
15
  s.extra_rdoc_files = [
@@ -26,6 +26,8 @@ Gem::Specification.new do |s|
26
26
  "ey_cloud_awareness.gemspec",
27
27
  "lib/engine_yard_cloud_instance.rb",
28
28
  "lib/ey_cloud_awareness.rb",
29
+ "lib/tasks/capistrano_tasks.rb",
30
+ "lib/tasks/ey_cloud_awareness.rake",
29
31
  "spec/ey_cloud_awareness_spec.rb",
30
32
  "spec/spec.opts",
31
33
  "spec/spec_helper.rb"
@@ -1,23 +1,28 @@
1
1
  class EngineYardCloudInstance
2
2
  CURRENT_INSTANCE_ID_CACHE_PATH = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/config/engine_yard_cloud_instance_id" : '/etc/engine_yard_cloud_instance_id'
3
+ CURRENT_SECURITY_GROUPS_CACHE_PATH = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/config/engine_yard_cloud_security_groups" : '/etc/engine_yard_cloud_security_groups'
3
4
  INSTANCE_DESCRIPTIONS_CACHE_PATH = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/config/engine_yard_cloud_instance_descriptions.yml" : '/etc/engine_yard_cloud_instance_descriptions.yml'
4
5
  DNA_PATH = '/etc/chef/dna.json'
5
6
 
6
7
  attr_reader :instance_id
7
8
  def initialize(instance_id)
8
- @instance_id = instance_id.to_sym
9
+ @instance_id = instance_id.to_s
9
10
  end
10
11
 
11
12
  def valid?
12
13
  data.present?
13
14
  end
14
15
 
15
- def refresh
16
- self.class.refresh
16
+ def clear
17
+ self.class.clear
18
+ end
19
+
20
+ def to_hash
21
+ data.deep_copy
17
22
  end
18
23
 
19
24
  def data
20
- self.class.data[instance_id]
25
+ self.class.data[instance_id.to_sym]
21
26
  end
22
27
 
23
28
  def method_missing(name, *args, &block)
@@ -29,7 +34,20 @@ class EngineYardCloudInstance
29
34
  end
30
35
  end
31
36
 
37
+ class_inheritable_accessor :proxy
38
+
32
39
  class << self
40
+ def to_hash
41
+ clear
42
+ data.deep_copy
43
+ end
44
+
45
+ def from_hash(hash)
46
+ self.proxy = true
47
+ @_data = hash.recursive_symbolize_keys!
48
+ self
49
+ end
50
+
33
51
  def app
34
52
  find_all_by_instance_roles :app, :app_master
35
53
  end
@@ -59,58 +77,73 @@ class EngineYardCloudInstance
59
77
  end
60
78
 
61
79
  def find_all_by_instance_roles(*args)
62
- data.select { |_, v| Array.wrap(args).include? v[:instance_role] }.map { |k, _| new k }
80
+ data.select { |_, v| Array.wrap(args).map(&:to_s).include? v[:instance_role] }.map { |k, _| new k }
63
81
  end
64
82
 
65
- def refresh
83
+ def clear
84
+ raise "[EY CLOUD AWARENESS GEM] Can't clear if we used from_hash" if self.proxy
66
85
  @_data = nil
67
86
  @_dna = nil
68
87
  cached_instance_descriptions true
88
+ cached_current_security_groups true
69
89
  cached_current_instance_id true
70
90
  end
71
91
 
72
92
  def data
73
93
  return @_data if @_data
94
+ raise "[EY CLOUD AWARENESS GEM] Can't calculate data if we used from_hash" if self.proxy
74
95
  hash = Hash.new
75
96
  cached_instance_descriptions.each do |instance_description|
97
+ next unless Set.new(Array.wrap(cached_current_security_groups)).superset? Set.new(instance_description[:aws_groups])
76
98
  hash[instance_description[:aws_instance_id]] ||= Hash.new
77
99
  current = hash[instance_description[:aws_instance_id]]
78
100
  # using current as a pointer
79
101
  if dna[:db_host] == instance_description[:dns_name] or dna[:db_host] == instance_description[:private_dns_name]
80
- current[:instance_role] = :db
102
+ current[:instance_role] = 'db'
81
103
  elsif Array.wrap(dna[:utility_instances]).include? instance_description[:private_dns_name]
82
- current[:instance_role] = :utility
104
+ current[:instance_role] = 'utility'
83
105
  elsif dna[:master_app_server][:private_dns_name] == instance_description[:private_dns_name]
84
- current[:instance_role] = :app_master
85
- else
86
- current[:instance_role] = :app
106
+ current[:instance_role] = 'app_master'
107
+ elsif instance_description[:aws_state] == 'running'
108
+ current[:instance_role] = 'app'
87
109
  end
88
110
  current[:private_dns_name] = instance_description[:private_dns_name]
89
111
  current[:dns_name] = instance_description[:dns_name]
90
112
  current[:aws_state] = instance_description[:aws_state]
113
+ current[:aws_groups] = instance_description[:aws_groups]
114
+ current[:aws_instance_id] = instance_description[:aws_instance_id]
91
115
  end
92
116
  @_data = hash.recursive_symbolize_keys!
93
117
  end
94
118
 
95
119
  def dna
120
+ raise "[EY CLOUD AWARENESS GEM] Can't see DNA if we used from_hash" if self.proxy
96
121
  @_dna ||= JSON.load(IO.read(DNA_PATH)).recursive_symbolize_keys!
97
122
  end
98
123
 
99
124
  private
100
125
 
101
- def cached_current_instance_id(refresh = false)
102
- if refresh or !File.readable?(CURRENT_INSTANCE_ID_CACHE_PATH)
103
- @_cached_current_instance_id = open("http://169.254.169.254/latest/meta-data/instance-id").gets
104
- begin
105
- File.open(CURRENT_INSTANCE_ID_CACHE_PATH, 'w') { |f| f.write @_cached_current_instance_id }
106
- rescue Errno::EACCES
107
- $stderr.puts "[EY CLOUD AWARENESS GEM] Not caching current instance id because #{CURRENT_INSTANCE_ID_CACHE_PATH} can't be written to"
126
+ # def cached_current_instance_id
127
+ # def cached_current_security_groups
128
+ %w{ current_instance_id current_security_groups }.each do |name|
129
+ eval %{
130
+ def cached_#{name}(refresh = false)
131
+ raise "[EY CLOUD AWARENESS GEM] Can't call #{name} if we used from_hash" if self.proxy
132
+ if refresh or !File.readable?(#{name.upcase}_CACHE_PATH)
133
+ @_cached_#{name} = open("http://169.254.169.254/latest/meta-data/#{name.gsub('current_', '').dasherize}").gets
134
+ begin
135
+ File.open(#{name.upcase}_CACHE_PATH, 'w') { |f| f.write @_cached_#{name} }
136
+ rescue Errno::EACCES
137
+ $stderr.puts "[EY CLOUD AWARENESS GEM] Not caching #{name.humanize.downcase} because \#{#{name.upcase}_CACHE_PATH} can't be written to"
138
+ end
139
+ end
140
+ @_cached_#{name} ||= IO.read(#{name.upcase}_CACHE_PATH)
108
141
  end
109
- end
110
- @_cached_current_instance_id ||= IO.read(CURRENT_INSTANCE_ID_CACHE_PATH)
142
+ }
111
143
  end
112
144
 
113
145
  def cached_instance_descriptions(refresh = false)
146
+ raise "[EY CLOUD AWARENESS GEM] Can't call cached_instance_descriptions if we used from_hash" if self.proxy
114
147
  if refresh or !File.readable?(INSTANCE_DESCRIPTIONS_CACHE_PATH)
115
148
  ec2 = RightAws::Ec2.new dna[:aws_secret_id], dna[:aws_secret_key]
116
149
  @_cached_instance_descriptions = ec2.describe_instances.map(&:recursive_symbolize_keys!)
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'set'
2
3
  require 'active_support'
3
4
  require 'right_aws'
4
5
  require File.expand_path(File.join(File.dirname(__FILE__), 'engine_yard_cloud_instance'))
@@ -10,4 +11,8 @@ class Hash
10
11
  values.select { |v| v.is_a?(Hash) }.each { |h| h.recursive_symbolize_keys! }
11
12
  self
12
13
  end
14
+
15
+ def deep_copy
16
+ Marshal.load(Marshal.dump(self))
17
+ end
13
18
  end
@@ -0,0 +1,39 @@
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
+ eyc_proxy.app.each { |i| role :app, i.dns_name }
30
+ eyc_proxy.db.each { |i| role :db, i.dns_name }
31
+ end
32
+
33
+ namespace :eyc do
34
+ %w{ app db utility all first }.each do |name|
35
+ task name, :roles => :app_master do
36
+ pp eyc_proxy.send(name).map(&:to_hash)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ require 'ey_cloud_awareness'
2
+
3
+ namespace :eyc do
4
+ task :to_json do
5
+ puts EngineYardCloudInstance.to_hash.to_json
6
+ end
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ey_cloud_awareness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seamus Abshere
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-03 00:00:00 -05:00
12
+ date: 2009-11-04 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -61,6 +61,8 @@ files:
61
61
  - ey_cloud_awareness.gemspec
62
62
  - lib/engine_yard_cloud_instance.rb
63
63
  - lib/ey_cloud_awareness.rb
64
+ - lib/tasks/capistrano_tasks.rb
65
+ - lib/tasks/ey_cloud_awareness.rake
64
66
  - spec/ey_cloud_awareness_spec.rb
65
67
  - spec/spec.opts
66
68
  - spec/spec_helper.rb