configuration_service 4.0.1 → 4.1.0

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: 3d499d3ed34dc61754cba18d38999edc665154e7
4
- data.tar.gz: dcba5f9be3aeede67f66fc14b404a9b2c4278be5
3
+ metadata.gz: 0f54605e38bbf4f4ae31e0261649f41afd4f2da2
4
+ data.tar.gz: 73a3f5fe6583fee49d3303d66cdc3d0850d99bc9
5
5
  SHA512:
6
- metadata.gz: 6c389d39ee89f21675296c6aff819a46650116f71ed64144cfa5c36cd79a707daa1bfa57ca6f8e4a64b1f6e20c71214185e72ddc036e1b1562225c4a7ee8a428
7
- data.tar.gz: e8969036d9967cb4e85cd933c15f2b54b0881fa4715541d583bacedfd53974da1be6e0bc0c756b0f399d00e193fc4fcaf43c88ee6d4b8261144c50de10d24b28
6
+ metadata.gz: 79f30829184142b8209c860a13d7929ac251e0b053e3e79be0b11ccef1bb5c48843a14f7d905c1711b5cd9bb5ae704f98a662a7b829ab74b7386a8b5ca10d245
7
+ data.tar.gz: 8d7525a0e583c09c9812f4023cb6952f8e0722a685fa78116bf7420c1cc84865b2109e65fb44fcb36dc2cdd3bc37f7ba6e1e98abc8e57609a86f366578608ab2
data/README.rdoc CHANGED
@@ -61,17 +61,18 @@ We could swap and/or reconfigure the provider by manipulating only the +Gemfile+
61
61
  The factory in the example above uses the process environment and (on JRuby) system properties as its context
62
62
  because {ConfigurationService::Factory.create_client} defaults to {ConfigurationService::Factory::EnvironmentContext}.
63
63
  To better understand the factory context, consider the example of a simple configuration service frontend
64
- that needs an {ConfigurationService::AdminClient AdminClient} for administrative access to the service:
64
+ that needs a multi-identifier (admin) client for administrative access to the service:
65
65
 
66
66
  # Bad example (see below for discussion)
67
67
  context = ConfigurationService::Factory::Context.new(
68
68
  "token" => "c3935418-f621-40de-ada3-cc8169f1348a",
69
+ # Note: no identifier provided
69
70
  "provider_id" => "vault",
70
71
  "provider_config" => {
71
72
  "address" => "http://127.0.0.1:8200"
72
73
  }
73
74
  )
74
- admin_client = ConfigurationService::Factory.create_admin_client(context)
75
+ admin_client = ConfigurationService::Factory.create_client(context)
75
76
 
76
77
  app = Rack::Builder.new do
77
78
  map "/configurations" do
@@ -88,12 +89,12 @@ In practice, the administrative context would most likely come from the configur
88
89
  In the following example, the {ConfigurationService::Factory} defaults to using the
89
90
  {ConfigurationService::Factory::EnvironmentContext EnvironmentContext} to acquire application configuration
90
91
  from a non-administrative {ConfigurationService::Client} client.
91
- Part of the application configuration is then used as the context for an administrative {ConfigurationService::AdminClient},
92
+ Part of the application configuration is then used as the context for a multi-identifier admin client,
92
93
  which is used as a model in the application.
93
94
 
94
95
  configuration = ConfigurationService::Factory.create_client.request_configuration
95
96
  app_name = configuration.data["app_name"]
96
- admin_client = ConfigurationService::Factory.create_admin_client(configuration.data["cs_config"])
97
+ admin_client = ConfigurationService::Factory.create_client(configuration.data["cs_config"])
97
98
 
98
99
  app = Rack::Builder.new do
99
100
  map "/" do
@@ -111,7 +112,7 @@ The above code would rely on configuration data that looked something like this:
111
112
  app_name: ACME config service UI
112
113
  cs_config:
113
114
  token: c3935418-f621-40de-ada3-cc8169f1348a
114
- interface: admin
115
+ # Note: no identifier provided
115
116
  provider_id: vault
116
117
  provider_config:
117
118
  address: http://127.0.0.1:8200
@@ -127,7 +128,32 @@ If this configuration data was stored under the configuration identifier "acme",
127
128
 
128
129
  So this application would use the token "0b2...4af" to access its configuration data
129
130
  under the identifier "acme"
130
- with a non-administrative {ConfigurationService::Client} client.
131
+ with a non-administrative {ConfigurationService::Client}.
131
132
  Part of its configuration data, in this case under the key "cs_config",
