configuration_service 1.0.0 → 1.1.1

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