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