configuration_service 4.0.1 → 4.1.0
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 +4 -4
- data/README.rdoc +33 -7
- data/features/authorization.feature +8 -1
- data/features/bootstrapping.feature +0 -1
- data/features/consuming.feature +12 -3
- data/features/step_definitions/authorization_steps.rb +16 -1
- data/features/step_definitions/consuming_steps.rb +4 -0
- data/lib/configuration_service.rb +1 -0
- data/lib/configuration_service/admin_client.rb +8 -0
- data/lib/configuration_service/client.rb +8 -0
- data/lib/configuration_service/decorator/reference_resolver.rb +155 -0
- data/lib/configuration_service/decorator_registry.rb +55 -0
- data/lib/configuration_service/errors.rb +6 -0
- data/lib/configuration_service/factory.rb +16 -1
- data/lib/configuration_service/factory/context.rb +12 -0
- data/lib/configuration_service/factory/context/symbolic_access_wrapper.rb +17 -15
- data/lib/configuration_service/factory/environment_context.rb +9 -3
- data/lib/configuration_service/provider/broken.rb +7 -0
- data/lib/configuration_service/provider/stub.rb +1 -1
- data/lib/configuration_service/test/orchestration_provider.rb +79 -23
- data/lib/configuration_service/test/orchestrator.rb +28 -0
- data/lib/configuration_service/test/stub_orchestration_provider.rb +1 -1
- data/lib/configuration_service/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f54605e38bbf4f4ae31e0261649f41afd4f2da2
|
4
|
+
data.tar.gz: 73a3f5fe6583fee49d3303d66cdc3d0850d99bc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
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
|
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.
|
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
|
-
|
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}
|
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
|
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
|
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
|
data/features/consuming.feature
CHANGED
@@ -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
|
9
|
-
And
|
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
|
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
|
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
|
@@ -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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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:
|
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:
|
260
|
+
service.publish_configuration(identifier: identifier, data: configuration, metadata: @metadata)
|
219
261
|
else
|
220
|
-
service.publish_configuration(identifier:
|
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
|
-
|
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
|
-
|
327
|
+
context["provider_id"] = 'broken'
|
273
328
|
end
|
274
|
-
|
275
|
-
|
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
|
#
|
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
|
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-
|
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
|