configuration_service 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 013e070f4e04c050a8fdd8232cac7fb22cb652b1
4
- data.tar.gz: 3d329fe8548014c9e43e1553a1e506d5438c6a7f
3
+ metadata.gz: fc898eb1373d71883928bb492b533ffadc0e895c
4
+ data.tar.gz: ec3e54e1a4768122bd1f0e14f77cfc8e6da36bed
5
5
  SHA512:
6
- metadata.gz: 226d8f3472c3a62716ced933186f5f926388575f29ab8ce91527d8b6ef19d5f9080409519dd8ecf37e405854705c17f7942ad6f994d8d4c76690866cc3438be9
7
- data.tar.gz: df846414184710ca2d20903e017f1712897e4df8c97dc569f3296eb8bf1ce5a5224586ed531dc8e00985822f42e4405020b89232e54e9b59ba2171ba8f3f2754
6
+ metadata.gz: 978bd8e5c8bd45ff27550378d5052bdadca16c72d4787fd88c5e5aca4a691b2906074b145e7123597cf5278a1e09932c5f33915857435b7c847ea6a2f6c51f4d
7
+ data.tar.gz: 4cbd165b89b7f76886333c3ec414391cf8db0254e00e50e6ac67cdb77367d28324a5963b9eae22b5a49eff198c836e0fbafec10a1747df0808662c20e3e8531f
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- configuration_service (1.0.0)
4
+ configuration_service (1.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -22,9 +22,18 @@ GEM
22
22
  multi_json (1.11.2)
23
23
  multi_test (0.1.2)
24
24
  rake (10.4.2)
25
+ rspec (3.3.0)
26
+ rspec-core (~> 3.3.0)
27
+ rspec-expectations (~> 3.3.0)
28
+ rspec-mocks (~> 3.3.0)
29
+ rspec-core (3.3.2)
30
+ rspec-support (~> 3.3.0)
25
31
  rspec-expectations (3.3.1)
26
32
  diff-lcs (>= 1.2.0, < 2.0)
27
33
  rspec-support (~> 3.3.0)
34
+ rspec-mocks (3.3.2)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.3.0)
28
37
  rspec-support (3.3.0)
29
38
 
30
39
  PLATFORMS
@@ -35,6 +44,7 @@ DEPENDENCIES
35
44
  configuration_service!
36
45
  cucumber (~> 2.0)
37
46
  rake (~> 10.0)
47
+ rspec (~> 3.3)
38
48
  rspec-expectations (~> 3.3)
39
49
 
40
50
  BUNDLED WITH
data/Rakefile CHANGED
@@ -1,8 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rdoc/task'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
3
6
 
4
7
  desc "Test the stub test orchestrator"
5
- task :test do
8
+ task :test => :spec do
6
9
  sh %{TEST_ORCHESTRATION_PROVIDER=stub bundle exec cucumber}
7
10
  end
8
11
 
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "configuration_service"
7
- spec.version = "1.0.0"
7
+ spec.version = "1.1.1"
8
8
  spec.authors = ["Sheldon Hearn"]
9
9
  spec.email = ["sheldonh@starjuice.net"]
10
10
 
@@ -20,5 +20,6 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "bundler", "~> 1.10"
21
21
  spec.add_development_dependency "rake", "~> 10.0"
22
22
  spec.add_development_dependency "cucumber", "~> 2.0"
23
+ spec.add_development_dependency "rspec", "~> 3.3"
23
24
  spec.add_development_dependency "rspec-expectations", "~> 3.3"
24
25
  end
@@ -9,6 +9,9 @@ module ConfigurationService
9
9
  # {consumption}[link:features/consuming_feature.html]
10
10
  # of identified configuration data with metadata.
11
11
  #
12
+ # It is recommended that consumers use a Factory to create
13
+ # and configure the service.
14
+ #
12
15
  class Base
13
16
 
14
17
  ##