132
- provides {ConfigurationService::Factory::Context} for creating an administrative {ConfigurationService::AdminClient},
133
+ provides {ConfigurationService::Factory::Context} for creating a multi-identifier admin client,
133
134
  which it would then use the token "c39...48a" to operate over multiple configuration identifiers on behalf of users.
135
+
136
+ === Decorators
137
+
138
+ The service supports decoration of the provider to extend functionality. A {ConfigurationService::Decorator} is
139
+ a class that take a service provider (or another decorator) as its only constructor argument, and provides the
140
+ same methods as a service provider.
141
+
142
+ This allows the service provider to be composed into a chain of multiple decorators to extend the functionality
143
+ of the service.
144
+
145
+ Decorator authors should be sure to register their decorators into the
146
+ {ConfigurationService::DecoratorRegistry}.
147
+ The gem comes with a sample decorator, {ConfigurationService::Decorator::ReferenceResolver},
148
+ although this is likely to be moved into a separate gem in a later release.
149
+
150
+ The easiest way to compose decorators into the service is with the {ConfigurationService::Factory}.
151
+
152
+ Here is sample environment that indicates to the factory that it should compose the reference resolver into the
153
+ service it creates:
154
+
155
+ CFGSRV_IDENTIFIER="acme"
156
+ CFGSRV_TOKEN="0b2a80f4-54ce-45f4-8267-f6558fee64af"
157
+ CFGSRV_DECORATORS="reference_resolver"
158
+ CFGSRV_PROVIDER="vault"
159
+ CFGSRV_PROVIDER_ADDRESS="http://127.0.0.1:8200"
@@ -29,7 +29,14 @@ Feature: Authorization
29
29
  Given the specified configuration exists
30
30
  And I am allowed to authorize consumption of configuration
31
31
  When I authorize consumption of the configuration
32
- Then I receive credentials that only allow consumption of the configuration
32
+ Then I receive credentials that allow consumption of the configuration
33
+
34
+ Scenario: Allowed to create credentials for consuming referenced configurations
35
+ Given existing configuration data containing references
36
+ And the referenced configuration exists
37
+ And I am allowed to authorize consumption of configuration
38
+ When I authorize consumption of the configuration
39
+ Then I receive credentials that allow consumption of the referenced configurations
33
40
 
34
41
  Scenario: Token-less request
35
42
  Given I do not have a token
@@ -11,4 +11,3 @@ Feature: Bootstrapping
11
11
  When I bootstrap the configuration service from the environment
12
12
  Then I receive a functioning configuration service
13
13
  And the environmental service configuration has been scrubbed
14
-
@@ -5,11 +5,19 @@ Feature: Consuming configuration data
5
5
  I want to request access to configuration data
6
6
 
7
7
  Scenario: Metadata-free hit
8
- Given I am allowed to request configuration
9
- And the specified configuration exists
8
+ Given the specified configuration exists
9
+ And I am allowed to request configuration
10
10
  When I request configuration data
11
11
  Then the specified configuration should be returned
12
12
 
13
+ Scenario: Resolved configuration
14
+ Given existing configuration data containing references
15
+ And the referenced configuration exists
16
+ And I am allowed to request configuration
17
+ When I request configuration data
18
+ Then the specified configuration should be returned
19
+ And the references should be replaced with the referenced configuration data
20
+
13
21
  Scenario: Metadata-free miss
14
22
  Given I am allowed to request configuration
15
23
  And the specified configuration does not exist
@@ -35,7 +43,8 @@ Feature: Consuming configuration data
35
43
  Then I should receive a 'no match' indication
36
44
 
37
45
  Scenario: CS request failure
38
- Given I am allowed to request configuration
46
+ Given the specified configuration exists
47
+ And I am allowed to request configuration
39
48
  And a CS request failure
40
49
  When I request configuration data
41
50
  Then I should receive a 'request failure' notification
@@ -2,6 +2,15 @@ Given(/^I do not have a token$/) do
2
2
  @test.deauthorize
3
3
  end
4
4
 
5
+ Given(/^existing configuration data containing references$/) do
6
+ @test.given_existing_configuration_containing_references
7
+ end
8
+
9
+ Given(/^the referenced configuration exists$/) do
10
+ @test.given_referenced_configuration_exists
11
+ end
12
+
13
+
5
14
  Given(/^I am allowed to request configuration$/) do
6
15
  @test.authorize(:requesting_configurations)
7
16
  end
