cloud-config 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 103ce286b688715803e0542a5b739d4719ff0c002113368d2db8828dcbe07be5
4
- data.tar.gz: e400a9d26f3f3e291147699f3077d4fac31d5d9d9ce82712bbe3690d75607e84
3
+ metadata.gz: a5d99c6f38f07031a7da456fa285c345a87ed6f124a534d2b5fe004540743e85
4
+ data.tar.gz: cba8c49a6fcc40d3cfac3f451826a3fddaaf6a0d8a7271033fbc7e833dc47cbb
5
5
  SHA512:
6
- metadata.gz: 9317560df6ac2d68e26a189994f8090386c466ba39720874911ee79f6e8869b52ed6b2d0c703d7d9b66ec291e625306ad58a85f044b558baff540798a1229d83
7
- data.tar.gz: 6d634f9653b9df6d443524ab931ed0f79768db9db33a650e020ee43f9c15e32d13b94bbace9a2f3af231fcdf24bd26d690f1474420afde4f8d9f86ffaef77614
6
+ metadata.gz: b883d58ca786602fe85135a77a31959c00dcd7d3af7534e63889320fb7d1d739285bb048fa6f2c72fb309c8ecf9ed4bc95da513be39a9d073a14ec768c4cb7c7
7
+ data.tar.gz: 0d61a08ecbba70c9ae743cf8fa84c68772da1a6a3566716fb9f17ed564472150ef5c6dcbc38e0f6b9722437327feef6cac85c6aca02e1bcd2c49ab0812c003fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2023-01-15
3
+ ## [0.2.0] - 2023-05-20
4
+
5
+ Bug fixes:
6
+
7
+ - Fix in memory cache expiry
8
+
9
+ Enhancements:
10
+
11
+ - Reset cache for single key
12
+ - Lookup providers by configuration key
13
+
14
+ ## [Released]
15
+
16
+ ## [0.1.0] - 2023-05-17
4
17
 
5
18
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cloud-config (0.1.0)
4
+ cloud-config (0.2.0)
5
5
  parallel
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  A library to modernise the way applications fetch configuration. Typically an application will use environment variables and settings files to store and retrieve configurations, but there are many issues with this approach. Often environment variables create a security risk and settings files hard code infrastructure dependencies into the code. Changing configuration settings will usually require redeploying large parts of the infrastructure and perhaps even need to go through the application deployment lifecycle.
4
4
 
5
- A modern approach stores configuration remotely, often using key/value databases. Storing configurations in a single database, separately from the codebase reduces infrastructure dependency. Configuration updates can automatically sync with any application, without requiring redeployments. Security risk is greatly reduced, since the configurations can be securely stored. Another goal with this approach is creating an environmentless codebase. The application no longer needs to know which environment it's running in, since all the configuration is handled by the infrastucture.
5
+ A modern approach stores configuration remotely, often using key/value databases. Storing configurations in a single database, separately from the codebase reduces infrastructure dependency. Configuration updates can automatically sync with any application, without requiring redeployments. Security risk is greatly reduced, since configurations can be securely stored. Another goal with this approach is creating an environmentless codebase. The application no longer needs to know which environment it's running in, since all the configuration is handled by the infrastucture.
6
+
7
+ Another common problem is password rotation. A typical application will need to be restarted or even redeployed when the configuration settings change, such as rotating passwords. CloudConfig can handle this elegantly, improving the uptime and resilience of the application.
6
8
 
7
9
  ## Installation
8
10
 
@@ -16,7 +18,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
18
 
17
19
  ## Usage
18
20
 
19
- CloudConfig can be configured to fetch keys from multiple providers. Since CloudConfig will need to know which provider has the correct key, all the keys will need to be preconfigured. An example configuration may look like
21
+ CloudConfig can be configured to fetch keys from multiple providers. Since CloudConfig will need to know which provider has the correct key, all the keys will need to be preconfigured. Duplicate keys will be overriden with the latest provider. An example configuration may look like
20
22
 
21
23
  ```ruby
22
24
  CloudConfig.configure do
@@ -27,6 +29,18 @@ CloudConfig.configure do
27
29
  end
28
30
  ```
29
31
 
32
+ Fetch a setting with
33
+
34
+ ```ruby
35
+ url = CloudConfig.get(:api_url)
36
+ ```
37
+
38
+ If caching has been configured, the cache can be reset using
39
+
40
+ ```ruby
41
+ url = CloudConfig.get(:api_url, reset_cache: true)
42
+ ```
43
+
30
44
  ### Configuration Options
31
45
 
32
46
  ```ruby
@@ -66,7 +80,7 @@ end
66
80
 
67
81
  ### Preloading
68
82
 
69
- CloudConfig can preload all the keys configured for preload enabled providers. A cache client must be configured, otherwise
83
+ CloudConfig can preload all the keys configured for preload enabled providers. A cache client must also be configured, otherwise
70
84
  preloading won't do anything.
71
85
 
72
86
  ```ruby
