ey_cloud_awareness 0.1.1 → 0.1.2

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