@@ -46,7 +55,13 @@ When(/^I authorize consumption of the configuration$/) do
46
55
  @test.authorize_consumption
47
56
  end
48
57
 
49
- Then(/^I receive credentials that only allow consumption of the configuration$/) do
58
+ Then(/^I receive credentials that allow consumption of the configuration$/) do
50
59
  expect(@test.credentials_allow_consumption?).to eq true
51
60
  expect(@test.credentials_allow_publication?).to eq false
52
61
  end
62
+
63
+ Then(/^I receive credentials that allow consumption of the referenced configurations$/) do
64
+ expect(@test.credentials_allow_reference_consumption?).to eq true
65
+ expect(@test.credentials_allow_reference_publication?).to eq false
66
+ end
67
+
@@ -10,6 +10,10 @@ Then(/^the specified configuration should be returned$/) do
10
10
  expect(@test.configuration_found_for_identifier?).to eq true
11
11
  end
12
12
 
13
+ Then(/^the references should be replaced with the referenced configuration data$/) do
14
+ expect(@test.references_replaced_with_configuration_data?).to eq true
15
+ end
16
+
13
17
  Given(/^no meta data filter$/) do
14
18
  end
15
19
 
@@ -1,6 +1,7 @@
1
1
  require "configuration_service/admin_client"
2
2
  require "configuration_service/base"
3
3
  require "configuration_service/configuration"
4
+ require "configuration_service/decorator/reference_resolver"
4
5
  require "configuration_service/errors"
5
6
  require "configuration_service/factory"
6
7
  require "configuration_service/provider_registry"
@@ -1,6 +1,14 @@
1
1
  require "configuration_service/client"
2
2
 
3
3
  module ConfigurationService
4
+
5
+ ##
6
+ # The multi-identifier administrative configuration service API
7
+ #
8
+ # See {file:README.rdoc} for a summary of the service, including links to user stories.
9
+ #
10
+ # It is recommended that consumers use a {ConfigurationService::Factory} to create and configure the service.
11
+ #
4
12
  class AdminClient
5
13
 
6
14
  ##
@@ -1,6 +1,14 @@
1
1
  require "configuration_service/utils"
2
2
 
3
3
  module ConfigurationService
4
+
5
+ ##
6
+ # The configuration service API
7
+ #
8
+ # See {file:README.rdoc} for a summary of the service, including links to user stories.
9
+ #
10
+ # It is recommended that consumers use a {ConfigurationService::Factory} to create and configure the service.
11
+ #
4
12
  class Client
5
13
 
6
14
  ##