@@ -74,6 +88,7 @@ CloudConfig.configure do
74
88
  cache_client CloudConfig::Cache::InMemory.new
75
89
 
76
90
  provider :in_memory, preload: true do
91
+ setting :key1
77
92
  end
78
93
  end
79
94
  ```
@@ -84,9 +99,77 @@ Call preload to cache all the keys.
84
99
  CloudConfig.preload
85
100
  ```
86
101
 
102
+ ### Custom Providers
103
+
104
+ CloudConfig comes equipped with a set of providers for fetching configuration from remote datastores. The limit set of providers will not be sufficient for most real world applications, so creating custom providers will be necessary. There is a very simple interface consisting of 3 methods necessary for creating a custom provider.
105
+
106
+ ```ruby
107
+ class CustomProvider
108
+ # Define initialize with a params argument.
109
+ def initialize(params = {}); end
110
+
111
+ # Define `get` for fetching keys from the remote datastore
112
+ def get(key); end
113
+
114
+ # Define `set` for storing keys in the remote datastore
115
+ def set(key, value); end
116
+ end
117
+ ```
118
+
119
+ Specify the class when configuring the custom provider.
120
+
121
+ ```ruby
122
+ CloudConfig.configure do
123
+ provider :custom_provider, provider_class: CustomProvider do
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Custom Cache
129
+
130
+ Defining a custom cache client will require an interface of 3 methods.
131
+
132
+ ```ruby
133
+ class CustomCache
134
+ # Define `key?` for checking whether the key exists in the cache client.
135
+ # This check allows nil values to be cached.
136
+ def key?(key); end
137
+
138
+ # Define `get` for fetching keys from the cache
139
+ def get(key); end
140
+
141
+ # Define `set` for storing keys in the cache
142
+ def set(key, value); end
143
+ end
144
+ ```
145
+
146
+ Configure CloudConfig to use the cache in the usual way
147
+
148
+ ```ruby
149
+ CloudConfig.configure do
150
+ cache_client CustomCache.new
151
+ end
152
+ ```
153
+
154
+ ## Connection Pooling
155
+
156
+ CloudConfig does not do any connection pooling. It is the responsibility of the application to handle connection pooling. For example, Rails handles its own connection pools, so CloudConfig will not attempt to interfere with the pool.
157
+
158
+ ## Examples
159
+
160
+ There are some example configurations in the [examples](examples) folder. In the terminal, execute
161
+
162
+ $ examples/001_parameter_store.rb
163
+
164
+ These examples make use of AWS infrastructure, so make sure the AWS enviroment variables have been correctly configured.
165
+
166
+ $ export AWS_ACCESS_KEY_ID=
167
+ $ export AWS_SECRET_ACCESS_KEY=
168
+ $ export AWS_REGION=
169
+
87
170
  ## Development
88
171
 
89
- It's recommended to use Docker when working with the library. Assuming Docker is installed, run
172
+ It's recommended to use Docker when working with this library. Assuming Docker is installed, run
90
173
 
91
174
  $ docker-compose run config /bin/bash
92
175
 
@@ -0,0 +1,45 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+
8
+ gem 'faraday'
9
+
10
+ gem 'cloud-config', path: '..'
11
+ end
12
+
13
+ require 'faraday'
14
+ require 'cloud-config/cache/in_memory'
15
+ require 'cloud-config/providers/in_memory'
16
+
17
+ CloudConfig.configure do
18
+ cache_client CloudConfig::Cache::InMemory.new
19
+
20
+ provider :in_memory, preload: true do
21
+ setting :url, cache: 60
22
+ end
23
+ end
24
+
25
+ CloudConfig.set(:url, 'https://httpstat.us/503')
26
+
27
+ url = CloudConfig.get(:url)
28
+
29
+ # Mimick a configuration change
30
+ CloudConfig.providers_by_key[:url].provider.set(:url, 'https://httpstat.us/200')
31
+
32
+ req = Faraday.new do |f|
33
+ f.response :raise_error
34
+ f.response :logger
35
+ end
36
+
37
+ begin
38
+ req.get(url)
39
+ rescue Faraday::ServerError
40
+ url = CloudConfig.get(:url, reset_cache: true)
41
+
42
+ sleep 1
43
+
44
+ retry
45
+ end
@@ -55,7 +55,7 @@ module CloudConfig
55
55
  # @param value [Object] Value of the key
56
56
  # @option options [Hash] :expire_in Time in seconds until key expires
57
57
  def set(key, value, options = {})
58
- expire_in = options.fetch(:expire_in) { DEFAULT_EXPIRE }
58
+ expire_in = options[:expire_in] || DEFAULT_EXPIRE
59
59
 
60
60
  expire_at = Time.now + expire_in
61
61
 
@@ -30,7 +30,7 @@ module CloudConfig
30
30
  #
31
31
  # @return [Object] Value of the key
32
32
  def with_cache(key, options = {})
33
- return cache.get(key) if cache&.key?(key)
33
+ return cache.get(key) if !options[:reset_cache] && cache&.key?(key)
34
34
 
35
35
  value = yield
36
36
 
@@ -34,10 +34,6 @@ module CloudConfig
34
34
  def set(key, value)
35
35
  client.put_parameter(name: key, value:)
36
36
  end
37
-
38
- # def client
39
- # @client ||= Aws::SSM::Client.new
40
- # end
41
37
  end
42
38
  end
43
39
  end
@@ -14,6 +14,10 @@ module CloudConfig
14
14
  # @return [Hash<Symbol,ProviderConfig>]
15
15
  attr_reader :providers
16
16
 
17
+ # @!attribute [r] Fetch provider configuration by setting key
18
+ # @return [Hash<Symbol,ProviderConfig>]
19
+ attr_reader :providers_by_key
20
+
17
21
  # Add a provider to the list of provider configurations.
18
22
  #
19
23
  # @param provider_name [Symbol] Name of the provider
@@ -25,6 +29,18 @@ module CloudConfig
25
29
  provider_config.instance_eval(&blk) if blk
26
30
  @providers ||= {}
27
31
  @providers[provider_name] = provider_config
32
+
33
+ update_provider_keys(provider_config)
34
+ end
35
+
36
+ # Update {#providers_by_key} with settings from the provider config.
37
+ #
38
+ # @param provider_config [ProviderConfig] Provider config containing the keys
39
+ def update_provider_keys(provider_config)
40
+ @providers_by_key ||= {}
41
+ provider_config.settings.each_key do |key|
42
+ @providers_by_key[key] = provider_config
43
+ end
28
44
  end
29
45
  end
30
46
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module CloudConfig
4
4
  # Version of CloudConfig
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
data/lib/cloud-config.rb CHANGED
@@ -35,14 +35,15 @@ module CloudConfig
35
35
  # Fetch the value of a key using the appropriate provider.
36
36
  #
37
37
  # @param key [String,Symbol] Key to lookup
38
+ # @param reset_cache [Boolean] Whether the cache for the key should be reset
38
39
  #
39
40
  # @return [Object] Value of the key
40
- def get(key)
41
- provider_config = providers.values.find { |provider| provider.settings.key?(key) }
41
+ def get(key, reset_cache: false)
42
+ provider_config = providers_by_key[key]
42
43
 
43
44
  raise MissingKey, 'Key not found' if provider_config.nil?
44
45
 
45
- load_key(provider_config, key)
46
+ load_key(provider_config, key, reset_cache:)
46
47
  end
47
48
 
48
49
  # Set the value of a key with the configured provider.
@@ -50,7 +51,7 @@ module CloudConfig
50
51
  # @param key [String,Symobl] Key to update
51
52
  # @param value [Object] Value of key
52
53
  def set(key, value)
53
- provider_config = providers.values.find { |provider| provider.settings.key?(key) }
54
+ provider_config = providers_by_key[key]
54
55
 
55
56
  raise MissingKey, 'Key not found' if provider_config.nil?
56
57
 
@@ -84,10 +85,11 @@ module CloudConfig
84
85
  #
85
86
  # @param provider_config [CloudConfig::ProviderConfig] provider configuration
86
87
  # @param key [String,Symbol] Key to fetch
88
+ # @param reset_cache [Boolean] Whether the cache for the key should be reset
87
89
  #
88
90
  # @return [Object] Value of the key
89
- def load_key(provider_config, key)
90
- with_cache(key, expire_in: provider_config.settings[key][:cache]) do
91
+ def load_key(provider_config, key, reset_cache: false)
92
+ with_cache(key, reset_cache:, expire_in: provider_config.settings[key][:cache]) do
91
93
  provider_config.provider.get(key)
92
94
  end
93
95
  end
@@ -95,6 +97,9 @@ module CloudConfig
95
97
  # Reset the {CloudConfig} configuration
96
98
  def reset!
97
99
  @cache = nil
98
- @provider = nil
100
+ @providers = {}
101
+ @providers_by_key = {}
99
102
  end
100
103
  end
104
+
105
+ CloudConfig.reset!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud-config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hypernova2002
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-17 00:00:00.000000000 Z
11
+ date: 2023-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel
@@ -230,6 +230,7 @@ files:
230
230
  - examples/001_parameter_store.rb
231
231
  - examples/002_parameter_store_with_cache.rb
232
232
  - examples/003_parameter_store_with_preload.rb
233
+ - examples/004_handling_configuration_changes.rb
233
234
  - lib/cloud-config.rb
234
235
  - lib/cloud-config/cache.rb
235
236
  - lib/cloud-config/cache/in_memory.rb