configuration_service 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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