@@ -0,0 +1,155 @@
1
+ require 'configuration_service/configuration'
2
+ require 'configuration_service/decorator_registry'
3
+ require 'delegate'
4
+
5
+ module ConfigurationService
6
+
7
+ ##
8
+ # The configuration service supports decoration of the provider to extend functionality.
9
+ #
10
+ # Decorators are classes that take a service provider (or another decorator) as their only constructor argument,
11
+ # and provide the same methods as a provider.
12
+ #
13
+ # This allows the service provider to be composed into a chain of multiple decorators to extend the functionality
14
+ # of the service.
15
+ #
16
+ module Decorator
17
+
18
+ ##
19
+ # Resolves configuration identifier references in configuration data
20
+ #
21
+ # This configuration service provider decorator affects the #request_configuration and #authorize_consumption provider methods
22
+ # to support configuration identifier references in configuration data.
23
+ #
24
+ # Configuration identifier references are strings of the form
25
+ #
26
+ # %{ configuration_idenfier }
27
+ #
28
+ # The referenced configuration identifier must exist when #request_configuration or #authorize_consumption encounter it.
29
+ #
30
+ # @example reference resolution
31
+ #
32
+ # # Published configuration identifiers:
33
+ # #
34
+ # # acme:
35
+ # # ---
36
+ # # credentials:
37
+ # # username: acme_user
38
+ # # password: secret123
39
+ # # auditing: %{ shared_auditing }
40
+ # #
41
+ # # shared_auditing:
42
+ # # ---
43
+ # # log_level: info
44
+ # # log_destination: syslog4http
45
+ # # log_format: unstructured
46
+ # # log_username: acme_user
47
+ # # log_password: acme123
48
+ #
49
+ # context = ConfigurationService::FactoryContext.new(
50
+ # identifier: "acme",
51
+ # token: ENV["CFGSRV_TOKEN"],
52
+ # decorators: "reference_resolver",
53
+ # #...
54
+ # )
55
+ # client = ConfigurationService::Factory.create_client(context)
56
+ # config = client.request_configuration
57
+ # config["auditing"]
58
+ #
59
+ # # =>
60
+ # # {
61
+ # # "credentials" => {
62
+ # # "username" => "acme_user",
63
+ # # "password" => "secret123"
64
+ # # },
65
+ # # "auditing" => {
66
+ # # "log_level" => "info",
67
+ # # "log_destination" => "syslog4http",
68
+ # # # ...
69
+ # # }
70
+ # # }
71
+ #
72
+ class ReferenceResolver < SimpleDelegator
73
+
74
+ ##
75
+ # The regular expression used to match configuration identifier references
76
+ #
77
+ PATTERN = /%{\s*([^\s]+)\s*}/
78
+
79
+ ##
80
+ # Creates a new reference resolver decorator
81
+ #
82
+ # @param [Object] provider
83
+ # a configuration service provider or configuration service decorator
84
+ #
85
+ def initialize(provider)
86
+ @provider = provider
87
+ super
88
+ end
89
+
90
+ ##
91
+ # Resolve configuration identifier references in configuration data
92
+ #
93
+ # The String values of configuration data returned by the provider are searched for configuration identifier references.
94
+ #
95
+ # Each reference is replaced with the configuration data identified by the referenced identifier. References are
96
+ # detected at any depth of a nested configuration dictionary, but reference resolving is not recursive.
97
+ #
98
+ # @see ConfigurationService::Client#request_configuration
99
+ #
100
+ def request_configuration(identifier, credentials)
101
+ configuration = @provider.request_configuration(identifier, credentials)
102
+ resolved_configuration_data = resolve_configuration_data(configuration.data, credentials)
103
+ ConfigurationService::Configuration.new(configuration.identifier, resolved_configuration_data, configuration.metadata)
104
+ end
105
+
106
+ ##
107
+ # Authorize consumption of consumption of configuration including referenced identifiers
108
+ #
109
+ # The String values of the configuration data identified by +identifier+ are searched for configuration identifier references.
110
+ #
111
+ # The returned credentials provide access not only to the given +identifier+ but also to all identifiers for which references
112
+ # were found in the search of the data.
113
+ #
114
+ # @see ConfigurationService::Client#authorize_configuration
115
+ #
116
+ def authorize_consumption(identifier, credentials)
117
+ configuration = @provider.request_configuration(identifier, credentials)
118
+ identifiers = ReferenceResolver.find_configuration_identifiers(configuration.data)
119
+ identifiers.unshift(identifier)
120
+ @provider.authorize_consumption(identifiers, credentials)
121
+ end
122
+
123
+ private
124
+
125
+ ##
126
+ # @param [Hash] configuration_data
127
+ # the data dictionary whose String values to search for configuration identifier references
128
+ # @return [Array] list of referenced configuration identifiers found
129
+ ##
130
+ def self.find_configuration_identifiers(configuration_data)
131
+ configuration_data.values.select { |value|
132
+ value.is_a? String
133
+ }.map { |value|
134
+ match = value.match(PATTERN)
135
+ match[1] if not match.nil?
136
+ }.compact
137
+ end
138
+
139
+ def resolve_configuration_data(configuration_data, credentials)
140
+ configuration_data.each { |key, value|
141
+ next if not value.is_a? String
142
+ match = value.match(PATTERN)
143
+ if not match.nil?
144
+ configuration_data[key] = @provider.request_configuration(match[1], credentials).data
145
+ end
146
+ }
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+
153
+ end
154
+
155
+ ConfigurationService::DecoratorRegistry.instance.register("reference_resolver", ConfigurationService::Decorator::ReferenceResolver)
@@ -0,0 +1,55 @@
1
+ require 'singleton'
2
+
3
+ module ConfigurationService
4
+
5
+ unless defined?(DecoratorRegistry)
6
+
7
+ ##
8
+ # A singleton registry of configuration service decorators
9
+ #
10
+ # @!method self.instance
11
+ # The singleton registry instance
12
+ #
13
+ # @return [ConfigurationService::DecoratorRegistry] singleton instance
14
+ #
15
+ class DecoratorRegistry
16
+
17
+ include Singleton
18
+
19
+ ##
20
+ # Register a configuration service decorator
21
+ #
22
+ # @param [String] identifier
23
+ # unique identifier for the configuration service decorator
24
+ # @param [Class] decorator
25
+ # the configuration service decorator class
26
+ #
27
+ def register(identifier, decorator)
28
+ @decorators[identifier] = decorator
29
+ end
30
+
31
+ ##
32
+ # Look up a configuration service decorator
33
+ #
34
+ # @param [String] identifier
35
+ # the unique identifier for the configuration service decorator.
36
+ # The decorator must already have been registered with {#register}.
37
+ #
38
+ # @return [Class] the configuration service decorator class
39
+ # @return [nil] if no decorator has been registered with the given +identifier+
40
+ #
41
+ def lookup(identifier)
42
+ raise ConfigurationService::DecoratorNotFoundError, "Decorator not found in registry" if not @decorators.key?(identifier)
43
+ @decorators[identifier]
44
+ end
45
+
46
+ # @private
47
+ def initialize
48
+ @decorators = {}
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -25,4 +25,10 @@ module ConfigurationService
25
25
  class ConfigurationNotFoundError < Error