@@ -0,0 +1,120 @@
1
+ module ConfigurationService
2
+
3
+ module Factory
4
+
5
+ class EnvironmentContext
6
+
7
+ class EnvDict < Hash # :nodoc: all
8
+
9
+ def initialize(env, *path)
10
+ @env = env
11
+ @path = build_path(*path)
12
+
13
+ env_keys.each do |k|
14
+ self.store(symbolic_key(k), @env[k])
15
+ end
16
+
17
+ self.default_proc = envdict_default_proc
18
+ end
19
+
20
+ def subslice(key)
21
+ EnvDict.new(@env, @path, key).tap do |s|
22
+ (@slices ||= []) << s
23
+ end
24
+ end
25
+
26
+ def subslice!(key)
27
+ delete_keys_prefixed_with!(key)
28
+ subslice(key)
29
+ end
30
+
31
+ def scrub!
32
+ @path or raise RuntimeError, "refusing to scrub without path"
33
+
34
+ scrub_env!
35
+ scrub_slices!
36
+ scrub_self!
37
+
38
+ @scrubbed = true
39
+ end
40
+
41
+ private
42
+
43
+ def envdict_default_proc
44
+ proc do |_, key|
45
+ assert_not_scrubbed!
46
+ try_string(key) or raise_key_error(key)
47
+ end
48
+ end
49
+
50
+ def try_string(k)
51
+ if (skey = k.intern rescue false) and self.include?(skey)
52
+ self.fetch(skey)
53
+ end
54
+ end
55
+
56
+ def raise_key_error(k)
57
+ envar = build_path(@path, k.to_s).upcase
58
+ raise KeyError, "missing environment variable #{envar}"
59
+ end
60
+
61
+ def env_keys
62
+ @env.keys.select { |k| under_path?(k) }
63
+ end
64
+
65
+ def under_path?(k)
66
+ !!symbolic_key(k)
67
+ end
68
+
69
+ def symbolic_key(k)
70
+ prefix = build_path(@path, "")
71
+ s = k.downcase
72
+ if s.slice!(prefix) and !s.empty?
73
+ s.intern
74
+ end
75
+ end
76
+
77
+ def build_path(*path)
78
+ path = path.compact
79
+ if path.empty?
80
+ nil
81
+ else
82
+ path.map { |p| p.to_s.downcase }.join("_")
83
+ end
84
+ end
85
+
86
+ def delete_keys_prefixed_with!(key)
87
+ self.keys.each do |k|
88
+ if k.to_s.start_with?("#{key}_")
89
+ self.delete(k)
90
+ end
91
+ end
92
+ end
93
+
94
+ def assert_not_scrubbed!
95
+ !@scrubbed or raise SecurityError, "can't access scrubbed environment dictionary"
96
+ end
97
+
98
+ def scrub_env!
99
+ env_keys.each do |k|
100
+ @env.delete(k)
101
+ end
102
+ end
103
+
104
+ def scrub_slices!
105
+ @slices.each { |s| s.scrub! } if @slices
106
+ end
107
+
108
+ def scrub_self!
109
+ self.keys.each do |k|
110
+ self.delete(k)
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+
120
+ end
@@ -0,0 +1,106 @@
1
+ module ConfigurationService
2
+
3
+ module Factory
4
+
5
+ ##
6
+ # Creates a Base bootstrapped with environmental configuration
7
+ #
8
+ # # With the following in the environment:
9
+ # #
10
+ # # CFGSRV_IDENTIFIER="acme"
11
+ # # CFGSRV_TOKEN="0b2a80f4-54ce-45f4-8267-f6558fee64af"
12
+ # # CFGSRV_PROVIDER="vault"
13
+ # # CFGSRV_PROVIDER_ADDRESS="http://127.0.0.1:8200"
14
+ #
15
+ # service = ConfigurationService::Factory::EnvironmentContext.create
16
+ # configuraton = service.request_configuration
17
+ # AcmeApplication.new(configuration.data).run
18
+ #
19
+ class EnvironmentContext
20
+
21
+ ##
22
+ # The prefix for matching environment variable names
23
+ #
24
+ # Names match if they begin with the prefix and an underscore.
25
+ #
26
+ attr_reader :prefix
27
+
28
+ ##
29
+ # The default prefix for matching environment variable names
30
+ #
31
+ DEFAULT_PREFIX = "CFGSRV" unless defined?(DEFAULT_PREFIX)
32
+
33
+ ##
34
+ # Returns a new factory
35
+ #
36
+ # The +env+ defaults to the process ENV and the +prefix+ defaults
37
+ # to the DEFAULT_PREFIX.
38
+ #
39
+ # Most consumers will not need to call this method; they can use
40
+ # .create which instantiates a factory with default +env+ and +prefix+
41
+ # and uses that internally.
42
+ #
43
+ def initialize(env = ENV, prefix = DEFAULT_PREFIX)
44
+ @env = EnvDict.new(env, prefix)
45
+ end
46
+
47
+ ##
48
+ # Return a Base bootstrapped with environmental configuration
49
+ #
50
+ # The environment is scanned for #prefix matches, within which
51
+ # the following variables are used:
52
+ #
53
+ # [IDENTIFIER] the unique identity of the configuration data
54
+ # (see Base#initialize)
55
+ # [TOKEN] authorization token for the identified configuration data
56
+ # (see Base#initialize)
57
+ # [PROVIDER] the unique identity of the service provider
58
+ # (see ProviderRegistry)
59
+ # [PROVIDER_*] configuration options for the service provider
60
+ # (see provider documentation)
61
+ #
62
+ # The service provider class is fetched from the ProviderRegistry using
63
+ # +PROVIDER+. A service provider instance is then constructed with a
64
+ # dictionary of the +PROVIDER_*+ variables, in which the keys are the
65
+ # name of the variable without +PROVIDER_+, downcased and intern'd.
66
+ #
67
+ # Then a service Base is constructed with the +IDENTIFIER+, +TOKEN+
68
+ # and service provider instance.
69
+ #
70
+ # And finally, the environment is scrubbed of the variables used, to
71
+ # protect them from accidental exposure (e.g. in an exception handler
72
+ # that prints the environment).
73
+ #
74
+ # Returns the constructed service Base.
75
+ #
76
+ def create
77
+ ConfigurationService.new(@env[:identifier], @env[:token], provider).tap do
78
+ @env.scrub! # TODO mandates that scrub not try to mutate the strings given to provider
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Return a Base bootstrapped with environmental configuration
84
+ #
85
+ # See #create.
86
+ #
87
+ # Uses the process ENV and the DEFAULT_PREFIX.
88
+ #
89
+ def self.create
90
+ new.create
91
+ end
92
+
93
+ private
94
+
95
+ def provider
96
+ provider_id = @env[:provider]
97
+ provider_config = @env.subslice(:provider)
98
+ provider_class = ConfigurationService::ProviderRegistry.instance.lookup(provider_id)
99
+ provider_class.new(provider_config) # TODO mandates keyword arguments or options hash
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,16 @@
1
+ Dir.glob(File.expand_path("../factory/**/*.rb", __FILE__), &method(:require))
2
+
3
+ module ConfigurationService
4
+
5
+ ##
6
+ # Module containing configuration service factory classes
7
+ #
8
+ # Current implementations provided by this package:
9
+ #
10
+ # * EnvironmentContext
11
+ #
12
+ module Factory
13
+
14
+ end
15
+
16
+ end
@@ -14,6 +14,8 @@ module ConfigurationService
14
14
  #
