configuration_service 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8c1d020e9ee176a21a78ca58785734d98ebda535
4
+ data.tar.gz: 5401e260c0d0a9da6ca31029b847b49fdaa8e6c7
5
+ SHA512:
6
+ metadata.gz: 842390201eb9dc49e9fc37c7a9f9913a6f08ea28fc710000ae220245d53f7503f27a9b2df3446cc74b54acc68492b889159b7485d2282fdf0a42ca22e738262f
7
+ data.tar.gz: cc8bdbf31cbaed9751562d69295a521ac0c089c65c3da176fb2b74a521d7e08221c58ad61954c4d00a33f10a7682b855259ed80fb1a2f63613404d6c1db5312b
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalisation:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ # Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in multicuke-blue.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ configuration_service (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ builder (3.2.2)
10
+ cucumber (2.0.2)
11
+ builder (>= 2.1.2)
12
+ cucumber-core (~> 1.2.0)
13
+ diff-lcs (>= 1.1.3)
14
+ gherkin (~> 2.12)
15
+ multi_json (>= 1.7.5, < 2.0)
16
+ multi_test (>= 0.1.2)
17
+ cucumber-core (1.2.0)
18
+ gherkin (~> 2.12.0)
19
+ diff-lcs (1.2.5)
20
+ gherkin (2.12.2)
21
+ multi_json (~> 1.3)
22
+ multi_json (1.11.2)
23
+ multi_test (0.1.2)
24
+ rake (10.4.2)
25
+ rspec-expectations (3.3.1)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.3.0)
28
+ rspec-support (3.3.0)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.10)
35
+ configuration_service!
36
+ cucumber (~> 2.0)
37
+ rake (~> 10.0)
38
+ rspec-expectations (~> 3.3)
39
+
40
+ BUNDLED WITH
41
+ 1.10.3
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Configuration service
2
+
3
+ The configuration service provides authorized publication and consumption of identified configuration data with metadata.
4
+
5
+ ## Documentation
6
+
7
+ Comprehensive rdoc documentation is available:
8
+
9
+ ```shell
10
+ git clone git@github.com:hetznerZA/configuration_service.git
11
+ cd configuration_service
12
+ bundle install
13
+ bundle exec rake doc
14
+ xdg-open html/index.html
15
+ ```
16
+
17
+ OSX users may need to use `open` instead of `xdg-open`.
18
+
19
+ ## Testing
20
+
21
+ Test as follows:
22
+
23
+ ```shell
24
+ git clone git@github.com:hetznerZA/configuration_service.git
25
+ cd configuration_service
26
+ bundle install
27
+ bundle exec rake test
28
+ ```
29
+
30
+ Note that the tests are applied to a stub service provider. This just tests the test framework architecture.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = Configuration service
2
+
3
+ The configuration service provides
4
+ {authorized}[link:features/authorization_feature.html]
5
+ {publication}[link:features/publishing_feature.html] and
6
+ {consumption}[link:features/consuming_feature.html]
7
+ of identified configuration data with metadata.
8
+
9
+ A pluggable Ruby API is provided in ConfigurationService::Base.
10
+
11
+ The declarative specification of the service is implemented with
12
+ cucumber, using a pluggable imperative orchestration providers. This
13
+ allows for implementations that are not providers to the Ruby API.
14
+ See ConfigurationService::Test.
15
+
16
+ A stub service provider is provided in
17
+ ConfigurationService::Provider::Stub.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rdoc/task'
3
+
4
+ desc "Test the stub test orchestrator"
5
+ task :test do
6
+ sh %{TEST_ORCHESTRATION_PROVIDER=stub bundle exec cucumber}
7
+ end
8
+
9
+ RDoc::Task.new do |rdoc|
10
+ rdoc.title = "Configuration service"
11
+ rdoc.main = "README.rdoc"
12
+ rdoc.rdoc_dir = "rdoc"
13
+ end
14
+
15
+ desc "Regenerate documentation"
16
+ task :doc => :rerdoc
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "configuration_service"
7
+ spec.version = "0.0.1"
8
+ spec.authors = ["Sheldon Hearn"]
9
+ spec.email = ["sheldonh@starjuice.net"]
10
+
11
+ spec.summary = %q{Configuration service}
12
+ spec.description = %q{Configuration service}
13
+ spec.homepage = "https://github.com/hetznerZA/configuration_service"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.10"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "cucumber", "~> 2.0"
23
+ spec.add_development_dependency "rspec-expectations", "~> 3.3"
24
+ end
@@ -0,0 +1,82 @@
1
+ module ConfigurationService
2
+
3
+ ##
4
+ # The configuration service API
5
+ #
6
+ # The service provides
7
+ # {authorized}[link:features/authorization_feature.html]
8
+ # {publication}[link:features/publishing_feature.html] and
9
+ # {consumption}[link:features/consuming_feature.html]
10
+ # of identified configuration data with metadata.
11
+ #
12
+ class Base
13
+
14
+ ##
15
+ # Creates a new API instance for the +provider+
16
+ #
17
+ def initialize(provider)
18
+ @provider = provider
19
+ end
20
+
21
+ ##
22
+ # Requests configuration data and metadata
23
+ #
24
+ # Delegates the request to the provider. An optional +metadata_filter+
25
+ # may be supplied to restrict access to, for example, a specific revision
26
+ # of the configuration.
27
+ #
28
+ # Returns a Configuration object or raises an Error on failure.
29
+ # Returns +nil+ if no matching configuration was found.
30
+ #
31
+ def request_configuration(metadata_filter = {})
32
+ @provider.request_configuration(metadata_filter)
33
+ end
34
+
35
+ ##
36
+ # Publishes configuration +data+ and optional +metadata+
37
+ #
38
+ # Delegates the publication to the provider. The +data+ and the +metadata+ (if specified)
39
+ # must be dictionaries (responding to #to_hash or #to_h). The provider receives a
40
+ # copy of the +metadata+ decorated with the following keys:
41
+ #
42
+ # * "timestamp" - the current UTC time in ISO8601 format
43
+ # * "revision" - a UUID for this publication
44
+ #
45
+ # Returns a Configuration object or raises an Error on failure. When a Configuration object
46
+ # is returned, its +metadata+ is the decorated copy, allowing access to additional metadata
47
+ # added by the API or the provider.
48
+ #
49
+ def publish_configuration(data, metadata = {})
50
+ dictionary?(data) or raise ConfigurationService::Error, "data must be a dictionary"
51
+ dictionary?(metadata) or raise ConfigurationService::Error, "metadata must be a dictionary"
52
+
53
+ @provider.publish_configuration(data, decorate(metadata))
54
+ end
55
+
56
+ ##
57
+ # True if the +metadata+ matches the +metadata_filter+
58
+ #
59
+ # The +metadata+ matches the +metadata_filter+ if every key in the +metadata_filter+
60
+ # is present in the +metadata+, and the values of both keys are equal (==).
61
+ #
62
+ # This is a utility method intended for use by providers.
63
+ #
64
+ def self.metadata_matches_filter?(metadata, metadata_filter)
65
+ metadata_filter.all? { |k, v| v == metadata[k] }
66
+ end
67
+
68
+ private
69
+
70
+ def dictionary?(o)
71
+ o.respond_to?(:to_hash) or o.respond_to?(:to_h)
72
+ end
73
+
74
+ def decorate(metadata)
75
+ revision = SecureRandom.uuid
76
+ metadata = metadata.merge("revision" => revision) unless metadata["revision"]
77
+ metadata = metadata.merge("timestamp" => Time.now.utc.iso8601) unless metadata["timestamp"]
78
+ end
79
+
80
+ end
81
+
82
+ end
@@ -0,0 +1,29 @@
1
+ module ConfigurationService
2
+
3
+ ##
4
+ # Encapsulates configuration +data+ and its +metadata+
5
+ #
6
+ # * +data+ is a dictionary of (possibly sensitive) configuration data,
7
+ # which providers are expected to secure.
8
+ # * +metadata+ is a dictionary of data about the configuration data,
9
+ # which providers are not expected to secure.
10
+ #
11
+ # It is recommended that both dictionaries use strings as keys, and that
12
+ # values be limited to those that can be serialized to JSON.
13
+ #
14
+ class Configuration
15
+
16
+ attr_reader :data, :metadata, :revision # :nodoc:
17
+
18
+ ##
19
+ # Returns a new Configuration
20
+ #
21
+ def initialize(data, metadata)
22
+ @data = data
23
+ @metadata = metadata
24
+ @revision = metadata["revision"]
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,18 @@
1
+ module ConfigurationService
2
+
3
+ ##
4
+ # The base of the configuration service exception tree
5
+ #
6
+ # * Error
7
+ # * AuthorizationError
8
+ #
9
+ class Error < StandardError
10
+ end
11
+
12
+ ##
13
+ # A configuration service authorization Error
14
+ #
15
+ class AuthorizationError < Error
16
+ end
17
+
18
+ end
@@ -0,0 +1,30 @@
1
+ module ConfigurationService
2
+
3
+ module Provider
4
+
5
+ ##
6
+ # A stub broken ConfigurationService::Base service provider
7
+ #
8
+ # Used to validate the test framework architecture.
9
+ #
10
+ class Broken
11
+
12
+ ##
13
+ # Raises Error
14
+ #
15
+ def publish_configuration(data, metadata)
16
+ raise ConfigurationService::Error, "error requested by test"
17
+ end
18
+
19
+ ##
20
+ # Raises Error
21
+ #
22
+ def request_configuration(metadata_filter = {})
23
+ raise ConfigurationService::Error, "error requested by test"
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,96 @@
1
+ require "time"
2
+ require "securerandom"
3
+
4
+ require "configuration_service"
5
+
6
+ require_relative "broken"
7
+ require_relative "stub_store"
8
+
9
+ module ConfigurationService
10
+
11
+ module Provider
12
+
13
+ ##
14
+ # A stub ConfigurationService::Base service provider
15
+ #
16
+ # Used to validate the test framework architecture.
17
+ #
18
+ class Stub
19
+
20
+ ##
21
+ # Maps roles to authorization tokens
22
+ #
23
+ BUILTIN_TOKENS = {
24
+ :consumer => '64867ebd-6364-0bd3-3fda-81-requestor',
25
+ :publisher => 'f53606cb-7f3c-4432-afe8-44-publisher',
26
+ :nothing => '2972abd7-b055-4841-8ad1-4a34-nothing',
27
+ } unless defined?(BUILTIN_TOKENS)
28
+
29
+ ##
30
+ # Returns a new Stub
31
+ #
32
+ # The object is initialized with a configuration +identifier+ and an
33
+ # authorization +token+ and holds a reference to the singleton StubStore.
34
+ #
35
+ def initialize(identifier, token)
36
+ @identifier = identifier
37
+ @token = token
38
+ @configurations = StubStore.instance
39
+ end
40
+
41
+ ##
42
+ # Requests configuration data and metadata
43
+ #
44
+ # See Base#request_configuration.
45
+ #
46
+ # The configured +identifier+ is used to fetch the data from the StubStore.
47
+ #
48
+ # An optional +metadata_filter+ may be supplied to restrict access to, for
49
+ # example, a specific revision of the configuration.
50
+ #
51
+ # Returns a Configuration object or raises AuthorizationError if the
52
+ # configured +token+ is not for the +:consumer+ role.
53
+ # Returns +nil+ if no matching configuration was found.
54
+ #
55
+ def request_configuration(metadata_filter)
56
+ authorize_request(:consumer)
57
+ data, metadata = @configurations.fetch(@identifier)
58
+ if Base.metadata_matches_filter?(metadata, metadata_filter)
59
+ Configuration.new(data, metadata)
60
+ end
61
+ rescue KeyError
62
+ nil
63
+ end
64
+
65
+ ##
66
+ # Publishes configuration +data+ and optional +metadata+
67
+ #
68
+ # See Base#publish_configuration.
69
+ #
70
+ # The +data+ and the +metadata+ (if specified) must be dictionaries
71
+ # (responding to #to_hash or #to_h). The provider receives a copy of the
72
+ # +metadata+ decorated with the following keys:
73
+ #
74
+ # * "timestamp" - the current UTC time in ISO8601 format
75
+ # * "revision" - a UUID for this publication
76
+ #
77
+ # Returns a Configuration object or raises an Error on failure.
78
+ #
79
+ def publish_configuration(data, metadata)
80
+ authorize_request(:publisher)
81
+ @configurations.store(@identifier, data, metadata)
82
+ Configuration.new(data, metadata)
83
+ end
84
+
85
+ private
86
+
87
+ def authorize_request(role)
88
+ raise AuthorizationError.new("missing token") unless @token
89
+ raise AuthorizationError.new("authorization failed") unless @token == BUILTIN_TOKENS[role]
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,58 @@
1
+ require "singleton"
2
+
3
+ unless defined?(ConfigurationService::Provider::StubStore)
4
+
5
+ module ConfigurationService
6
+
7
+ module Provider
8
+
9
+ ##
10
+ # A singleton store for the Stub service provider
11
+ #
12
+ class StubStore
13
+
14
+ include Singleton
15
+
16
+ def initialize # :nodoc:
17
+ @configurations = {}
18
+ end
19
+
20
+ ##
21
+ # Returns the configuration data and metadata for +identifier+
22
+ #
23
+ # The dictionaries are returned as an array, to be used as follows:
24
+ #
25
+ # data, metadata = StubStore.instance.fetch("acme")
26
+ #
27
+ def fetch(identifier)
28
+ @configurations.fetch(identifier)
29
+ end
30
+
31
+ ##
32
+ # Stores the +data+ and +metadata associated with the +identifier+
33
+ #
34
+ def store(identifier, data, metadata)
35
+ @configurations.store(identifier, [data, metadata])
36
+ end
37
+
38
+ ##
39
+ # Deletes the data and metadata associated with the +identifier+
40
+ #
41
+ # It is not an error to delete a non-existent +identifier+
42
+ #
43
+ def delete(identifier)
44
+ @configurations.delete(identifier)
45
+ nil
46
+ end
47
+
48
+ ##
49
+ # Return the singleton store instance
50
+ # :singleton-method: instance
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,13 @@
1
+ module ConfigurationService
2
+
3
+ ##
4
+ # A configuration service provider is an imperative implementation
5
+ # of the ConfigurationService API, to be plugged into a
6
+ # ConfigurationService::Base.
7
+ #
8
+ # A Stub implementation is provided to validate the test framework architecture.
9
+ #
10
+ module Provider
11
+ end
12
+
13
+ end