26
26
  end
27
27
 
28
+ ##
29
+ # Bootstrapped with decorator, but decorator not found
30
+ ##
31
+ class DecoratorNotFoundError < Error
32
+ end
33
+
28
34
  end
@@ -1,6 +1,8 @@
1
1
  require "configuration_service/factory/context"
2
2
  require "configuration_service/factory/environment_context"
3
3
  require "configuration_service/factory/yaml_file_context"
4
+ require "configuration_service/decorator_registry"
5
+ require "configuration_service/errors"
4
6
 
5
7
  module ConfigurationService
6
8
 
@@ -65,7 +67,8 @@ module ConfigurationService
65
67
  #
66
68
  def self.create_client(context = EnvironmentContext.new)
67
69
  context = Context.new(context) if context.is_a?(Hash)
68
- provider = create_provider(context)
70
+ decorators = get_decorators(context.decorators)
71
+ provider = decorators.any? ? decorate(decorators << create_provider(context)) : create_provider(context)
69
72
  identifier = context.identifier? ? context.identifier : nil
70
73
  ConfigurationService::Client.new(credentials: context.token, provider: provider, identifier: identifier)
71
74
  ensure
@@ -82,6 +85,18 @@ module ConfigurationService
82
85
 
83
86
  private
84
87
 
88
+ def self.get_decorators(decorators)
89
+ [decorators].flatten.map { |decorator|
90
+ ConfigurationService::DecoratorRegistry.instance.lookup(decorator)
91
+ }
92
+ end
93
+
94
+ def self.decorate(chain)
95
+ chain.reverse.inject do |acc, decorator|
96
+ decorator.new(acc)
97
+ end
98
+ end
99
+
85
100
  def self.create_provider(context)
86
101
  provider_id = context.provider_id
87
102
  provider_config = context.provider_config
@@ -30,6 +30,8 @@ module ConfigurationService
30
30
  # (see {ConfigurationService::ProviderRegistry})
31
31
  # [provider_config] configuration options for the service provider
32
32
  # (see service provider documentation)
33
+ # [decorators] list of decorators to compose into the service
34
+ # (see {ConfigurationService::Decorator} and #{ConfigurationService::DecoratorRegistry})
33
35
  #
34
36
  def initialize(source)
35
37
  @env = SymbolicAccessWrapper.new(source)
@@ -75,6 +77,16 @@ module ConfigurationService
75
77
  @env[:provider_config]
76
78
  end
77
79
 
80
+ ##
81
+ # List of unique identities of decorators to compose into the service
82
+ #
83
+ # @see ConfigurationService::Decorator
84
+ # @see ConfigurationService::DecoratorRegistry
85
+ #
86
+ def decorators
87
+ @env.include?(:decorators) ? @env[:decorators] : []
88
+ end
89
+
78
90
  ##
79
91
  # Deletes the internal copy of the +source+
80
92
  #
@@ -8,10 +8,11 @@ module ConfigurationService
8
8
 
9
9
  def initialize(hash)
10
10
  hash.each do |k, v|
11
- if v.is_a?(Hash)
12
- self.store(k.intern, self.class.new(v))
13
- else
14
- self.store(k.intern, v)
11
+ case v
12
+ when Hash
13
+ self.store(k.intern, self.class.new(v))
14
+ else
15
+ self.store(k.intern, v)
15
16
  end
16
17
  end
17
18
  self.default_proc = indifferent_access_default_proc
@@ -19,21 +20,22 @@ module ConfigurationService
19
20
 
20
21
  private
21
22
 