15
15
  # Used to validate the test framework architecture.
16
16
  #
17
+ # Registered into the ProviderRegistry with identifier "stub".
18
+ #
17
19
  class Stub
18
20
 
19
21
  ##
@@ -28,7 +30,11 @@ module ConfigurationService
28
30
  ##
29
31
  # Returns a new Stub
30
32
  #
31
- def initialize
33
+ # It requires an arbitrary +name+, used solely for testing factory
34
+ # methods.
35
+ #
36
+ def initialize(name:)
37
+ @name = name or raise ArgumentError, "missing required argument: name"
32
38
  @configurations = StubStore.instance
33
39
  end
34
40
 
@@ -79,3 +85,5 @@ module ConfigurationService
79
85
  end
80
86
 
81
87
  end
88
+
89
+ ConfigurationService::ProviderRegistry.instance.register("stub", ConfigurationService::Provider::Stub)
@@ -0,0 +1,50 @@
1
+ require 'singleton'
2
+
3
+ module ConfigurationService
4
+
5
+ unless defined?(ProviderRegistry)
6
+
7
+ ##
8
+ # A singleton registry of uniquely identified service provider classes
9
+ #
10
+ # Provider classes are registered and looked up by unique string identifier.
11
+ #
12
+ class ProviderRegistry
13
+
14
+ include Singleton
15
+
16
+ def initialize ## :nodoc:
17
+ @providers = {}
18
+ end
19
+
20
+ ##
21
+ # Register a +provider+ identified by the string +identifier+
22
+ #
23
+ # The +provider+ should be a class with a keyword argument
24
+ # constructor.
25
+ #
26
+ def register(identifier, provider)
27
+ @providers[identifier] = provider
28
+ end
29
+
30
+ ##
31
+ # Return the +provider+ identified by the string +identifier+
32
+ #
33
+ # The +provider+ must already have been registered with #register,
34
+ # and should be a class with a keyword argument constructor.
35
+ #
36
+ # Returns +nil+ if no provider has been registered with the given
37
+ # +identifier+.
38
+ #
39
+ def lookup(identifier)
40
+ @providers[identifier]
41
+ end
42
+
43
+ ##
44
+ # Return the singleton registry instance
45
+ # :singleton-method: instance
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -14,8 +14,14 @@ module ConfigurationService
14
14
  # implementation into ConfigurationService::Base, build your own test
