engineyard-metadata 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,23 +1,34 @@
1
1
  = engineyard-metadata
2
2
 
3
- Pulls metadata from EC2 and EngineYard so that your EngineYard Cloud instances know about each other.
3
+ Presents a simple, unchanging interface to get metadata about your EngineYard AppCloud instances running on Amazon EC2.
4
4
 
5
5
  == Purpose
6
6
 
7
- To define an unchanging interface to useful metadata (passwords, IP addresses, etc.) that is otherwise buried deep inside EngineYard's chef config files and EC2 API calls.
7
+ To define an unchanging interface to useful metadata (passwords, IP addresses, etc.) that is otherwise buried deep inside EngineYard's chef config files and various API calls.
8
8
 
9
9
  == Examples
10
10
 
11
11
  * Get a dynamically updated list of all running app servers so that you can pool memcached over all of them. (<tt>EY::Metadata.app_servers</tt>)
12
12
  * Get the current database password so that you can write a script that connects to it. (<tt>EY::Metadata.database_password</tt>)
13
13
 
14
+ Get the full method list in {the engineyard-metadata rdoc}[http://rdoc.info/github/seamusabshere/engineyard-metadata].
15
+
14
16
  == Use
15
17
 
16
- Get the full method list in {the engineyard-metadata rdoc}[http://rdoc.info/github/seamusabshere/engineyard-metadata].
18
+ === From the inside
19
+
20
+ When you're executing the gem from your instances, you don't have to configure anything. Just require the gem.
21
+
22
+ === From the outside
23
+
24
+ You need to provide...
25
+
26
+ * <tt>ENV['EY_CLOUD_TOKEN']</tt> or have <tt>~/.eyrc</tt>
27
+ * <tt>ENV['REPOSITORY_URI']</tt> or execute the gem from the local copy of your application's repo.
17
28
 
18
- Metadata getters are defined directly on <tt>EY::Metadata</tt>. Even if EngineYard changes the structure of the config files or Amazon EC2's API changes, these methods will stay the same.
29
+ === Where the methods are defined
19
30
 
20
- This only runs on EngineYard AppCloud instances (running on Amazon EC2).
31
+ Metadata getters are defined directly on <tt>EY::Metadata</tt> (which in turn delegates out to various adapters). Even if EngineYard changes the structure of the config files or Amazon EC2's API changes, these methods will stay the same.
21
32
 
22
33
  [...]
23
34
  >> require 'rubygems'
@@ -26,8 +37,6 @@ This only runs on EngineYard AppCloud instances (running on Amazon EC2).
26
37
  [...]
27
38
  >> EY::Metadata.database_host
28
39
  => "external_db_master.compute-1.amazonaws.com"
29
- >> EY::Metadata.database_password
30
- => "foobarfoo"
31
40
  >> EY::Metadata.app_servers
32
41
  => [ 'app_1.compute-1.amazonaws.com' , 'app_master.compute-1.amazonaws.com' ]
33
42
  >> EY::Metadata.db_servers
data/Rakefile CHANGED
@@ -11,6 +11,7 @@ begin
11
11
  gem.homepage = "http://github.com/seamusabshere/engineyard-metadata"
12
12
  gem.authors = ["Seamus Abshere"]
13
13
  gem.add_dependency 'activesupport', '>=2.3.4'
14
+ gem.add_dependency 'nap', '>=0.4'
14
15
  gem.add_development_dependency "fakeweb"
15
16
  gem.add_development_dependency "fakefs"
16
17
  gem.add_development_dependency "rspec", "~>1"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{engineyard-metadata}
8
- s.version = "0.0.4"
8
+ s.version = "0.0.5"
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-10-11}
12
+ s.date = %q{2010-10-14}
13
13
  s.description = %q{Pulls metadata from EC2 and EngineYard so that your EngineYard AppCloud (Amazon EC2) instances know about each other.}
14
14
  s.email = %q{seamus@abshere.net}
15
15
  s.extra_rdoc_files = [
@@ -27,10 +27,14 @@ Gem::Specification.new do |s|
27
27
  "lib/engineyard-metadata.rb",
28
28
  "lib/engineyard-metadata/amazon_ec2_api.rb",
29
29
  "lib/engineyard-metadata/chef_dna.rb",
30
+ "lib/engineyard-metadata/engine_yard_cloud_api.rb",
31
+ "lib/engineyard-metadata/insider.rb",
30
32
  "lib/engineyard-metadata/metadata.rb",
33
+ "lib/engineyard-metadata/outsider.rb",
31
34
  "spec/metadata_spec.rb",
32
35
  "spec/spec_helper.rb",
33
- "spec/support/dna.json"
36
+ "spec/support/dna.json",
37
+ "spec/support/engine_yard_cloud_api_response.json"
34
38
  ]
35
39
  s.homepage = %q{http://github.com/seamusabshere/engineyard-metadata}
36
40
  s.rdoc_options = ["--charset=UTF-8"]
@@ -48,17 +52,20 @@ Gem::Specification.new do |s|
48
52
 
49
53
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
54
  s.add_runtime_dependency(%q<activesupport>, [">= 2.3.4"])
55
+ s.add_runtime_dependency(%q<nap>, [">= 0.4"])
51
56
  s.add_development_dependency(%q<fakeweb>, [">= 0"])
52
57
  s.add_development_dependency(%q<fakefs>, [">= 0"])
53
58
  s.add_development_dependency(%q<rspec>, ["~> 1"])
54
59
  else
55
60
  s.add_dependency(%q<activesupport>, [">= 2.3.4"])
61
+ s.add_dependency(%q<nap>, [">= 0.4"])
56
62
  s.add_dependency(%q<fakeweb>, [">= 0"])
57
63
  s.add_dependency(%q<fakefs>, [">= 0"])
58
64
  s.add_dependency(%q<rspec>, ["~> 1"])
59
65
  end
60
66
  else
61
67
  s.add_dependency(%q<activesupport>, [">= 2.3.4"])
68
+ s.add_dependency(%q<nap>, [">= 0.4"])
62
69
  s.add_dependency(%q<fakeweb>, [">= 0"])
63
70
  s.add_dependency(%q<fakefs>, [">= 0"])
64
71
  s.add_dependency(%q<rspec>, ["~> 1"])
@@ -2,6 +2,7 @@ require 'open-uri'
2
2
 
3
3
  module EY
4
4
  module Metadata
5
+ # An adapter that reads from Amazon EC2's metadata web service, which is only available from cloud instances.
5
6
  class AmazonEc2Api
6
7
  # The present instance's Amazon Ec2 instance id.
7
8
  def present_instance_id
@@ -8,89 +8,90 @@ end if ActiveSupport::VERSION::MAJOR == 3
8
8
 
9
9
  module EY
10
10
  module Metadata
11
+ # An adapter that reads from /etc/chef/dna.json, which is only available on cloud instances.
11
12
  class ChefDna
12
13
  PATH = '/etc/chef/dna.json'
13
- MYSQL_BIN = '/usr/bin/mysql'
14
- MYSQLDUMP_BIN = '/usr/bin/mysqldump'
15
-
14
+
16
15
  def data # :nodoc:
17
16
  @data ||= ActiveSupport::JSON.decode File.read(PATH)
18
17
  end
19
-
18
+
20
19
  # The present instance's role
21
20
  def present_instance_role
22
21
  data['instance_role']
23
22
  end
24
-
23
+
25
24
  # The present instance's public hostname.
26
25
  def present_public_hostname
27
26
  data['engineyard']['environment']['instances'].detect { |i| i['id'] == EY::Metadata.present_instance_id }['public_hostname']
28
27
  end
29
-
28
+
30
29
  # Currently the same as the SSH password.
31
30
  def database_password
32
31
  data['users'][0]['password']
33
32
  end
34
-
33
+
35
34
  # Currently the same as the SSH username.
36
35
  def database_username
37
36
  data['users'][0]['username']
38
37
  end
39
-
38
+
40
39
  # For newly deployed applications, equal to the application name.
41
40
  def database_name
42
41
  data['engineyard']['environment']['apps'][0]['database_name']
43
42
  end
44
-
43
+
45
44
  # Public hostname where you should connect to the database.
46
45
  #
47
46
  # Currently the db master public hostname.
48
47
  def database_host
49
- data['db_host']
48
+ db_master
50
49
  end
51
-
50
+
52
51
  # SSH username.
53
52
  def ssh_username
54
53
  data['engineyard']['environment']['ssh_username']
55
54
  end
56
-
55
+
57
56
  # SSH password.
58
57
  def ssh_password
59
58
  data['engineyard']['environment']['ssh_password']
60
59
  end
61
-
60
+
62
61
  # The public hostnames of all the app servers.
63
62
  #
64
63
  # If you're on a solo app, it counts the solo as an app server.
65
64
  def app_servers
66
65
  data['engineyard']['environment']['instances'].select { |i| %w{ app_master app solo }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
67
66
  end
68
-
67
+
69
68
  # The public hostnames of all the app slaves.
70
69
  def app_slaves
71
70
  data['engineyard']['environment']['instances'].select { |i| %w{ app }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
72
71
  end
73
-
72
+
74
73
  # The public hostnames of all the db servers.
75
74
  #
76
75
  # If you're on a solo app, it counts the solo as a db server.
77
76
  def db_servers
78
77
  data['engineyard']['environment']['instances'].select { |i| %w{ db_master db_slave solo }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
79
78
  end
80
-
79
+
81
80
  # The public hostnames of all the db slaves.
82
81
  def db_slaves
83
82
  data['engineyard']['environment']['instances'].select { |i| %w{ db_slave }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
84
83
  end
85
-
84
+
86
85
  # The public hostnames of all the utility servers.
87
86
  #
88
87
  # If you're on a solo app, it counts the solo as a utility.
89
88
  def utilities
90
89
  data['engineyard']['environment']['instances'].select { |i| %w{ util solo }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
91
90
  end
92
-
91
+
93
92
  # The public hostname of the app_master.
93
+ #
94
+ # If you're on a solo app, it counts the solo as the app_master.
94
95
  def app_master
95
96
  if x = data['engineyard']['environment']['instances'].detect { |i| i['role'] == 'app_master' }
96
97
  x['public_hostname']
@@ -98,8 +99,10 @@ module EY
98
99
  solo
99
100
  end
100
101
  end
101
-
102
- # The public hostname of the db_master,
102
+
103
+ # The public hostname of the db_master.
104
+ #
105
+ # If you're on a solo app, it counts the solo as the app_master.
103
106
  def db_master
104
107
  if x = data['engineyard']['environment']['instances'].detect { |i| i['role'] == 'db_master' }
105
108
  x['public_hostname']
@@ -107,28 +110,33 @@ module EY
107
110
  solo
108
111
  end
109
112
  end
110
-
113
+
111
114
  # The public hostname of the solo.
112
115
  def solo
113
116
  if x = data['engineyard']['environment']['instances'].detect { |i| i['role'] == 'solo' }
114
117
  x['public_hostname']
115
118
  end
116
119
  end
117
-
120
+
118
121
  # The shell command for mysql, including username, password, hostname and database
119
122
  def mysql_command
120
- "#{MYSQL_BIN} -h #{database_host} -u #{database_username} -p#{database_password} #{database_name}"
123
+ "/usr/bin/mysql -h #{database_host} -u #{database_username} -p#{database_password} #{database_name}"
121
124
  end
122
125
 
123
126
  # The shell command for mysql, including username, password, hostname and database
124
127
  def mysqldump_command
125
- "#{MYSQLDUMP_BIN} -h #{database_host} -u #{database_username} -p#{database_password} #{database_name}"
128
+ "/usr/bin/mysqldump -h #{database_host} -u #{database_username} -p#{database_password} #{database_name}"
126
129
  end
127
-
130
+
128
131
  # The name of the EngineYard AppCloud environment.
129
132
  def environment_name
130
133
  data['environment']['name']
131
134
  end
135
+
136
+ # The stack in use, like nginx_passenger.
137
+ def stack_name
138
+ data['engineyard']['environment']['stack_name']
139
+ end
132
140
  end
133
141
  end
134
142
  end
@@ -0,0 +1,151 @@
1
+ require 'etc'
2
+ require 'yaml'
3
+ require 'rest' # from nap gem
4
+ require 'active_support'
5
+ require 'active_support/version'
6
+ %w{
7
+ active_support/json
8
+ }.each do |active_support_3_requirement|
9
+ require active_support_3_requirement
10
+ end if ActiveSupport::VERSION::MAJOR == 3
11
+
12
+ module EY
13
+ module Metadata
14
+ # An adapter that reads from the public EngineYard Cloud API (https://cloud.engineyard.com). Available from anywhere.
15
+ #
16
+ # See README for what environment variables and/or files you need to have in place for this to work.
17
+ class EngineYardCloudApi
18
+ URL = 'https://cloud.engineyard.com/api/v2/environments'
19
+
20
+ # Currently the same as the SSH username.
21
+ def database_username
22
+ data['ssh_username']
23
+ end
24
+
25
+ # The username for connecting by SSH.
26
+ def ssh_username
27
+ data['ssh_username']
28
+ end
29
+
30
+ # Currently the same as the app name, at least for recently-created environments.
31
+ #
32
+ # It used to be named after the environment.
33
+ def database_name
34
+ data['apps'][0]['name']
35
+ end
36
+
37
+ # The hostname of the database host.
38
+ def database_host
39
+ db_master
40
+ end
41
+
42
+ # The public hostname of the db_master.
43
+ #
44
+ # If you're on a solo app, it counts the solo as the app_master.
45
+ def db_master
46
+ if x = data['instances'].detect { |i| i['role'] == 'db_master' }
47
+ x['public_hostname']
48
+ else
49
+ solo
50
+ end
51
+ end
52
+
53
+ # The public hostnames of all the app servers.
54
+ #
55
+ # If you're on a solo app, it counts the solo as an app server.
56
+ def app_servers
57
+ data['instances'].select { |i| %w{ app_master app solo }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
58
+ end
59
+
60
+ # The public hostnames of all the db servers.
61
+ #
62
+ # If you're on a solo app, it counts the solo as a db server.
63
+ def db_servers
64
+ data['instances'].select { |i| %w{ db_master db_slave solo }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
65
+ end
66
+
67
+ # The public hostnames of all the utility servers.
68
+ #
69
+ # If you're on a solo app, it counts the solo as a utility.
70
+ def utilities
71
+ data['instances'].select { |i| %w{ util solo }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
72
+ end
73
+
74
+ # The public hostnames of all the app slaves.
75
+ def app_slaves
76
+ data['instances'].select { |i| %w{ app }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
77
+ end
78
+
79
+ # The public hostnames of all the db slaves.
80
+ def db_slaves
81
+ data['instances'].select { |i| %w{ db_slave }.include? i['role'] }.map { |i| i['public_hostname'] }.sort
82
+ end
83
+
84
+ # The public hostname of the app_master.
85
+ #
86
+ # If you're on a solo app, it counts the solo as the app_master.
87
+ def app_master
88
+ if x = data['instances'].detect { |i| i['role'] == 'app_master' }
89
+ x['public_hostname']
90
+ else
91
+ solo
92
+ end
93
+ end
94
+
95
+ # The public hostname of the solo.
96
+ def solo
97
+ if x = data['instances'].detect { |i| i['role'] == 'solo' }
98
+ x['public_hostname']
99
+ end
100
+ end
101
+
102
+ # The name of the EngineYard AppCloud environment.
103
+ def environment_name
104
+ data['name']
105
+ end
106
+
107
+ # The stack in use, like nginx_passenger.
108
+ def stack_name
109
+ data['stack_name']
110
+ end
111
+
112
+ # The secret API token to access https://cloud.engineyard.com
113
+ def ey_cloud_token
114
+ @ey_cloud_token ||= if ENV['EY_CLOUD_TOKEN'].to_s.strip.length > 0
115
+ ENV['EY_CLOUD_TOKEN']
116
+ elsif File.exist? EY::Metadata.eyrc_path
117
+ YAML.load(File.read(EY::Metadata.eyrc_path))['api_token']
118
+ else
119
+ raise RuntimeError, "[engineyard-metadata gem] You need to download #{eyrc_path} or set ENV['EY_CLOUD_TOKEN']"
120
+ end
121
+ end
122
+
123
+ # The URL that EngineYard has on file for your application.
124
+ def repository_uri
125
+ @repository_uri ||= if ENV['REPOSITORY_URI'].to_s.strip.length > 0
126
+ ENV['REPOSITORY_URI']
127
+ elsif File.exist? EY::Metadata.git_config_path
128
+ `git config --get remote.origin.url`
129
+ else
130
+ raise RuntimeError, "[engineyard-metadata gem] You need to be inside a app's git repo or set ENV['REPOSITORY_URI']"
131
+ end
132
+ end
133
+
134
+ def data
135
+ return @data if @data.is_a? Hash
136
+ raw_json = REST.get(URL, 'X-EY-Cloud-Token' => ey_cloud_token).body
137
+ raw_data = ActiveSupport::JSON.decode raw_json
138
+ catch :found_environment_by_repository_uri do
139
+ raw_data['environments'].each do |environment_hsh|
140
+ if environment_hsh['apps'].any? { |app_hsh| app_hsh['repository_uri'] == repository_uri }
141
+ @data = environment_hsh
142
+ throw :found_environment_by_repository_uri
143
+ end
144
+ end
145
+ end
146
+ raise RuntimeError, "[engineyard-metadata gem] Couldn't find an EngineYard environment with the repository uri #{repository_uri}" unless @data.is_a? Hash
147
+ @data
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,35 @@
1
+ module EY
2
+ module Metadata
3
+ # This gets pulled in when you're running directly on a cloud instance.
4
+ module Insider
5
+ DELEGATED_TO_AMAZON_EC2_API = %w{
6
+ present_instance_id
7
+ present_security_group
8
+ }
9
+
10
+ DELEGATED_TO_CHEF_DNA = KEYS - DELEGATED_TO_AMAZON_EC2_API
11
+
12
+ DELEGATED_TO_AMAZON_EC2_API.each do |name|
13
+ define_method name do
14
+ amazon_ec2_api.send name
15
+ end
16
+ end
17
+
18
+ DELEGATED_TO_CHEF_DNA.each do |name|
19
+ define_method name do
20
+ chef_dna.send name
21
+ end
22
+ end
23
+
24
+ # An adapter that reads from the EngineYard AppCloud /etc/chef/dna.json file.
25
+ def chef_dna
26
+ @chef_dna ||= ChefDna.new
27
+ end
28
+
29
+ # An adapter that reads from Amazon's EC2 API.
30
+ def amazon_ec2_api
31
+ @amazon_ec2_api ||= AmazonEc2Api.new
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,11 +1,13 @@
1
1
  module EY
2
+ # All methods are defined on this module. For example, you're supposed to say
3
+ #
4
+ # EY::Metadata.database_username
5
+ #
6
+ # instead of trying to call it from a particular adapter.
2
7
  module Metadata
3
- DELEGATED_TO_AMAZON_EC2_API = %w{
8
+ KEYS = %w{
4
9
  present_instance_id
5
10
  present_security_group
6
- }
7
-
8
- DELEGATED_TO_CHEF_DNA = %w{
9
11
  present_instance_role
10
12
  present_public_hostname
11
13
  database_password
@@ -25,33 +27,24 @@ module EY
25
27
  db_slaves
26
28
  solo
27
29
  environment_name
30
+ stack_name
28
31
  }
29
32
 
30
- DELEGATED_TO_AMAZON_EC2_API.each do |name|
31
- EY::Metadata.send :define_method, name do
32
- amazon_ec2_api.send name
33
- end
34
- end
35
-
36
- DELEGATED_TO_CHEF_DNA.each do |name|
37
- EY::Metadata.send :define_method, name do
38
- chef_dna.send name
39
- end
33
+ # This gets raised when you can't get a particular piece of metadata from the execution environment you're in.
34
+ class CannotGetFromHere < RuntimeError
40
35
  end
41
-
42
- extend self
43
36
 
37
+ autoload :Insider, 'engineyard-metadata/insider'
38
+ autoload :Outsider, 'engineyard-metadata/outsider'
44
39
  autoload :ChefDna, 'engineyard-metadata/chef_dna'
45
40
  autoload :AmazonEc2Api, 'engineyard-metadata/amazon_ec2_api'
41
+ autoload :EngineYardCloudApi, 'engineyard-metadata/engine_yard_cloud_api'
46
42
 
47
- # An adapter that reads from the EngineYard AppCloud /etc/chef/dna.json file.
48
- def chef_dna
49
- @chef_dna ||= EY::Metadata::ChefDna.new
50
- end
51
-
52
- # An adapter that reads from Amazon's EC2 API.
53
- def amazon_ec2_api
54
- @amazon_ec2_api ||= EY::Metadata::AmazonEc2Api.new
43
+ # this is a pretty sloppy way of detecting whether we're on ec2
44
+ if File.exist? '/etc/chef/dna.json'
45
+ extend Insider
46
+ else
47
+ extend Outsider
55
48
  end
56
49
  end
57
50
  end
@@ -0,0 +1,35 @@
1
+ module EY
2
+ module Metadata
3
+ # This gets pulled in when you're running from your developer machine (i.e., not on a cloud instance).
4
+ module Outsider
5
+ IMPOSSIBLE = KEYS.grep(/present/) + KEYS.grep(/password/) + KEYS.grep(/mysql/)
6
+
7
+ POSSIBLE = KEYS - IMPOSSIBLE
8
+
9
+ IMPOSSIBLE.each do |name|
10
+ define_method name do
11
+ raise CannotGetFromHere
12
+ end
13
+ end
14
+
15
+ POSSIBLE.each do |name|
16
+ define_method name do
17
+ engine_yard_cloud_api.send name
18
+ end
19
+ end
20
+
21
+ def eyrc_path
22
+ File.join File.expand_path("~#{Etc.getpwuid.name}"), '.eyrc'
23
+ end
24
+
25
+ def git_config_path
26
+ File.join Dir.pwd, '.git', 'config'
27
+ end
28
+
29
+ # An adapter that reads from the public EngineYard Cloud API (https://cloud.engineyard.com)
30
+ def engine_yard_cloud_api
31
+ @engine_yard_cloud_api ||= EngineYardCloudApi.new
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,80 +1,42 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe EY do
4
- it 'has a FakeFS dna.json' do
5
- File.exist?('/etc/chef/dna.json').should == true
6
- end
7
- end
8
-
9
- describe EY::Metadata do
10
- it 'gets the present instance ID' do
11
- EY::Metadata.present_instance_id.should == PRESENT_INSTANCE_ID
12
- end
13
-
14
- it 'gets the present instance role (as a string)' do
15
- EY::Metadata.present_instance_role.should == 'app_master'
16
- end
17
-
18
- it 'gets the present public hostname' do
19
- EY::Metadata.present_public_hostname.should == PRESENT_PUBLIC_HOSTNAME
20
- end
21
-
22
- it 'gets the present security group' do
23
- EY::Metadata.present_security_group.should == PRESENT_SECURITY_GROUP
24
- end
25
-
26
- it 'gets the database password' do
27
- EY::Metadata.database_password.should == 'USERS-0-PASSWORD'
28
- end
29
-
3
+ shared_examples_for "all execution environments" do
30
4
  it 'gets the database username' do
31
- EY::Metadata.database_username.should == 'USERS-0-USERNAME'
5
+ EY::Metadata.database_username.should == 'FAKE_SSH_USERNAME'
32
6
  end
33
7
 
34
8
  it 'gets the database name' do
35
- EY::Metadata.database_name.should == 'APPS-0-DATABASE_NAME'
9
+ EY::Metadata.database_name.should == 'FAKE_APP_NAME'
36
10
  end
37
11
 
38
12
  it 'gets the database host' do
39
- EY::Metadata.database_host.should == 'external_db_master.compute-1.amazonaws.com'
13
+ EY::Metadata.database_host.should == 'FAKE_DB_MASTER_PUBLIC_HOSTNAME'
40
14
  end
41
-
15
+
42
16
  it 'gets the ssh username' do
43
- EY::Metadata.ssh_username.should == 'SSH-USERNAME'
44
- end
45
-
46
- it 'gets the ssh password' do
47
- EY::Metadata.ssh_password.should == 'SSH-PASSWORD'
17
+ EY::Metadata.ssh_username.should == 'FAKE_SSH_USERNAME'
48
18
  end
49
19
 
50
20
  it 'gets the app server hostnames' do
51
21
  EY::Metadata.app_servers.should == [ 'app_1.compute-1.amazonaws.com' , 'app_master.compute-1.amazonaws.com' ]
52
22
  end
53
-
23
+
54
24
  it 'gets the db server hostnames' do
55
- EY::Metadata.db_servers.should == [ 'db_master.compute-1.amazonaws.com', 'db_slave_1.compute-1.amazonaws.com' ]
25
+ EY::Metadata.db_servers.should == [ 'FAKE_DB_MASTER_PUBLIC_HOSTNAME', 'db_slave_1.compute-1.amazonaws.com' ]
56
26
  end
57
-
27
+
58
28
  it 'gets the utilities hostnames' do
59
- EY::Metadata.utilities.should == [ 'util_1.compute-1.amazonaws.com' ]
29
+ EY::Metadata.utilities.should == [ 'FAKE_UTIL_1_PUBLIC_HOSTNAME' ]
60
30
  end
61
31
 
62
32
  it 'gets the app master hostname' do
63
33
  EY::Metadata.app_master.should == 'app_master.compute-1.amazonaws.com'
64
34
  end
65
-
35
+
66
36
  it 'gets the db master hostname' do
67
- EY::Metadata.db_master.should == 'db_master.compute-1.amazonaws.com'
37
+ EY::Metadata.db_master.should == 'FAKE_DB_MASTER_PUBLIC_HOSTNAME'
68
38
  end
69
-
70
- it 'gets the mysql command' do
71
- EY::Metadata.mysql_command.should == '/usr/bin/mysql -h external_db_master.compute-1.amazonaws.com -u USERS-0-USERNAME -pUSERS-0-PASSWORD APPS-0-DATABASE_NAME'
72
- end
73
-
74
- it 'gets the mysqldump command' do
75
- EY::Metadata.mysqldump_command.should == '/usr/bin/mysqldump -h external_db_master.compute-1.amazonaws.com -u USERS-0-USERNAME -pUSERS-0-PASSWORD APPS-0-DATABASE_NAME'
76
- end
77
-
39
+
78
40
  it 'gets the db slave hostnames' do
79
41
  EY::Metadata.db_slaves.should == [ 'db_slave_1.compute-1.amazonaws.com' ]
80
42
  end
@@ -82,12 +44,134 @@ describe EY::Metadata do
82
44
  it 'gets the app slave hostnames' do
83
45
  EY::Metadata.app_slaves.should == [ 'app_1.compute-1.amazonaws.com' ]
84
46
  end
85
-
47
+
86
48
  it 'gets the solo hostname' do
87
49
  EY::Metadata.solo.should == nil
88
50
  end
89
-
51
+
90
52
  it 'gets the environment name' do
91
- EY::Metadata.environment_name.should == 'APP-NAME_production'
53
+ EY::Metadata.environment_name.should == 'FAKE_ENVIRONMENT_NAME'
54
+ end
55
+
56
+ it 'gets the stack name' do
57
+ EY::Metadata.stack_name.should == 'FAKE_STACK_NAME'
58
+ end
59
+ end
60
+
61
+ describe 'EY::Metadata' do
62
+ describe "being executed from a developer/administrator's local machine" do
63
+ before(:all) do
64
+ pretend_we_are_on_a_developer_machine
65
+ # forcibly reload metadata.rb, so that it can extend itself based on its execution environment
66
+ load File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'engineyard-metadata', 'metadata.rb'))
67
+ end
68
+
69
+ after(:all) do
70
+ stop_pretending
71
+ end
72
+
73
+ it 'cannot get the present instance ID' do
74
+ lambda {
75
+ EY::Metadata.present_instance_id
76
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
77
+ end
78
+
79
+ it 'cannot get the present instance role (as a string)' do
80
+ lambda {
81
+ EY::Metadata.present_instance_role
82
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
83
+ end
84
+
85
+ it 'cannot get the present public hostname' do
86
+ lambda {
87
+ EY::Metadata.present_public_hostname
88
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
89
+ end
90
+
91
+ it 'cannot get the present security group' do
92
+ lambda {
93
+ EY::Metadata.present_security_group
94
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
95
+ end
96
+
97
+ it 'cannot get the database password' do
98
+ lambda {
99
+ EY::Metadata.database_password
100
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
101
+ end
102
+
103
+ it 'cannot get the ssh password' do
104
+ lambda {
105
+ EY::Metadata.ssh_password
106
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
107
+ end
108
+
109
+ it 'cannot get the mysql command' do
110
+ lambda {
111
+ EY::Metadata.mysql_command
112
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
113
+ end
114
+
115
+ it 'cannot get the mysqldump command' do
116
+ lambda {
117
+ EY::Metadata.mysqldump_command
118
+ }.should raise_error(EY::Metadata::CannotGetFromHere)
119
+ end
120
+
121
+ it 'gets the raw EngineYard Cloud API data' do
122
+ EY::Metadata.engine_yard_cloud_api.data.should be_a(Hash)
123
+ end
124
+
125
+ it_should_behave_like "all execution environments"
126
+ end
127
+
128
+ describe "being executed on an EngineYard AppCloud (i.e. Amazon EC2) instance" do
129
+ before(:all) do
130
+ pretend_we_are_on_an_engineyard_appcloud_ec2_instance
131
+ # forcibly reload metadata.rb, so that it can extend itself based on its execution environment
132
+ load File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'engineyard-metadata', 'metadata.rb'))
133
+ end
134
+
135
+ after(:all) do
136
+ stop_pretending
137
+ end
138
+
139
+ it_should_behave_like "all execution environments"
140
+
141
+ it 'has a FakeFS dna.json' do
142
+ File.exist?('/etc/chef/dna.json').should == true
143
+ end
144
+
145
+ it 'gets the present instance ID' do
146
+ EY::Metadata.present_instance_id.should == PRESENT_INSTANCE_ID
147
+ end
148
+
149
+ it 'gets the present instance role (as a string)' do
150
+ EY::Metadata.present_instance_role.should == 'app_master'
151
+ end
152
+
153
+ it 'gets the present public hostname' do
154
+ EY::Metadata.present_public_hostname.should == PRESENT_PUBLIC_HOSTNAME
155
+ end
156
+
157
+ it 'gets the present security group' do
158
+ EY::Metadata.present_security_group.should == PRESENT_SECURITY_GROUP
159
+ end
160
+
161
+ it 'gets the database password' do
162
+ EY::Metadata.database_password.should == 'USERS-0-PASSWORD'
163
+ end
164
+
165
+ it 'gets the ssh password' do
166
+ EY::Metadata.ssh_password.should == 'SSH-PASSWORD'
167
+ end
168
+
169
+ it 'gets the mysql command' do
170
+ EY::Metadata.mysql_command.should =~ %r{mysql -h FAKE_DB_MASTER_PUBLIC_HOSTNAME -u FAKE_SSH_USERNAME -pUSERS-0-PASSWORD FAKE_APP_NAME}
171
+ end
172
+
173
+ it 'gets the mysqldump command' do
174
+ EY::Metadata.mysqldump_command.should =~ %r{mysqldump -h FAKE_DB_MASTER_PUBLIC_HOSTNAME -u FAKE_SSH_USERNAME -pUSERS-0-PASSWORD FAKE_APP_NAME}
175
+ end
92
176
  end
93
177
  end
data/spec/spec_helper.rb CHANGED
@@ -3,38 +3,51 @@ require 'spec'
3
3
  # require 'ruby-debug'
4
4
  # assumes active-support 3
5
5
  require 'active_support/json/encoding'
6
+ require 'fakeweb'
7
+ require 'fakefs/safe'
6
8
 
7
9
  PRESENT_PUBLIC_HOSTNAME = 'app_master.compute-1.amazonaws.com'
8
10
  PRESENT_SECURITY_GROUP = 'ey-data1_production-1-2-3'
9
11
  PRESENT_INSTANCE_ID = 'i-deadbeef'
12
+ REPOSITORY_URI = 'FAKE_REPOSITORY_URI'
10
13
 
11
- require 'fakeweb'
12
- FakeWeb.allow_net_connect = false
13
- # fake call to amazon ec2 api to get present security group
14
- FakeWeb.register_uri :get,
15
- "http://169.254.169.254/latest/meta-data/security-groups",
16
- :status => ["200", "OK"],
17
- :body => PRESENT_SECURITY_GROUP
14
+ def pretend_we_are_on_a_developer_machine
15
+ ENV['REPOSITORY_URI'] = REPOSITORY_URI
16
+ FakeWeb.allow_net_connect = false
17
+ FakeWeb.register_uri :get,
18
+ "https://cloud.engineyard.com/api/v2/environments",
19
+ :status => ["200", "OK"],
20
+ :body => File.read(File.join(File.dirname(__FILE__), 'support', 'engine_yard_cloud_api_response.json'))
21
+ # FakeFS.activate!
22
+ end
18
23
 
19
- # fake call to amazon ec2 api to get present instance id
20
- FakeWeb.register_uri :get,
21
- "http://169.254.169.254/latest/meta-data/instance-id",
22
- :status => ["200", "OK"],
23
- :body => PRESENT_INSTANCE_ID
24
24
 
25
- dna_json = File.read File.join(File.dirname(__FILE__), 'support', 'dna.json')
26
- require 'fakefs'
27
- FileUtils.mkdir_p '/etc/chef'
28
- File.open '/etc/chef/dna.json', 'w' do |f|
29
- f.write dna_json
30
- end
25
+ def pretend_we_are_on_an_engineyard_appcloud_ec2_instance
26
+ FakeWeb.allow_net_connect = false
27
+ # fake call to amazon ec2 api to get present security group
28
+ FakeWeb.register_uri :get,
29
+ "http://169.254.169.254/latest/meta-data/security-groups",
30
+ :status => ["200", "OK"],
31
+ :body => PRESENT_SECURITY_GROUP
31
32
 
32
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
33
- $LOAD_PATH.unshift(File.dirname(__FILE__))
34
- require 'engineyard-metadata'
33
+ # fake call to amazon ec2 api to get present instance id
34
+ FakeWeb.register_uri :get,
35
+ "http://169.254.169.254/latest/meta-data/instance-id",
36
+ :status => ["200", "OK"],
37
+ :body => PRESENT_INSTANCE_ID
35
38
 
36
- # Spec::Runner.configure do |config|
37
- # config.before(:all) do
38
- #
39
- # end
40
- # end
39
+ # first read a file from the real file system...
40
+ dna_json = File.read File.join(File.dirname(__FILE__), 'support', 'dna.json')
41
+ # ... then turn on the fakefs
42
+ FakeFS.activate!
43
+ FileUtils.mkdir_p '/etc/chef'
44
+ File.open '/etc/chef/dna.json', 'w' do |f|
45
+ f.write dna_json
46
+ end
47
+ end
48
+
49
+ def stop_pretending
50
+ FakeFS.deactivate!
51
+ FakeWeb.clean_registry
52
+ FakeWeb.allow_net_connect = true
53
+ end
@@ -15,7 +15,7 @@
15
15
  "internal_ssh_private_key": "-----BEGIN RSA PRIVATE KEY-----\nPRIVATE-KEY\n-----END RSA PRIVATE KEY-----\n",
16
16
  "utility_instances": [
17
17
  {
18
- "name": "APP-NAME_production_util_1",
18
+ "name": "FAKE_ENVIRONMENT_NAME_util_1",
19
19
  "hostname": "internal_util_1.compute-1.internal"
20
20
  }
21
21
  ],
@@ -77,7 +77,7 @@
77
77
  "vhosts": [
78
78
  {
79
79
  "name": "_",
80
- "role": "APP-NAME_production"
80
+ "role": "FAKE_ENVIRONMENT_NAME"
81
81
  }
82
82
  ]
83
83
  }
@@ -86,15 +86,15 @@
86
86
  "users": [
87
87
  {
88
88
  "gid": "1000",
89
- "username": "USERS-0-USERNAME",
89
+ "username": "FAKE_SSH_USERNAME",
90
90
  "uid": "1000",
91
91
  "comment": "",
92
92
  "password": "USERS-0-PASSWORD"
93
93
  }
94
94
  ],
95
95
  "environment": {
96
- "name": "APP-NAME_production",
97
- "stack": "nginx_passenger",
96
+ "name": "FAKE_ENVIRONMENT_NAME",
97
+ "stack": "FAKE_STACK_NAME",
98
98
  "framework_env": "production"
99
99
  },
100
100
  "master_app_server": {
@@ -107,7 +107,7 @@
107
107
  "name": "dev-libs/oniguruma"
108
108
  }
109
109
  ],
110
- "db_host": "external_db_master.compute-1.amazonaws.com",
110
+ "db_host": "FAKE_DB_HOST",
111
111
  "haproxy": {
112
112
  "username": "HAPROXY-USERNAME",
113
113
  "password": "HAPROXY-PASSWORD"
@@ -116,12 +116,12 @@
116
116
  "environment": {
117
117
  "apps": [
118
118
  {
119
- "name": "APP-NAME",
119
+ "name": "FAKE_APP_NAME",
120
120
  "newrelic": false,
121
121
  "components": [
122
122
 
123
123
  ],
124
- "database_name": "APPS-0-DATABASE_NAME",
124
+ "database_name": "FAKE_APP_NAME",
125
125
  "migration_command": "rake db:migrate",
126
126
  "type": "rack",
127
127
  "repository_name": "APPS-0-REPOSITORY_NAME",
@@ -153,7 +153,7 @@
153
153
  }
154
154
  ],
155
155
  "aws_secret_key": "AWS-SECRET-KEY",
156
- "name": "APP-NAME_production",
156
+ "name": "FAKE_ENVIRONMENT_NAME",
157
157
  "ssh_keys": [
158
158
  "ssh-rsa AAAAB3NzaC1yc2EAAAAB key-a",
159
159
  "ssh-rsa AAAAB3NzaC1yc2EAAAAB key-b",
@@ -192,7 +192,7 @@
192
192
  },
193
193
  {
194
194
  "name": null,
195
- "public_hostname": "db_master.compute-1.amazonaws.com",
195
+ "public_hostname": "FAKE_DB_MASTER_PUBLIC_HOSTNAME",
196
196
  "components": [
197
197
  {
198
198
  "key": "ssmtp"
@@ -222,7 +222,7 @@
222
222
  },
223
223
  {
224
224
  "name": "foobarfoo",
225
- "public_hostname": "util_1.compute-1.amazonaws.com",
225
+ "public_hostname": "FAKE_UTIL_1_PUBLIC_HOSTNAME",
226
226
  "components": [
227
227
  {
228
228
  "key": "ssmtp"
@@ -242,7 +242,7 @@
242
242
  "backup_interval": 24,
243
243
  "admin_ssh_key": "ssh-rsa AAAAB3NzaC1 ey-cloud-production\n",
244
244
  "internal_ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAAB \n",
245
- "ssh_username": "SSH-USERNAME",
245
+ "ssh_username": "FAKE_SSH_USERNAME",
246
246
  "internal_ssh_private_key": "-----BEGIN RSA PRIVATE KEY-----\nINTERNAL-SSH-PRIVATE-KEY\n-----END RSA PRIVATE KEY-----\n",
247
247
  "mailserver": "smtp.engineyard.com",
248
248
  "components": [
@@ -255,7 +255,7 @@
255
255
 
256
256
  ],
257
257
  "backup_window": 10,
258
- "stack_name": "nginx_passenger",
258
+ "stack_name": "FAKE_STACK_NAME",
259
259
  "alert_email": "ALERT-EMAIL",
260
260
  "ssh_password": "SSH-PASSWORD",
261
261
  "db_stack_name": "mysql",
@@ -0,0 +1,28 @@
1
+ {"environments":
2
+ [
3
+ {"app_master":
4
+ {"public_hostname":"app_master.compute-1.amazonaws.com","name":null,"status":"running","amazon_id":"i-aaaaaaaa","role":"app_master","id":11111
5
+ },"ssh_username":"FAKE_SSH_USERNAME","stack_name":"FAKE_STACK_NAME","framework_env":"production","name":"FAKE_ENVIRONMENT_NAME","account":
6
+ {"name":"FAKE_ACCOUNT_NAME","id":12345
7
+ },"instances_count":5,"apps":
8
+ [
9
+ {"repository_uri":"FAKE_REPOSITORY_URI","name":"FAKE_APP_NAME","account":
10
+ {"name":"FAKE_ACCOUNT_NAME","id":12345
11
+ },"id":292301
12
+ }
13
+ ],"id":382918,"instances":
14
+ [
15
+ {"public_hostname":"app_master.compute-1.amazonaws.com","name":null,"status":"running","amazon_id":"i-aaaaaaaa","role":"app_master","id":11111
16
+ },
17
+ {"public_hostname":"app_1.compute-1.amazonaws.com","name":null,"status":"running","amazon_id":"i-bbbbbbbb","role":"app","id":22222
18
+ },
19
+ {"public_hostname":"FAKE_DB_MASTER_PUBLIC_HOSTNAME","name":null,"status":"running","amazon_id":"i-cccccccc","role":"db_master","id":33333
20
+ },
21
+ {"public_hostname":"db_slave_1.compute-1.amazonaws.com","name":null,"status":"running","amazon_id":"i-cccccccc","role":"db_slave","id":44444
22
+ },
23
+ {"public_hostname":"FAKE_UTIL_1_PUBLIC_HOSTNAME","name":"data1_production_util1","status":"running","amazon_id":"i-dddddddd","role":"util","id":55555
24
+ }
25
+ ]
26
+ }
27
+ ]
28
+ }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: engineyard-metadata
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 4
10
- version: 0.0.4
9
+ - 5
10
+ version: 0.0.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Seamus Abshere
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-11 00:00:00 -05:00
18
+ date: 2010-10-14 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -35,7 +35,7 @@ dependencies:
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
38
- name: fakeweb
38
+ name: nap
39
39
  prerelease: false
40
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
41
  none: false
@@ -45,11 +45,12 @@ dependencies:
45
45
  hash: 3
46
46
  segments:
47
47
  - 0
48
- version: "0"
49
- type: :development
48
+ - 4
49
+ version: "0.4"
50
+ type: :runtime
50
51
  version_requirements: *id002
51
52
  - !ruby/object:Gem::Dependency
52
- name: fakefs
53
+ name: fakeweb
53
54
  prerelease: false
54
55
  requirement: &id003 !ruby/object:Gem::Requirement
55
56
  none: false
@@ -63,9 +64,23 @@ dependencies:
63
64
  type: :development
64
65
  version_requirements: *id003
65
66
  - !ruby/object:Gem::Dependency
66
- name: rspec
67
+ name: fakefs
67
68
  prerelease: false
68
69
  requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
69
84
  none: false
70
85
  requirements:
71
86
  - - ~>
@@ -75,7 +90,7 @@ dependencies:
75
90
  - 1
76
91
  version: "1"
77
92
  type: :development
78
- version_requirements: *id004
93
+ version_requirements: *id005
79
94
  description: Pulls metadata from EC2 and EngineYard so that your EngineYard AppCloud (Amazon EC2) instances know about each other.
80
95
  email: seamus@abshere.net
81
96
  executables: []
@@ -96,10 +111,14 @@ files:
96
111
  - lib/engineyard-metadata.rb
97
112
  - lib/engineyard-metadata/amazon_ec2_api.rb
98
113
  - lib/engineyard-metadata/chef_dna.rb
114
+ - lib/engineyard-metadata/engine_yard_cloud_api.rb
115
+ - lib/engineyard-metadata/insider.rb
99
116
  - lib/engineyard-metadata/metadata.rb
117
+ - lib/engineyard-metadata/outsider.rb
100
118
  - spec/metadata_spec.rb
101
119
  - spec/spec_helper.rb
102
120
  - spec/support/dna.json
121
+ - spec/support/engine_yard_cloud_api_response.json
103
122
  has_rdoc: true
104
123
  homepage: http://github.com/seamusabshere/engineyard-metadata
105
124
  licenses: []