22
- def indifferent_access_default_proc
23
- proc do |_, key|
24
- try_string(key) or raise_key_error(key)
25
- end
23
+ def indifferent_access_default_proc
24
+ proc do |_, key|
25
+ try_string(key) or raise_key_error(key)
26
26
  end
27
+ end
27
28
 
28
- def try_string(k)
29
- if (skey = k.intern rescue false) and self.include?(skey)
30
- self.fetch(skey)
31
- end
29
+ def try_string(k)
30
+ if (skey = k.intern rescue false) and self.include?(skey)
31
+ self.fetch(skey)
32
32
  end
33
+ end
33
34
 
34
- def raise_key_error(k)
35
- raise KeyError, "missing key #{k}"
36
- end
35
+ def raise_key_error(k)
36
+ raise ConfigurationService::AuthorizationError, "missing key #{k}" if k === :token
37
+ raise KeyError, "missing key #{k}"
38
+ end
37
39
 
38
40
  end
39
41
 
@@ -46,6 +46,8 @@ module ConfigurationService
46
46
  # (see {ConfigurationService::ProviderRegistry})
47
47
  # [PROVIDER_*] configuration options for the service provider
48
48
  # (see service provider documentation)
49
+ # [DECORATORS] list of decorators to compose into the service
50
+ # (see {ConfigurationService::Decorator} and #{ConfigurationService::DecoratorRegistry})
49
51
  #
50
52
  # If sources are not specified, the process +ENV+ and (on JRuby) system properties are used.
51
53
  # Where a system property and environment variable of the same name exist,
@@ -61,11 +63,15 @@ module ConfigurationService
61
63
  {
62
64
  to_envvar("IDENTIFIER") => :identifier,
63
65
  to_envvar("TOKEN") => :token,
64
- to_envvar("PROVIDER") => :provider_id
65
- }.each do |envvar, key|
66
+ to_envvar("PROVIDER") => :provider_id,
67
+ to_envvar("DECORATORS") => :decorators
68
+ }.select { |envvar, key|
69
+ merged_sources.include?(envvar)
70
+ }.each { |envvar, key|
66
71
  env[key] = merged_sources[envvar]
67
72
  @scrub_keys << envvar
68
- end
73
+ }
74
+ env[:decorators] = env[:decorators].split(/[,\s]+/) if env.include?(:decorators)
69
75
  config_prefix = to_envvar("PROVIDER_")
70
76
  env[:provider_config] = merged_sources
71
77
  .select { |envvar| envvar.start_with?(config_prefix) && @scrub_keys << envvar }
@@ -1,3 +1,5 @@
1
+ require 'configuration_service'
2
+
1
3
  module ConfigurationService
2
4
 
3
5
  module Provider
@@ -9,6 +11,9 @@ module ConfigurationService
9
11
  #
10
12
  class Broken
11
13
 
14
+ def initialize(options = {})
15
+ end
16
+
12
17
  ##
13
18
  # @raise [ConfigurationService::Error] always
14
19
  #
@@ -28,3 +33,5 @@ module ConfigurationService
28
33
  end
29
34
 
30
35
  end
36
+
37
+ ConfigurationService::ProviderRegistry.instance.register("broken", ConfigurationService::Provider::Broken)
@@ -75,7 +75,7 @@ module ConfigurationService
75
75
  #
76
76
  def publish_configuration(configuration, credentials)
77
77
  authorize_request(:publisher, credentials)
78
- @configurations.store(configuration.identifier, configuration.data, configuration.metadata)
78
+ @configurations.store(configuration.identifier, configuration.data.dup, configuration.metadata)
79
79
  configuration
80
80
  end
81
81
 
@@ -1,5 +1,6 @@
1
1
  require 'cucumber/errors'
2
2
  require 'configuration_service'
3
+ require 'configuration_service/decorator/reference_resolver'
3
4
 
4
5
  module ConfigurationService
5
6
 
@@ -38,7 +39,8 @@ module ConfigurationService
38
39
  # The provider is always initialized with the same configuration +identifier+
39
40
  #
40
41
  def initialize
41
- @identifier = "acme"
42
+ @identifier = 'acme'
43
+ @configuration = { "verbose" => true }
42
44
  end
43
45
 
44
46
  ##
@@ -107,15 +109,15 @@ module ConfigurationService
107
109
  #
108
110
  # @return [String] a credentials
109
111
  #
110
- def credentials_for(role)
112
+ def credentials_for(role, identifier = @identifier)
111
113
  raise NotImplementedError, "#{self.class} must implement credentials_for(role)"
112
114
  end
113
115
 