15
15
  # orchestration provider from scratch, using Orchestrator as a guide.
16
16
  #
17
- # Extensions should implement #service_provider, #broken_service_provider,
18
- # #token_for and #delete_configuration.
17
+ # Extensions should implement:
18
+ #
19
+ # * #service_provider_id
20
+ # * #service_provider_configuration
21
+ # * #service_provider
22
+ # * #broken_service_provider
23
+ # * #token_for
24
+ # * #delete_configuration
19
25
  #
20
26
  class OrchestrationProvider
21
27
 
@@ -45,6 +51,26 @@ module ConfigurationService
45
51
  @identifier = "acme"
46
52
  end
47
53
 
54
+ ##
55
+ # Returns the string identifier of the service provider
56
+ #
57
+ # This should be the +identifier+ with which the service provider
58
+ # registers into the ProviderRegistry.
59
+ #
60
+ def service_provider_id
61
+ raise NotImplementedError, "#{self.class} must implement service_provider_id"
62
+ end
63
+
64
+ ##
65
+ # Return configuration for the service provider
66
+ #
67
+ # The configuration should be a dictionary of keyword arguments that can
68
+ # be passed to the constructor of the service provider class.
69
+ #
70
+ def service_provider_configuration # :doc:
71
+ raise NotImplementedError, "#{self.class} must implement service_provider_configuration"
72
+ end
73
+
48
74
  ##
49
75
  # See Orchestrator#authorize
50
76
  #
@@ -46,6 +46,7 @@ module ConfigurationService
46
46
 
47
47
  ##
48
48
  # Arrange a published configuration fixture
49
+ #
49
50
  def given_existing_configuration
50
51
  @provider.given_existing_configuration
51
52
  end
@@ -203,6 +204,63 @@ module ConfigurationService
203
204
  @provider.fail_next_request
204
205
  end
205
206
 
