ecology 0.0.1 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in your gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ecology (0.0.11)
5
+ multi_json
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ minitest (2.3.0)
11
+ mocha (0.9.12)
12
+ multi_json (1.0.3)
13
+ rake (0.9.2)
14
+ scope (0.2.1)
15
+ minitest
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ bundler (~> 1.0.10)
22
+ ecology!
23
+ mocha
24
+ rake
25
+ scope (~> 0.2.1)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Ooyala, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,221 @@
1
+ Ecology
2
+ =======
3
+
4
+ Ecology is a gem to handle configuration variables. At Ooyala, we use
5
+ it for setting application metadata about logging, monitoring,
6
+ testing, deployment and other "outside the application"
7
+ infrastructure. So it's the application's ecology, right?
8
+
9
+ Installing
10
+ ==========
11
+
12
+ "gem install ecology" works pretty well. You can also specify Ecology
13
+ from a Gemfile if you're using Bundler.
14
+
15
+ Ooyalans should make sure that "gems.sv2" is listed as a gem source in
16
+ your Gemfile or on your gem command line.
17
+
18
+ Finding Your Ecology
19
+ ====================
20
+
21
+ By default an application called "bob.sh" will have an ecology file in
22
+ the same directory called "bob.ecology". Ecology just strips off the
23
+ final file extension, replaces it with ".ecology", and looks there.
24
+
25
+ You can also specify a different location in your Ecology.read call,
26
+ or set the ECOLOGY_SPEC environment variable to a different location.
27
+
28
+ An Ecology is a JSON file of roughly this structure:
29
+
30
+ {
31
+ "application": "MyApp",
32
+ "environment-from": "RACK_ENV",
33
+ "logging": {
34
+ "default_component": "SplodgingLib",
35
+ "extra_json_fields": {
36
+ "app_group": "SuperSpiffyGroup",
37
+ "precedence": 7
38
+ },
39
+ "console_print": "off",
40
+ "filename": "/tmp/bobo.txt",
41
+ "stderr_level": "fatal"
42
+ },
43
+ "monitoring": {
44
+ "zookeeper-host": "zookeeper-dev.sv2"
45
+ }
46
+ }
47
+
48
+ Absolutely every part of it is optional, including the presence of the file at all.
49
+
50
+ You can override the application name, as shown above.
51
+
52
+ Paths
53
+ =====
54
+
55
+ If you have a configurable per-environment path, you probably want it in the "paths"
56
+ section of your ecology. For instance:
57
+
58
+ {
59
+ "application": "SomeApp",
60
+ "paths": {
61
+ "pid_location": "/pid_dir/",
62
+ "app1_location": "$app/../dir1",
63
+ "app1_log_path": "$cwd/logs"
64
+ }
65
+ }
66
+
67
+ You can then access these paths with Ecology.path("app1_location") and
68
+ similar. In the paths, "$app" will be replaced by the directory the
69
+ application is run from, "$cwd" will be replaced by the current
70
+ working directory, "$env" will be replaced by the current environment,
71
+ and "$pid" will be replaced by the current process ID.
72
+
73
+ Reading Data
74
+ ============
75
+
76
+ If your library is configured via Ecology, you'll likely want to read data
77
+ from it. For instance, let's look at the Termite logging library's method
78
+ of configuration:
79
+
80
+ {
81
+ "application": "SomeApp",
82
+ "logging": {
83
+ "level": "info",
84
+ "stderr_level": "warn",
85
+ "stdout_level": 4,
86
+ "file_path": "$app/../log_to",
87
+ "extra_json_fields": {
88
+ "app_tag": "splodging_apps",
89
+ "precedence": 9
90
+ }
91
+ }
92
+ }
93
+
94
+ Termite can read the level via Ecology.property("logging:level"), which will
95
+ give it in whatever form it appears in the JSON.
96
+
97
+ Ecology.property("logging:extra_json_fields") would be returned as a Hash.
98
+ You can return it as a String, Symbol, Array, Fixnum or Hash by supplying
99
+ the :as option:
100
+
101
+ Ecology.property("logging:info", :as => Symbol) # :info
102
+ Ecology.property("logging:stdout_level", :as => String) # "4"
103
+ Ecology.property("logging:extra_json_fields", :as => Symbol) # error!
104
+ Ecology.property("logging:file_path", :as => :path) # "/home/theuser/sub/log_to"
105
+
106
+ Environment-Specific Data
107
+ =========================
108
+
109
+ Often you'll want to supply a different path, hostname or other
110
+ configuration variable depending on what environment you're
111
+ currently deployed to - staging may want a different MemCacheD
112
+ server than development, say.
113
+
114
+ Here's another logging example:
115
+
116
+ {
117
+ "application": "Ooyala Rails",
118
+ "environment-from": ["RAILS_ENV", "RACK_ENV"],
119
+ "logging": {
120
+ "console_out": {
121
+ "env:development": true,
122
+ "env:*": false
123
+ },
124
+ "stderr_level": {
125
+ "env:development": "fatal",
126
+ "env:production": "warn"
127
+ },
128
+ "stdout_level": "info"
129
+ }
130
+ }
131
+
132
+ In this case, data can be converted from a Hash into a Fixnum
133
+ or String automatically:
134
+
135
+ Ecology.property("logging:stderr_level", :as => String)
136
+
137
+ Ecology returns "fatal" or "warn" here, depending on the value
138
+ of RAILS_ENV or RACK_ENV.
139
+
140
+ Using Other Ecologies
141
+ =====================
142
+
143
+ The data in a given Ecology file can build on one or more
144
+ other Ecology files.
145
+
146
+ {
147
+ "application": "SomeApp",
148
+ "environment-from": [ "APP_ENV", "RACK_ENV" ],
149
+ "uses": [ "ecologies/logging.ecology", "ecologies/monitoring.ecology" ]
150
+ }
151
+
152
+ Each field will be overridden by the "latest" value -- the top-level
153
+ Ecology overrides the Ecologies that it uses, and so on. If multiple
154
+ Ecologies are used, the earlier Ecologies in the list override the
155
+ later Ecologies.
156
+
157
+ This can be used to set up Ecology "modules" for common functionality,
158
+ or to override certain settings in certain environments from a common
159
+ base template.
160
+
161
+ Events
162
+ ======
163
+
164
+ You often want to set your ecology-related properties when the ecology
165
+ is initialized, but no earlier. You may not know exactly when the
166
+ earliest call to Ecology.read will be. In that case, you want to use
167
+ the on_initialize event hook:
168
+
169
+ Ecology.on_initialize do
170
+ @my_property = Ecology.property("my:property")
171
+ end
172
+
173
+ If the ecology was already initialized before you set the
174
+ on_initialize hook, then the hook will run immediately.
175
+
176
+ There is also an on_reset hook. Read "Testing with an Ecology" to
177
+ find out why you'd ever care about that.
178
+
179
+ Testing with an Ecology
180
+ =======================
181
+
182
+ In production use, you'll probably never reset the ecology. However,
183
+ in testing you may frequently want to, especially if you're testing a
184
+ library that ties closely into the ecology.
185
+
186
+ There are two basic approaches your library can take, and they affect
187
+ testing.
188
+
189
+ Termite, our logging library, copies settings from the ecology into
190
+ its instance. Then, when you reset the ecology, you can also discard
191
+ old logger objects with old settings.
192
+
193
+ Glowworm, our feature flags library, is basically a big singleton and
194
+ uses ecology data, so it needs to reset its internal state when the
195
+ ecology is reset, and then re-read that state when the ecology is next
196
+ initialized.
197
+
198
+ Code for that for your library might look something like:
199
+
200
+ MyLib.on_reset do
201
+ @myvar1 = nil
202
+ @myvar2 = nil
203
+ end
204
+
205
+ MyLib.on_initialize do
206
+ @myvar1 = Ecology.property("mylib:property1", :as => :string)
207
+ @myvar2 = Ecology.property("mylib:property2", :as => :path)
208
+ end
209
+
210
+ Hooks persist across resets. That is, your on_reset hook will be
211
+ called on every reset until you explicitly remove it.
212
+
213
+ Releasing within Ooyala
214
+ =======================
215
+
216
+ Ooyalans, to release Ecology to gems.sv2, use the following:
217
+
218
+ gem build
219
+ rake _0.8.7_ -f ../ooyala_gems.rake gem:push ecology-0.0.1.gem
220
+
221
+ Change the version to the actual version you'd like to push.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require "bundler"
2
+ require "rake/testtask"
3
+
4
+ require File.join(File.dirname(__FILE__), "lib", "ecology", "version")
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = Dir.glob("test/**/*test.rb")
9
+ t.verbose = true
10
+ end
11
+
12
+ desc 'Builds the gem'
13
+ task :build do
14
+ sh "gem build ecology.gemspec"
15
+ end
16
+
17
+ desc 'Builds and installs the gem'
18
+ task :install => :build do
19
+ sh "gem install ecology-#{Ecology::VERSION}"
20
+ end
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ * Use Erubis before evaluating
2
+
3
+ * On Ecology.read calls, make sure ecology filename hasn't changed (and warn if it has)
4
+
5
+ * Have Ecology.read try to do a read relative to the executable's directory first rather than relative to cwd.
data/ecology.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "ecology/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "ecology"
8
+ s.version = Ecology::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Noah Gibbs"]
11
+ s.email = ["noah@ooyala.com"]
12
+ s.homepage = "http://www.ooyala.com"
13
+ s.summary = %q{Ruby config variable management}
14
+ s.description = <<EOS
15
+ Ecology sets configuration data for an application based
16
+ on environment variables and other factors. It is meant
17
+ to unify configuration data for logging, testing, monitoring
18
+ and deployment.
19
+ EOS
20
+
21
+ s.rubyforge_project = "ecology"
22
+
23
+ ignores = File.readlines(".gitignore").grep(/\S+/).map {|pattern| pattern.chomp }
24
+ dotfiles = Dir[".*"]
25
+ s.files = Dir["**/*"].reject {|f| File.directory?(f) || ignores.any? {|i| File.fnmatch(i, f) } } + dotfiles
26
+ s.test_files = s.files.grep(/^test\//)
27
+
28
+ s.require_paths = ["lib"]
29
+
30
+ s.add_dependency "multi_json"
31
+
32
+ s.add_development_dependency "bundler", "~> 1.0.10"
33
+ s.add_development_dependency "scope", "~> 0.2.1"
34
+ s.add_development_dependency "mocha"
35
+ s.add_development_dependency "rake"
36
+ end
data/ecology.gemspec~ ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "ecology/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "ecology"
8
+ s.version = Ecology::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Noah Gibbs"]
11
+ s.email = ["noah@ooyala.com"]
12
+ s.homepage = "http://www.ooyala.com"
13
+ s.summary = %q{Ruby config variable management}
14
+ s.description = <<EOS
15
+ Ecology sets configuration data for an application based
16
+ on environment variables and other factors. It is meant
17
+ to unify configuration data for logging, testing, monitoring
18
+ and deployment.
19
+ EOS
20
+
21
+ s.rubyforge_project = "ecology"
22
+
23
+ ignores = File.readlines(".gitignore").grep(/\S+/).map {|pattern| pattern.chomp }
24
+ dotfiles = [".gemtest", ".gitignore", ".rspec", ".yardopts"]
25
+ s.files = Dir["**/*"].reject {|f| File.directory?(f) || ignores.any? {|i| File.fnmatch(i, f) } } + dotfiles
26
+ s.test_files = s.files.grep(/^spec\//)
27
+
28
+ s.require_paths = ["lib"]
29
+
30
+ s.add_dependency "multi_json"
31
+
32
+ s.add_development_dependency "bundler", "~> 1.0.10"
33
+ s.add_development_dependency "scope", "~> 0.2.1"
34
+ s.add_development_dependency "mocha"
35
+ s.add_development_dependency "rake"
36
+ end
@@ -0,0 +1,3 @@
1
+ module Ecology
2
+ VERSION = "0.0.11"
3
+ end
@@ -0,0 +1,3 @@
1
+ module Ecology
2
+ VERSION = "0.0.10"
3
+ end
data/lib/ecology.rb ADDED
@@ -0,0 +1,264 @@
1
+ require "multi_json"
2
+ require "thread"
3
+
4
+ module Ecology
5
+ class << self
6
+ attr_reader :application
7
+ attr_reader :data
8
+ attr_reader :environment
9
+ attr_accessor :mutex
10
+ end
11
+
12
+ ECOLOGY_EXTENSION = ".ecology"
13
+
14
+ Ecology.mutex = Mutex.new
15
+
16
+ class << self
17
+ # Normally this is only for testing.
18
+ def reset
19
+ # Preserve triggers across resets by default
20
+ @triggers ||= {}
21
+
22
+ @application = nil
23
+ @environment = nil
24
+ @data = nil
25
+ @ecology_initialized = nil
26
+
27
+ publish_event :reset
28
+ end
29
+
30
+ def clear_triggers
31
+ @triggers = {}
32
+ end
33
+
34
+ def read(ecology_pathname = nil)
35
+ return if @ecology_initialized
36
+
37
+ should_publish_event = false
38
+
39
+ mutex.synchronize do
40
+ return if @ecology_initialized
41
+
42
+ file_path = ENV['ECOLOGY_SPEC'] || ecology_pathname || default_ecology_name
43
+ if File.exist?(file_path)
44
+ @data = {}
45
+ contents = merge_with_overrides(file_path)
46
+ end
47
+
48
+ @application ||= File.basename($0)
49
+ @environment ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development"
50
+
51
+ should_publish_event = true
52
+
53
+ @ecology_initialized = true
54
+ end
55
+
56
+ # Do this outside the mutex to reduce the likelihood
57
+ # of deadlocks.
58
+ publish_event(:initialize) if should_publish_event
59
+ end
60
+
61
+ def on_initialize(token = nil, &block)
62
+ on_event(:initialize, token, &block)
63
+ end
64
+
65
+ def on_reset(token = nil, &block)
66
+ on_event(:reset, token, &block)
67
+ end
68
+
69
+ def remove_trigger(token)
70
+ @triggers ||= {}
71
+ @triggers.each do |event, trigger_list|
72
+ @triggers[event].delete(token)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def on_event(event, token = nil, &block)
79
+ mutex.synchronize do
80
+ @token_offset ||= 0
81
+ token ||= "token#{@token_offset}"
82
+
83
+ @triggers ||= {}
84
+ @triggers[event] ||= {}
85
+ @triggers[event][token] = block
86
+
87
+ if event == :initialize && @ecology_initialized
88
+ block.call
89
+ end
90
+ end
91
+ end
92
+
93
+ def publish_event(event)
94
+ @triggers ||= {}
95
+
96
+ # This doesn't lock the mutex, because there's too high
97
+ # a chance of somebody calling Ecology.read or on_event
98
+ # or something while we're doing this. That would
99
+ # deadlock, which is no good.
100
+ (@triggers[event] || {}).each do |token, event_block|
101
+ event_block.call
102
+ end
103
+ end
104
+
105
+ def merge_with_overrides(file_path)
106
+ contents = File.read(file_path)
107
+ file_data = MultiJson.decode(contents);
108
+
109
+ return unless file_data
110
+
111
+ # First, try to set @application and @environment from the file data
112
+
113
+ @application ||= file_data["application"]
114
+ @environment ||= file_data["environment"]
115
+
116
+ if !@environment && file_data["environment-from"]
117
+ from = file_data["environment-from"]
118
+ if from.respond_to?(:map)
119
+ @environment ||= from.map {|v| ENV[v]}.compact.first
120
+ else
121
+ @environment = ENV[from] ? ENV[from].to_s : nil
122
+ end
123
+ end
124
+
125
+ # Next, filter the data by the current environment
126
+ file_data = environmentize_data(file_data)
127
+
128
+ # Merge the file data into @data
129
+ @data = deep_merge(@data, file_data)
130
+
131
+ # Finally, process any inheritance/overrides
132
+ if file_data["uses"]
133
+ if file_data["uses"].respond_to?(:map)
134
+ file_data["uses"].map { |file| merge_with_overrides(file) }
135
+ else
136
+ merge_with_overrides(file_data["uses"])
137
+ end
138
+ end
139
+ end
140
+
141
+ def deep_merge(hash1, hash2)
142
+ all_keys = hash1.keys | hash2.keys
143
+ ret = {}
144
+
145
+ all_keys.each do |key|
146
+ if hash1.has_key?(key) && hash2.has_key?(key)
147
+ if hash1[key].is_a?(Hash) && hash2[key].is_a?(Hash)
148
+ ret[key] = deep_merge(hash1[key], hash2[key])
149
+ else
150
+ ret[key] = hash1[key]
151
+ end
152
+ elsif hash1.has_key?(key)
153
+ ret[key] = hash1[key]
154
+ else
155
+ ret[key] = hash2[key]
156
+ end
157
+ end
158
+
159
+ ret
160
+ end
161
+
162
+ def environmentize_data(data_in)
163
+ if data_in.is_a?(Array)
164
+ data_in.map { |subdata| environmentize_data(subdata) }
165
+ elsif data_in.is_a?(Hash)
166
+ if data_in.keys.any? { |k| k =~ /^env:/ }
167
+ value = data_in["env:#{@environment}"] || data_in["env:*"]
168
+ return nil unless value
169
+ environmentize_data(value)
170
+ else
171
+ data_out = {}
172
+ data_in.each { |k, v| data_out[k] = environmentize_data(v) }
173
+ data_out
174
+ end
175
+ else
176
+ data_in
177
+ end
178
+ end
179
+
180
+ public
181
+
182
+ def property(param, options = {})
183
+ components = param.split(":").compact.select {|s| s != ""}
184
+
185
+ value = components.inject(@data) do |data, component|
186
+ if data
187
+ data[component]
188
+ else
189
+ nil
190
+ end
191
+ end
192
+
193
+ return nil unless value
194
+ return value unless options[:as]
195
+
196
+ unless value.is_a?(Hash)
197
+ if [String, :string].include?(options[:as])
198
+ return value.to_s
199
+ elsif [Symbol, :symbol].include?(options[:as])
200
+ return value.to_s.to_sym
201
+ elsif [Fixnum, :int, :integer, :fixnum].include?(options[:as])
202
+ return value.to_i
203
+ elsif [Hash, :hash].include?(options[:as])
204
+ raise "Cannot convert scalar value to Hash!"
205
+ elsif [:path].include?(options[:as])
206
+ return string_to_path(value.to_s)
207
+ elsif [:json].include?(options[:as])
208
+ raise "JSON return type not yet supported!"
209
+ else
210
+ raise "Unknown type #{options[:as].inspect} passed to Ecology.data(:as) for property #{property}!"
211
+ end
212
+ end
213
+
214
+ return value if options[:as] == Hash
215
+ raise "Couldn't convert JSON fields to #{options[:as].inspect} for property #{property}!"
216
+ end
217
+
218
+ PATH_SUBSTITUTIONS = {
219
+ "$env" => proc { Ecology.environment },
220
+ "$cwd" => proc { Dir.getwd },
221
+ "$app" => proc { File.dirname($0) },
222
+ "$pid" => proc { Process.pid.to_s },
223
+ }
224
+
225
+ def path(path_name)
226
+ path_data = @data ? @data["paths"] : nil
227
+ return nil unless path_data && path_data[path_name]
228
+
229
+ string_to_path path_data[path_name]
230
+ end
231
+
232
+ private
233
+
234
+ def string_to_path(path)
235
+ PATH_SUBSTITUTIONS.each do |key, value|
236
+ path.gsub! key, value.call
237
+ end
238
+
239
+ path
240
+ end
241
+
242
+ public
243
+
244
+ def default_ecology_name(executable = $0)
245
+ suffix = File.extname(executable)
246
+ executable[0..(executable.length - 1 - suffix.size)] +
247
+ ECOLOGY_EXTENSION
248
+ end
249
+
250
+ # This is a convenience function because the Ruby
251
+ # thread API has no accessor for the thread ID,
252
+ # but includes it in "to_s" (buh?)
253
+ def thread_id(thread)
254
+ return "main" if thread == Thread.main
255
+
256
+ str = thread.to_s
257
+
258
+ match = nil
259
+ match = str.match /(0x\d+)/
260
+ return nil unless match
261
+ match[1]
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+ require "thread"
3
+
4
+ class EcologyTest < Scope::TestCase
5
+ context "with ecology" do
6
+ setup do
7
+ Ecology.reset
8
+ end
9
+
10
+ should "correctly determine default ecology names" do
11
+ assert_equal "/path/to/bob.txt.ecology", Ecology.default_ecology_name("/path/to/bob.txt.rb")
12
+ assert_equal "relative/path/to/app.ecology", Ecology.default_ecology_name("relative/path/to/app.rb")
13
+ assert_equal "/path/to/bob.ecology", Ecology.default_ecology_name("/path/to/bob.sh")
14
+ assert_equal "\\path\\to\\bob.ecology", Ecology.default_ecology_name("\\path\\to\\bob.EXE")
15
+ end
16
+
17
+ should "respect the ECOLOGY_SPEC environment variable" do
18
+ ENV['ECOLOGY_SPEC'] = '/tmp/bobo.txt'
19
+ File.expects(:exist?).with('/tmp/bobo.txt').returns(true)
20
+ File.expects(:read).with('/tmp/bobo.txt').returns('{ "application": "foo_app" }')
21
+ Ecology.read
22
+
23
+ assert_equal "foo_app", Ecology.application
24
+ end
25
+
26
+ should "recognize that this is the main thread" do
27
+ assert_equal "main", Ecology.thread_id(Thread.current)
28
+ end
29
+
30
+ should "work without an ECOLOGY_SPEC" do
31
+ $0 = "whatever_app.rb"
32
+
33
+ ENV['ECOLOGY_SPEC'] = nil
34
+ File.expects(:exist?).with("whatever_app.ecology").returns(false)
35
+
36
+ Ecology.read
37
+
38
+ assert_equal "whatever_app.rb", Ecology.application
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,78 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class EnvironmentTest < Scope::TestCase
4
+ setup do
5
+ Ecology.reset
6
+ end
7
+
8
+ context "with environment-from in your ecology" do
9
+ setup do
10
+ set_up_ecology <<ECOLOGY_CONTENTS
11
+ {
12
+ "application": "SomeApp",
13
+ "environment-from": ["SOME_ENV_VAR", "VAR2"]
14
+ }
15
+ ECOLOGY_CONTENTS
16
+
17
+ ENV["SOME_ENV_VAR"] = ENV["VAR2"] = nil
18
+ end
19
+
20
+ should "default to the development environment" do
21
+ Ecology.read
22
+ assert_equal "development", Ecology.environment
23
+ end
24
+
25
+ should "use the environment variables to determine environment" do
26
+ ENV["SOME_ENV_VAR"] = "staging"
27
+ Ecology.read
28
+ assert_equal "staging", Ecology.environment
29
+ end
30
+
31
+ should "use secondary environment variables when the primary isn't set" do
32
+ ENV["VAR2"] = "daily-staging"
33
+ Ecology.read
34
+ assert_equal "daily-staging", Ecology.environment
35
+ end
36
+
37
+ should "use primary environment variables in preference to secondary" do
38
+ ENV["SOME_ENV_VAR"] = "theatrical staging"
39
+ ENV["VAR2"] = "daily-staging"
40
+ Ecology.read
41
+ assert_equal "theatrical staging", Ecology.environment
42
+ end
43
+ end
44
+
45
+ context "with an environment override in your ecology" do
46
+ setup do
47
+ set_up_ecology <<ECOLOGY_CONTENTS
48
+ {
49
+ "application": "SomeApp",
50
+ "environment": "not really staging",
51
+ "environment-from": "SOME_ENV_VAR"
52
+ }
53
+ ECOLOGY_CONTENTS
54
+ end
55
+
56
+ should "use the environment override" do
57
+ Ecology.read
58
+ assert_equal "not really staging", Ecology.environment
59
+ end
60
+ end
61
+
62
+ context "with a single environment-from in your ecology" do
63
+ setup do
64
+ set_up_ecology <<ECOLOGY_CONTENTS
65
+ {
66
+ "application": "SomeApp",
67
+ "environment-from": "SOME_ENV_VAR"
68
+ }
69
+ ECOLOGY_CONTENTS
70
+ end
71
+
72
+ should "use the environment override" do
73
+ ENV['SOME_ENV_VAR'] = "bob's pants"
74
+ Ecology.read
75
+ assert_equal "bob's pants", Ecology.environment
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class EnvironmentVarTest < Scope::TestCase
4
+ setup do
5
+ Ecology.reset
6
+ end
7
+
8
+ context "with environments in your ecology" do
9
+ setup do
10
+ set_up_ecology <<ECOLOGY_CONTENTS
11
+ {
12
+ "application": "SomeApp",
13
+ "environment-from": ["RACK_ENV"],
14
+ "domain": {
15
+ "property1" : {
16
+ "env:staging": "value1",
17
+ "env:development": "value2",
18
+ "env:*": "value3"
19
+ }
20
+ }
21
+ }
22
+ ECOLOGY_CONTENTS
23
+
24
+ ENV["RACK_ENV"] = nil
25
+ end
26
+
27
+ should "select the right environment value for a property" do
28
+ ENV["RACK_ENV"] = "staging"
29
+ Ecology.read
30
+ assert_equal "value1", Ecology.property("domain::property1", :as => String)
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,77 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class OverridePropertiesTest < Scope::TestCase
4
+ setup do
5
+ Ecology.reset
6
+ end
7
+
8
+ context "with environment-from in your ecology" do
9
+ setup do
10
+ set_up_ecology <<ECOLOGY_GRANDPARENT1_CONTENTS, "grandparent1.ecology"
11
+ {
12
+ "testing": {
13
+ "capabilities": [ "rails", "rvm" ],
14
+ "othertag": 9
15
+ },
16
+ "monitoring": {
17
+ "property9": 71
18
+ }
19
+ }
20
+ ECOLOGY_GRANDPARENT1_CONTENTS
21
+
22
+ set_up_ecology <<ECOLOGY_GRANDPARENT2_CONTENTS, "grandparent2.ecology"
23
+ {
24
+ "monitoring": {
25
+ "property3": 7,
26
+ "property9": 134
27
+ }
28
+ }
29
+ ECOLOGY_GRANDPARENT2_CONTENTS
30
+
31
+ set_up_ecology <<ECOLOGY_PARENT_CONTENTS, "parent.ecology"
32
+ {
33
+ "uses": ["grandparent1.ecology", "grandparent2.ecology"],
34
+ "logging": {
35
+ "property1": "foo"
36
+ },
37
+ "monitoring": {
38
+ "property1": "burgers",
39
+ "property2": "bar",
40
+ "property3": "quux"
41
+ }
42
+ }
43
+ ECOLOGY_PARENT_CONTENTS
44
+
45
+ set_up_ecology <<ECOLOGY_CONTENTS
46
+ {
47
+ "application": "SomeApp",
48
+ "uses": "parent.ecology",
49
+ "monitoring": {
50
+ "property2": "baz"
51
+ }
52
+ }
53
+ ECOLOGY_CONTENTS
54
+ Ecology.read
55
+ end
56
+
57
+ should "get overridden properties correctly" do
58
+ assert_equal "baz", Ecology.property("monitoring::property2")
59
+ end
60
+
61
+ should "get inherited properties correctly" do
62
+ assert_equal "foo", Ecology.property("logging::property1")
63
+ end
64
+
65
+ should "get properties in an overridden hash" do
66
+ assert_equal "burgers", Ecology.property("monitoring::property1")
67
+ end
68
+
69
+ should "get grandparent properties" do
70
+ assert_equal 9, Ecology.property("testing::othertag")
71
+ end
72
+
73
+ should "have first parent override second parent properties" do
74
+ assert_equal 71, Ecology.property("monitoring::property9")
75
+ end
76
+ end
77
+ end
data/test/path_test.rb ADDED
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class PathTest < Scope::TestCase
4
+ setup do
5
+ Ecology.reset
6
+ end
7
+
8
+ context "with paths in your ecology" do
9
+ setup do
10
+ set_up_ecology <<ECOLOGY_CONTENTS
11
+ {
12
+ "application": "SomeApp",
13
+ "paths": {
14
+ "pid_location": "/pid_dir/",
15
+ "whozit_location": "$app/../dir1",
16
+ "whatsit_path": "$cwd/logs",
17
+ "some_other_location": "dir/to/there.$pid"
18
+ }
19
+ }
20
+ ECOLOGY_CONTENTS
21
+ Ecology.read
22
+ end
23
+
24
+ should "find an absolute path" do
25
+ assert_equal "/pid_dir/", Ecology.path("pid_location")
26
+ end
27
+
28
+ should "find an application-relative path" do
29
+ $0 = "some/path/my_app.rb"
30
+ assert_equal "some/path/../dir1", Ecology.path("whozit_location")
31
+ end
32
+
33
+ should "find a cwd-relative path" do
34
+ Dir.expects(:getwd).returns("some/path")
35
+ assert_equal "some/path/logs", Ecology.path("whatsit_path")
36
+ end
37
+
38
+ should "substitute correctly for a PID path" do
39
+ Process.expects(:pid).returns(379)
40
+ assert_equal "dir/to/there.379", Ecology.path("some_other_location")
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,76 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class PropertyTest < Scope::TestCase
4
+ setup do
5
+ Ecology.reset
6
+ end
7
+
8
+ context "with environments in your ecology" do
9
+ setup do
10
+ set_up_ecology <<ECOLOGY_CONTENTS
11
+ {
12
+ "application": "SomeApp",
13
+ "environment-from": ["RACK_ENV"],
14
+ "domain": {
15
+ "property1" : "strval1",
16
+ "property2" : "374",
17
+ "property3" : 1987
18
+ }
19
+ }
20
+ ECOLOGY_CONTENTS
21
+ Ecology.read
22
+ end
23
+
24
+ should "get a top-level property" do
25
+ assert_equal "SomeApp", Ecology.property("application")
26
+ end
27
+
28
+ should "get a string property without a typecast" do
29
+ assert_equal "strval1", Ecology.property("domain::property1")
30
+ end
31
+
32
+ should "get a string property with a typecast" do
33
+ assert_equal "strval1", Ecology.property("domain::property1", :as => String)
34
+ end
35
+
36
+ should "get a string-number property with a String typecast" do
37
+ assert_equal "374", Ecology.property("domain::property2", :as => String)
38
+ end
39
+
40
+ should "get a string-number property with a Fixnum typecast" do
41
+ assert_equal 374, Ecology.property("domain::property2", :as => Fixnum)
42
+ end
43
+
44
+ should "get a string-number property with no typecast" do
45
+ assert_equal "374", Ecology.property("domain::property2")
46
+ end
47
+
48
+ should "get an integer property with a String typecast" do
49
+ assert_equal "1987", Ecology.property("domain::property3", :as => String)
50
+ end
51
+
52
+ should "get an integer property with a Fixnum typecast" do
53
+ assert_equal 1987, Ecology.property("domain::property3", :as => Fixnum)
54
+ end
55
+
56
+ should "get an integer property with no typecast" do
57
+ assert_equal 1987, Ecology.property("domain::property3")
58
+ end
59
+
60
+ should "be able to use :integer for a Fixnum typecast" do
61
+ assert_equal 374, Ecology.property("domain::property2", :as => :integer)
62
+ end
63
+
64
+ should "be able to use :int for a Fixnum typecast" do
65
+ assert_equal 374, Ecology.property("domain::property2", :as => :int)
66
+ end
67
+
68
+ should "be able to use :string for a String typecast" do
69
+ assert_equal "1987", Ecology.property("domain::property3", :as => :string)
70
+ end
71
+
72
+ should "be able to use :symbol for a Symbol typecast" do
73
+ assert_equal "1987".to_sym, Ecology.property("domain::property3", :as => :symbol)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,18 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.require(:default, :development)
4
+ require "minitest/autorun"
5
+
6
+ # For testing Ecology itself, use the local version *first*.
7
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
8
+
9
+ require "ecology"
10
+
11
+ class Scope::TestCase
12
+ def set_up_ecology(file_contents, filename = "some.ecology")
13
+ ENV["ECOLOGY_SPEC"] = filename
14
+ File.stubs(:exist?).with(filename).returns(true)
15
+ File.expects(:read).with(filename).returns(file_contents).at_least_once
16
+ end
17
+
18
+ end
@@ -0,0 +1,127 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class EnvironmentTest < Scope::TestCase
4
+ setup do
5
+ Ecology.reset
6
+ end
7
+
8
+ teardown do
9
+ Ecology.clear_triggers
10
+ end
11
+
12
+ context "without an ecology" do
13
+ should "call on_initialize events at initialize" do
14
+ callee_mock = mock("object that gets called")
15
+ callee_mock.expects(:method)
16
+
17
+ Ecology.on_initialize("test_on_init") { callee_mock.method }
18
+ Ecology.read
19
+ end
20
+
21
+ should "call on_initialize events when called after initialize" do
22
+ callee_mock = mock("object that gets called")
23
+ callee_mock.expects(:method)
24
+
25
+ Ecology.read
26
+ Ecology.on_initialize("test_on_init") { callee_mock.method }
27
+ end
28
+
29
+ should "call on_initialize events with no token" do
30
+ callee_mock = mock("object that gets called")
31
+ callee_mock.expects(:method)
32
+
33
+ Ecology.read
34
+ Ecology.on_initialize { callee_mock.method }
35
+ end
36
+ end
37
+
38
+ context "with an ecology" do
39
+ setup do
40
+ set_up_ecology <<ECOLOGY_CONTENTS
41
+ {
42
+ "application": "SomeApp"
43
+ }
44
+ ECOLOGY_CONTENTS
45
+ end
46
+
47
+ should "call on_initialize events at initialize" do
48
+ callee_mock = mock("object that gets called")
49
+ callee_mock.expects(:method)
50
+
51
+ Ecology.on_initialize("test_on_init") { callee_mock.method }
52
+ Ecology.read
53
+ end
54
+
55
+ should "call on_initialize events when called after initialize" do
56
+ callee_mock = mock("object that gets called")
57
+ callee_mock.expects(:method)
58
+
59
+ Ecology.read
60
+ Ecology.on_initialize("test_on_init") { callee_mock.method }
61
+ end
62
+
63
+ should "call on_initialize events again after reset" do
64
+ callee_mock = mock("object that gets called")
65
+ callee_mock.expects(:method).twice
66
+
67
+ Ecology.on_initialize("test_on_init") { callee_mock.method }
68
+ Ecology.read
69
+ Ecology.reset
70
+ Ecology.read
71
+ end
72
+
73
+ should "call tokenless on_initialize events again after reset" do
74
+ callee_mock = mock("object that gets called")
75
+ callee_mock.expects(:method).twice
76
+
77
+ Ecology.on_initialize { callee_mock.method }
78
+ Ecology.read
79
+ Ecology.reset
80
+ Ecology.read
81
+ end
82
+
83
+ should "call on_reset events across multiple resets" do
84
+ callee_mock = mock("object that gets called")
85
+ callee_mock.expects(:method).twice
86
+
87
+ Ecology.read
88
+ Ecology.on_reset("test_on_reset") { callee_mock.method }
89
+ Ecology.reset
90
+ Ecology.reset
91
+ end
92
+
93
+ should "call on_reset events with no token across multiple resets" do
94
+ callee_mock = mock("object that gets called")
95
+ callee_mock.expects(:method).twice
96
+
97
+ Ecology.read
98
+ Ecology.on_reset { callee_mock.method }
99
+ Ecology.reset
100
+ Ecology.reset
101
+ end
102
+
103
+ should "remove on_reset events after remove_trigger" do
104
+ callee_mock = mock("object that gets called")
105
+ callee_mock.expects(:method).once # Not three times...
106
+
107
+ Ecology.read
108
+ Ecology.on_reset("on_reset_remove") { callee_mock.method }
109
+ Ecology.reset
110
+ Ecology.remove_trigger("on_reset_remove")
111
+ Ecology.reset
112
+ Ecology.reset
113
+ end
114
+
115
+ should "repeat on_initialize events even when called after initialize" do
116
+ callee_mock = mock("object that gets called")
117
+ callee_mock.expects(:method).twice
118
+
119
+ Ecology.read
120
+ Ecology.on_initialize("test_on_init") { callee_mock.method }
121
+ Ecology.reset
122
+ Ecology.read
123
+ end
124
+
125
+ end
126
+
127
+ end
metadata CHANGED
@@ -1,45 +1,140 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ecology
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.0.11
6
6
  platform: ruby
7
- authors:
8
- - Caleb Spare
7
+ authors:
8
+ - Noah Gibbs
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-23 00:00:00.000000000Z
13
- dependencies: []
14
- description: ecology
15
- email:
16
- - caleb@ooyala.com
12
+
13
+ date: 2011-10-06 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: multi_json
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 1.0.10
35
+ type: :development
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: scope
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.2.1
46
+ type: :development
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: mocha
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: rake
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id005
70
+ description: |
71
+ Ecology sets configuration data for an application based
72
+ on environment variables and other factors. It is meant
73
+ to unify configuration data for logging, testing, monitoring
74
+ and deployment.
75
+
76
+ email:
77
+ - noah@ooyala.com
17
78
  executables: []
79
+
18
80
  extensions: []
81
+
19
82
  extra_rdoc_files: []
20
- files: []
21
- homepage: ''
83
+
84
+ files:
85
+ - ecology.gemspec
86
+ - ecology.gemspec~
87
+ - Gemfile
88
+ - Gemfile.lock
89
+ - lib/ecology/version.rb
90
+ - lib/ecology/version.rb~
91
+ - lib/ecology.rb
92
+ - LICENSE
93
+ - Rakefile
94
+ - README.md
95
+ - test/ecology_test.rb
96
+ - test/environment_test.rb
97
+ - test/environment_var_test.rb
98
+ - test/override_properties_test.rb
99
+ - test/path_test.rb
100
+ - test/property_test.rb
101
+ - test/test_helper.rb
102
+ - test/trigger_test.rb
103
+ - TODO
104
+ - .gitignore
105
+ homepage: http://www.ooyala.com
22
106
  licenses: []
107
+
23
108
  post_install_message:
24
109
  rdoc_options: []
25
- require_paths:
110
+
111
+ require_paths:
26
112
  - lib
27
- required_ruby_version: !ruby/object:Gem::Requirement
113
+ required_ruby_version: !ruby/object:Gem::Requirement
28
114
  none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: "0"
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
120
  none: false
35
- requirements:
36
- - - ! '>='
37
- - !ruby/object:Gem::Version
38
- version: '0'
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: "0"
39
125
  requirements: []
126
+
40
127
  rubyforge_project: ecology
41
- rubygems_version: 1.8.7
128
+ rubygems_version: 1.8.10
42
129
  signing_key:
43
130
  specification_version: 3
44
- summary: ecology
45
- test_files: []
131
+ summary: Ruby config variable management
132
+ test_files:
133
+ - test/ecology_test.rb
134
+ - test/environment_test.rb
135
+ - test/environment_var_test.rb
136
+ - test/override_properties_test.rb
137
+ - test/path_test.rb
138
+ - test/property_test.rb
139
+ - test/test_helper.rb
140
+ - test/trigger_test.rb