114
116
  ##
115
117
  # @see ConfigurationService::Test::Orchestrator#authorize
116
118
  #
117
- def authorize(role)
118
- @credentials = credentials_for(role)
119
+ def authorize(role, identifier = @identifier)
120
+ @credentials = credentials_for(role, identifier)
119
121
  end
120
122
 
121
123
  ##
@@ -136,11 +138,33 @@ module ConfigurationService
136
138
  # @see ConfigurationService::Test::Orchestrator#given_existing_configuration
137
139
  #
138
140
  def given_existing_configuration
139
- authorized_as(:publisher) do
140
- @existing_configuration = publish_configuration
141
+ authorized_as(:publisher, @identifier) do
142
+ @existing_configuration = publish_configuration(@identifier, @configuration)
141
143
  end
142
144
  end
143
145
 
146
+ def given_existing_configuration_containing_references
147
+ @references = ["ref1", "ref2", "ref3"]
148
+ @unresolved_configuration_data = @references.each_with_object({}) do |reference, configuration_data|
149
+ configuration_data[reference] = "%{#{reference}}"
150
+ end
151
+ authorized_as(:publisher, @identifier) do
152
+ @existing_configuration = publish_configuration(@identifier, @unresolved_configuration_data)
153
+ end
154
+ end
155
+
156
+ def given_referenced_configuration_exists
157
+ @references.each { |ref|
158
+ identifier = ref
159
+ configuration = {
160
+ "#{ref}key" => "#{ref}value"
161
+ }
162
+ authorized_as(:publisher, identifier) do
163
+ publish_configuration(identifier, configuration)
164
+ end
165
+ }
166
+ end
167
+
144
168
  ##
145
169
  # @see ConfigurationService::Test::Orchestrator#given_invalid_configuration
146
170
  #
@@ -152,7 +176,7 @@ module ConfigurationService
152
176
  # @see ConfigurationService::Test::Orchestrator#given_missing_configuration
153
177
  #
154
178
  def given_missing_configuration
155
- authorized_as(:publisher) do
179
+ authorized_as(:publisher, @identifier) do
156
180
  delete_configuration
157
181
  @existing_configuration = nil
158
182
  end
@@ -177,9 +201,9 @@ module ConfigurationService
177
201
  #
178
202
  # @see ConfigurationService::Client#request_configuration
179
203
  #
180
- def request_configuration
204
+ def request_configuration(identifier = @identifier)
181
205
  wrap_response do
182
- @requested_configuration = service.request_configuration(identifier: @identifier)
206
+ @requested_configuration = service.request_configuration(identifier: identifier)
183
207
  end
184
208
  end
185
209
 
@@ -200,6 +224,24 @@ module ConfigurationService
200
224
  publish_configuration
201
225
  end
202
226
 
227
+ ##
228
+ # @return [Array] of responses
229
+ ##
230
+ def credentials_allow_reference_consumption?
231
+ @references.map { |ref|
232
+ request_configuration(ref)
233
+ }
234
+ end
235
+
236
+ ##
237
+ # @return [Array] of responses
238
+ ##
239
+ def credentials_allow_reference_publication?
240
+ @references.map { |ref|
241
+ publish_configuration(ref)
242
+ }
243
+ end
244
+
203
245
  ##
204
246
  # Publish configuration
205
247
  #
@@ -212,12 +254,12 @@ module ConfigurationService
212
254
  #
213
255
  # @see ConfigurationService::Client#publish_configuration
214
256
  #
215
- def publish_configuration
257
+ def publish_configuration(identifier = @identifier, configuration = @configuration)
216
258
  wrap_response do
217
259
  if @metadata
218
- service.publish_configuration(identifier: @identifier, data: configuration, metadata: @metadata)
260
+ service.publish_configuration(identifier: identifier, data: configuration, metadata: @metadata)
219
261
  else
220
- service.publish_configuration(identifier: @identifier, data: configuration)
262
+ service.publish_configuration(identifier: identifier, data: configuration)
221
263
  end
222
264
  end
223
265
  end
@@ -233,6 +275,20 @@ module ConfigurationService
233
275
  @requested_configuration.identifier == @identifier
234
276
  end
235
277
 
278
+ def references_replaced_with_configuration_data?
279
+ requested_configuration = @requested_configuration.data
280
+ resolved_configuration = {}
281
+ @unresolved_configuration_data.each { |key, value|
282
+ next if not value.is_a? String
283
+ match = value.match(ConfigurationService::Decorator::ReferenceResolver::PATTERN)
284
+ if not match.nil?
285
+ identifier = match[1]
286
+ resolved_configuration[key] = service.request_configuration(identifier: identifier).data
287
+ end
288
+ }
289
+ return requested_configuration === resolved_configuration
290
+ end
291
+
236
292
  ##