207
+ ##
208
+ # Arrange environmental service configuration
209
+ #
210
+ # Environmental service configuration is configuration for bootstrapping
211
+ # a configuration service and provider.
212
+ #
213
+ # TODO This method is imperative
214
+ #
215
+ def given_environmental_service_configuration
216
+ sp_env = @provider.service_provider_configuration.inject({}) do |m, (k, v)|
217
+ m["CFGSRV_PROVIDER_#{k.to_s.upcase}"] = v
218
+ m
219
+ end
220
+ @env = {
221
+ "CFGSRV_IDENTIFIER" => "acme",
222
+ "CFGSRV_TOKEN" => "ea81cbfb-221c-41ad-826e-d3eff6342345",
223
+ "CFGSRV_PROVIDER" => @provider.service_provider_id,
224
+ }.merge(sp_env)
225
+ end
226
+
227
+ ##
228
+ # Bootstrap a configuration service environmentally
229
+ #
230
+ # Environmental service configuration (as arranged by
231
+ # #given_environmental_service_configuration) is given to an
232
+ # EnvironmentContext factory to create a service configuration instance.
233
+ #
234
+ # TODO This method is imperative
235
+ #
236
+ def bootstrap_configuration_service_environmentally
237
+ factory = ConfigurationService::Factory::EnvironmentContext.new(@env, "CFGSRV")
238
+ @service = factory.create
239
+ end
240
+
241
+ ##
242
+ # Tests that a bootstrapped configuration service is functional
243
+ #
244
+ # TODO This method is imperative
245
+ #
246
+ def bootstrapped_configuration_service_functional?
247
+ response = begin
248
+ ConfigurationService::Test::Response::Success.new(@service.request_configuration)
249
+ rescue ConfigurationService::Error => e
250
+ ConfigurationService::Test::Response::Failure.new(e)
251
+ end
252
+ !response.failed?
253
+ end
254
+
255
+ ##
256
+ # Tests that environmental service configuration is scrubbed
257
+ #
258
+ # TODO This method is imperative
259
+ #
260
+ def environmental_service_configuration_scrubbed?
261
+ !@env.include?("CFGSRV_TOKEN")
262
+ end
263
+
206
264
  private
207
265
 
208
266
  def role_for(activity)
@@ -13,13 +13,24 @@ module ConfigurationService
13
13
  #
14
14
  class StubOrchestrationProvider < OrchestrationProvider
15
15
 
16
+ def service_provider_id
17
+ "stub"
18
+ end
19
+
20
+ ##
21
+ # Return configuration for a Stub
22
+ #
23
+ def service_provider_configuration # :doc:
24
+ {name: "Stub configuration service provider"}
25
+ end
26
+
16
27
  private
17
28
 
18
29
  ##
19
30
  # Returns a new Stub
20
31
  #
21
32
  def service_provider # :doc:
22
- ConfigurationService::Provider::Stub.new
33
+ ConfigurationService::Provider::Stub.new(service_provider_configuration)
23
34
  end
24
35
 
25
36
  ##
@@ -1,6 +1,8 @@
1
1
  require "configuration_service/base"
2
2
  require "configuration_service/configuration"
3
3
  require "configuration_service/errors"
4
+ require "configuration_service/factory"
5
+ require "configuration_service/provider_registry"
4
6
 
5
7
  ##
6
8
  # See ConfigurationService::Base.
@@ -10,7 +12,7 @@ module ConfigurationService
10
12
  ##
11
13
  # Creates a new ConfigurationService::Base for the +provider+
12
14
  #
13
- # See ConfigurationService::Base#initialize.
15
+ # See Base.new.
14
16
  #
15
17
  def self.new(identifier, token, provider)
16
18
  ConfigurationService::Base.new(identifier, token, provider)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configuration_service
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sheldon Hearn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-08-17 00:00:00.000000000 Z
11
+ date: 2015-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.3'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec-expectations
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -74,6 +88,7 @@ extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
76
90
  - ".gitignore"
91
+ - ".rspec"
77
92
  - Gemfile
78
93
  - Gemfile.lock
79
94
  - README.md
@@ -84,10 +99,14 @@ files:
84
99
  - lib/configuration_service/base.rb
85
100
  - lib/configuration_service/configuration.rb
86
101
  - lib/configuration_service/errors.rb
102
+ - lib/configuration_service/factory.rb
103
+ - lib/configuration_service/factory/environment_context.rb
104
+ - lib/configuration_service/factory/environment_context/env_dict.rb
87
105
  - lib/configuration_service/provider.rb
88
106
  - lib/configuration_service/provider/broken.rb
89
107
  - lib/configuration_service/provider/stub.rb
90
108
  - lib/configuration_service/provider/stub_store.rb
109
+ - lib/configuration_service/provider_registry.rb
91
110
  - lib/configuration_service/test.rb
92
111
  - lib/configuration_service/test/orchestration_provider.rb
93
112
  - lib/configuration_service/test/orchestration_provider_registry.rb