ecology 0.0.1 → 0.0.11

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