237
293
  # Arrange for the next publish or request operation to fail
238
294
  #
@@ -259,25 +315,25 @@ module ConfigurationService
259
315
 
260
316
  private
261
317
 
262
- def configuration
263
- @configuration ||= {"verbose" => true}
264
- end
265
-
266
318
  def service
267
-
268
- provider = service_provider
319
+ context = {
320
+ "token" => @credentials,
321
+ "provider_id" => service_provider_id,
322
+ "provider_config" => service_provider_configuration,
323
+ }
269
324
 
270
325
  if @fail_next
271
326
  @fail_next = false
272
- provider = broken_service_provider
327
+ context["provider_id"] = 'broken'
273
328
  end
274
-
275
- ConfigurationService::Client.new(credentials: @credentials, provider: provider)
329
+
330
+ context["decorators"] = ['reference_resolver'] if @references
331
+ ConfigurationService::Factory.create_client(context)
276
332
  end
277
333
 
278
- def authorized_as(role)
334
+ def authorized_as(role, identifier)
279
335
  restore_credentials = @credentials
280
- authorize(role)
336
+ authorize(role, identifier)
281
337
  yield.tap do
282
338
  @credentials = restore_credentials
283
339
  end
@@ -51,6 +51,14 @@ module ConfigurationService
51
51
  @provider.given_existing_configuration
52
52
  end
53
53
 
54
+ def given_existing_configuration_containing_references
55
+ @provider.given_existing_configuration_containing_references
56
+ end
57
+
58
+ def given_referenced_configuration_exists
59
+ @provider.given_referenced_configuration_exists
60
+ end
61
+
54
62
  ##
55
63
  # Use invalid configuration data in the next publishing operation
56
64
  #
@@ -141,6 +149,16 @@ module ConfigurationService
141
149
  request_allowed?
142
150
  end
143
151
 
152
+ def credentials_allow_reference_consumption?
153
+ @responses = @provider.credentials_allow_reference_consumption?
154
+ requests_allowed?
155
+ end
156
+
157
+ def credentials_allow_reference_publication?
158
+ @responses = @provider.credentials_allow_reference_publication?
159
+ requests_allowed?
160
+ end
161
+
144
162
  ##
145
163
  # Configuration for the requested identifier was found
146
164
  #
@@ -150,6 +168,10 @@ module ConfigurationService
150
168
  @provider.configuration_found_for_identifier?
151
169
  end
152
170
 
171
+ def references_replaced_with_configuration_data?
172
+ @provider.references_replaced_with_configuration_data?
173
+ end
174
+
153
175
  ##
154
176
  # Whether the last publish or request was allowed
155
177
  #
@@ -160,6 +182,12 @@ module ConfigurationService
160
182
  @response.allowed?
161
183
  end
162
184
 
185
+ def requests_allowed?
186
+ @responses.all? { |response|
187
+ response.allowed?
188
+ }
189
+ end
190
+
163
191
  ##
164
192
  # Whether the last publish or request was allowed but failed
165
193
  #
@@ -57,7 +57,7 @@ module ConfigurationService
57
57
  #
58
58
  # @see ConfigurationService::Test::OrchestrationProvider#credentials_for
59
59
  #
60
- def credentials_for(role)
60
+ def credentials_for(role, identifier)
61
61
  ConfigurationService::Provider::Stub::BUILTIN_TOKENS[role]
62
62
  end
63
63
 
@@ -1,5 +1,5 @@
1
1
  module ConfigurationService
2
2
 
3
- VERSION = "4.0.1"
3
+ VERSION = "4.1.0"
4
4
 
5
5
  end
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: 4.0.1
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sheldon Hearn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-08 00:00:00.000000000 Z
11
+ date: 2016-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -113,6 +113,8 @@ files:
113
113
  - lib/configuration_service/base.rb
114
114
  - lib/configuration_service/client.rb
115
115
  - lib/configuration_service/configuration.rb
116
+ - lib/configuration_service/decorator/reference_resolver.rb
117
+ - lib/configuration_service/decorator_registry.rb
116
118
  - lib/configuration_service/errors.rb
117
119
  - lib/configuration_service/factory.rb
118
120
  - lib/configuration_service/factory/context.rb