cloud-config 0.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.
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ module Cache
5
+ # A simple class for storing key-value configuration in-memory. Keys can be individuallly set to
6
+ # expire.
7
+ #
8
+ # @example
9
+ # cache = CloudConfig::Cache::InMemory.new
10
+ # cache.set(:key_to_expire, :key_value, expire_in: 5)
11
+ #
12
+ # cache.get(:key_to_expire) #=> :key_value
13
+ #
14
+ # sleep 5
15
+ #
16
+ # cache.get(:key_to_expire) #=> nil
17
+ class InMemory
18
+ # Default time to expire keys (seconds)
19
+ DEFAULT_EXPIRE = 60 * 60
20
+
21
+ # @!attribute [r] settings
22
+ # @return [Hash]
23
+ attr_reader :settings
24
+
25
+ def initialize
26
+ @settings = {}
27
+ end
28
+
29
+ # Check whether the key exists in the cache. Expired keys will return false.
30
+ #
31
+ # @param key [String,Symbol] Key to check
32
+ #
33
+ # @return [Boolean] Whether key exists
34
+ def key?(key)
35
+ expire(key)
36
+
37
+ settings.key?(key)
38
+ end
39
+
40
+ # Fetch the key from the cache
41
+ #
42
+ # @param key [String,Symbol] Key to check
43
+ #
44
+ # @return [Object] Value of key
45
+ def get(key)
46
+ expire(key)
47
+
48
+ settings.dig(key, :value)
49
+ end
50
+
51
+ # Set the value of the key in the cache. Optionally set an expiry of the key, otherwise
52
+ # a default expiry of {DEFAULT_EXPIRE} will be set.
53
+ #
54
+ # @param key [String,Symbol] Key to set
55
+ # @param value [Object] Value of the key
56
+ # @option options [Hash] :expire_in Time in seconds until key expires
57
+ def set(key, value, options = {})
58
+ expire_in = options.fetch(:expire_in) { DEFAULT_EXPIRE }
59
+
60
+ expire_at = Time.now + expire_in
61
+
62
+ settings[key] = {
63
+ value:,
64
+ expire_at:
65
+ }
66
+ end
67
+
68
+ # Delete the key from the cache
69
+ #
70
+ # @param key [String,Symbol] Key to delete
71
+ def delete(key)
72
+ settings.delete(key)
73
+ end
74
+
75
+ # Expire the key from the cache, if the expiry time has passed
76
+ #
77
+ # @param key [String,Symbol] Key to expire
78
+ def expire(key)
79
+ expire_at = settings.dig(key, :expire_at)
80
+
81
+ delete(key) if expire_at && expire_at <= Time.now
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ module Cache
5
+ # A simple class for storing key-value data in Redis.
6
+ #
7
+ # @example
8
+ # cache = CloudConfig::Cache::Redis.new(url:)
9
+ # cache.set(:key_to_expire, :key_value, expire_in: 5)
10
+ #
11
+ # cache.get(:key_to_expire) #=> :key_value
12
+ #
13
+ # sleep 5
14
+ #
15
+ # cache.get(:key_to_expire) #=> nil
16
+ class Redis
17
+ # Default time to expire keys (seconds)
18
+ DEFAULT_EXPIRE = 60 * 60
19
+
20
+ # @!attribute [r] Redis client
21
+ # @return [Redis]
22
+ attr_reader :client
23
+
24
+ def initialize(params = {})
25
+ @client = ::Redis.new(params)
26
+ end
27
+
28
+ # Check whether the key exists in the cache. Expired keys will return false.
29
+ #
30
+ # @param key [String,Symbol] Key to check
31
+ #
32
+ # @return [Boolean] Whether key exists
33
+ def key?(key)
34
+ client.exists(key) == 1
35
+ end
36
+
37
+ # Fetch the key from the cache
38
+ #
39
+ # @param key [String,Symbol] Key to check
40
+ #
41
+ # @return [Object] Value of key
42
+ def get(key)
43
+ client.get(key)
44
+ end
45
+
46
+ # Set the value of the key in the cache. Optionally set an expiry of the key, otherwise
47
+ # a default expiry of {DEFAULT_EXPIRE} will be set.
48
+ #
49
+ # @param key [String,Symbol] Key to set
50
+ # @param value [Object] Value of the key
51
+ # @option options [Hash] :expire_in Time in seconds until key expires
52
+ def set(key, value, options = {})
53
+ client.set(key, value, ex: options[:expire_in] || DEFAULT_EXPIRE)
54
+ end
55
+
56
+ # Delete the key from the cache
57
+ #
58
+ # @param key [String,Symbol] Key to delete
59
+ def delete(key)
60
+ client.del(key)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ # A module for handling the caching of keys.
5
+ module Cache
6
+ # Extend {Cache::ClassMethods} when included
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ # Class methods for {Cache}
12
+ module ClassMethods
13
+ # @!attribute [r] Cache client instance
14
+ # @return [Object]
15
+ attr_reader :cache
16
+
17
+ # Configure the cache client
18
+ #
19
+ # @param client [Object] Cache client instance
20
+ def cache_client(client)
21
+ @cache = client
22
+ end
23
+
24
+ # Fetch the value of the key from the cache, if the key exists in the cache
25
+ #
26
+ # @param key [String,Symbol] Key to fetch from the cache
27
+ # @option options [Hash<Symbol,Object>] Cache options
28
+ #
29
+ # @yield Fetch the value of the key if the key does not exist in the cache
30
+ #
31
+ # @return [Object] Value of the key
32
+ def with_cache(key, options = {})
33
+ return cache.get(key) if cache&.key?(key)
34
+
35
+ value = yield
36
+
37
+ cache&.set(key, value, options)
38
+
39
+ value
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ # Base error class for {CloudConfig}
5
+ class Error < StandardError; end
6
+
7
+ # Raise MissingKey error when the key is not configured.
8
+ class MissingKey < Error; end
9
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'provider_options'
4
+
5
+ module CloudConfig
6
+ # A class for storing provider configuration. Use this class to create a new provider and set the provider
7
+ # parameters.
8
+ class ProviderConfig
9
+ attr_reader :settings, :provider_name, :provider_options
10
+
11
+ # Create a new instance of {ProviderConfig}.
12
+ #
13
+ # @param provider_name [String,Symbol] Name of the provider
14
+ # @param provider_options [Hash<Symbol,Object>] List of provider options
15
+ def initialize(provider_name, provider_options)
16
+ @provider_name = provider_name
17
+ @provider_options = ProviderOptions.new(provider_options)
18
+ @settings = {}
19
+ end
20
+
21
+ # Store the name of a key with this provider. Provider additional options such as caching.
22
+ #
23
+ # @param setting_name [String,Symbol] Setting key
24
+ # @option setting_options [Hash<Symbol,Object>] List of options for the key
25
+ def setting(setting_name, setting_options = {})
26
+ setting_options = merge_options(setting_options)
27
+ @settings[setting_name] = setting_options
28
+ end
29
+
30
+ # Return an instance of the configured provider.
31
+ #
32
+ # @return [Object] Instance of the provider
33
+ def provider
34
+ @provider ||= provider_class.new(provider_options.to_h)
35
+ end
36
+
37
+ # Return the class of the configured provider.
38
+ #
39
+ # @return [Object] Class of the provider
40
+ def provider_class
41
+ @provider_class ||= if provider_options.klass
42
+ generate_class(provider_options.klass)
43
+ else
44
+ provider_class_from_name(provider_name)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def generate_class(klass)
51
+ return klass unless klass.is_a?(String)
52
+
53
+ Object.const_get(klass)
54
+ end
55
+
56
+ def provider_class_from_name(class_name)
57
+ capitalised_class_name = class_name.to_s.split('_').map(&:capitalize).join
58
+
59
+ CloudConfig::Providers.const_get(capitalised_class_name)
60
+ end
61
+
62
+ def merge_options(options)
63
+ options ||= {}
64
+
65
+ provider_options.to_h.each do |key, value|
66
+ if options.key?(key)
67
+ options[key] = value.merge(options[key]) if options[key].is_a?(Hash) && value.is_a?(Hash)
68
+ else
69
+ options[key] = value
70
+ end
71
+ end
72
+
73
+ options
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ # A helper class for storing provider options.
5
+ class ProviderOptions
6
+ attr_reader :preload, :klass
7
+
8
+ # Create a new instance of {ProviderOptions}.
9
+ #
10
+ # @option param [Hash] :preload Enable preloading for the provider
11
+ # @option param [Hash] :class The class of the provider
12
+ def initialize(params = {})
13
+ @preload = params[:preload]
14
+ @klass = params[:class]
15
+ end
16
+
17
+ # Returns whether asynchronous preloading is enabled for the provider.
18
+ #
19
+ # @return [Boolean] Whether asynchronous preloading is enabled
20
+ def async_preload?
21
+ return false unless preload.is_a?(Hash)
22
+
23
+ preload[:async]
24
+ end
25
+
26
+ # Returns the class in the form of a hash
27
+ #
28
+ # @return [Hash<Symbol,Object>] Class parameters
29
+ def to_h
30
+ { preload:, class: klass }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ module Providers
5
+ # A class for fetching configuration from the AWS Parameter Store
6
+ #
7
+ # @example
8
+ # provider = AwsParameterStore.new # Assuming AWS credentials are already configured
9
+ # provider.set(:example_key, :example_value)
10
+ # provider.get(:example_key) #=> 'example_value'
11
+ class AwsParameterStore
12
+ # @!attribute [r] An instance of the AWS Parameter Store client
13
+ # @return [Aws::SSM::Client]
14
+ attr_reader :client
15
+
16
+ # Create a new instance of {AwsParameterStore}.
17
+ def initialize(_params = {})
18
+ @client = Aws::SSM::Client.new
19
+ end
20
+
21
+ # Fetch the value of the key
22
+ #
23
+ # @param key [String,Symbol] Key to fetch
24
+ #
25
+ # @return [String] Value of the key
26
+ def get(key)
27
+ client.get_parameter(name: key).parameter.value
28
+ end
29
+
30
+ # Set the value of the key
31
+ #
32
+ # @param key [String,Symbol] Key to set
33
+ # @param value [Object] Value of the key
34
+ def set(key, value)
35
+ client.put_parameter(name: key, value:)
36
+ end
37
+
38
+ # def client
39
+ # @client ||= Aws::SSM::Client.new
40
+ # end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ module Providers
5
+ # A class that acts as a provider for storing configuration in-memory
6
+ #
7
+ # @example
8
+ # provider = InMemory.new # Assuming AWS credentials are already configured
9
+ # provider.set(:example_key, :example_value)
10
+ # provider.get(:example_key) #=> 'example_value'
11
+ class InMemory
12
+ # @!attribute [r] settings
13
+ # @return [Hash]
14
+ attr_reader :settings
15
+
16
+ # Create an instance of {InMemory}
17
+ def initialize(_params = {})
18
+ @settings = {}
19
+ end
20
+
21
+ # Fetch the value of the key
22
+ #
23
+ # @param key [String,Symbol] Key to fetch
24
+ #
25
+ # @return [Object] Value of the key
26
+ def get(key)
27
+ settings[key]
28
+ end
29
+
30
+ # Set the value of the key
31
+ #
32
+ # @param key [String,Symbol] Key to set
33
+ # @param value [Object] Value of the key
34
+ def set(key, value)
35
+ @settings[key] = value
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ module Providers
5
+ # A class for fetching configuration from the AWS Parameter Store
6
+ #
7
+ # @example
8
+ # provider = YamlFile.new # Assuming AWS credentials are already configured
9
+ # provider.set(:example_key, :example_value)
10
+ # provider.get(:example_key) #=> 'example_value'
11
+ class YamlFile
12
+ # @!attribute [r] Contents of the Yaml file
13
+ # @return [Hash]
14
+ attr_reader :contents
15
+
16
+ # Create an instance of {YamlFile}
17
+ #
18
+ # @option params [Hash] :filename Name of the YAML file
19
+ def initialize(params = {})
20
+ @contents = YAML.load_file(params[:filename]) || {}
21
+ end
22
+
23
+ # Fetch the value of the key
24
+ #
25
+ # @param key [String,Symbol] Key to fetch
26
+ #
27
+ # @return [Object] Value of the key
28
+ def get(key)
29
+ contents[key]
30
+ end
31
+
32
+ # Set the value of the key
33
+ #
34
+ # @param key [String,Symbol] Key to set
35
+ # @param value [Object] Value of the key
36
+ def set(key, value)
37
+ @contents[key] = value
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ # A module for configuring providers
5
+ module Providers
6
+ # Extend {Providers::ClassMethods} when included
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ # Class methods for {Providers}
12
+ module ClassMethods
13
+ # @!attribute [r] Provider configurations
14
+ # @return [Hash<Symbol,ProviderConfig>]
15
+ attr_reader :providers
16
+
17
+ # Add a provider to the list of provider configurations.
18
+ #
19
+ # @param provider_name [Symbol] Name of the provider
20
+ # @param provider_options [Hash<Symbol,Object>] Options for configuring the provider
21
+ #
22
+ # @yield A block to be evaluated on an instance of {ProviderConfig}
23
+ def provider(provider_name, provider_options = {}, &blk)
24
+ provider_config = ProviderConfig.new(provider_name, provider_options)
25
+ provider_config.instance_eval(&blk) if blk
26
+ @providers ||= {}
27
+ @providers[provider_name] = provider_config
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloudConfig
4
+ # Version of CloudConfig
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parallel'
4
+
5
+ require_relative 'cloud-config/error'
6
+ require_relative 'cloud-config/version'
7
+ require_relative 'cloud-config/providers'
8
+ require_relative 'cloud-config/cache'
9
+ require_relative 'cloud-config/provider_config'
10
+
11
+ # CloudConfig handles fetching key-value configuration settings from multiple providers.
12
+ # Configure CloudConfig to handle providers and the configuration keys.
13
+ #
14
+ # @example
15
+ # CloudConfig.configure do
16
+ # provider :aws_parameter_store, preload: { async: true } do
17
+ # setting :db_url, cache: 60
18
+ # setting :api_url
19
+ # end
20
+ # end
21
+ #
22
+ module CloudConfig
23
+ include Providers
24
+ include Cache
25
+
26
+ module_function
27
+
28
+ # Configure {CloudConfig} with providers and their configuration keys
29
+ #
30
+ # @yield Evaluate itself.
31
+ def configure(&)
32
+ instance_eval(&)
33
+ end
34
+
35
+ # Fetch the value of a key using the appropriate provider.
36
+ #
37
+ # @param key [String,Symbol] Key to lookup
38
+ #
39
+ # @return [Object] Value of the key
40
+ def get(key)
41
+ provider_config = providers.values.find { |provider| provider.settings.key?(key) }
42
+
43
+ raise MissingKey, 'Key not found' if provider_config.nil?
44
+
45
+ load_key(provider_config, key)
46
+ end
47
+
48
+ # Set the value of a key with the configured provider.
49
+ #
50
+ # @param key [String,Symobl] Key to update
51
+ # @param value [Object] Value of key
52
+ def set(key, value)
53
+ provider_config = providers.values.find { |provider| provider.settings.key?(key) }
54
+
55
+ raise MissingKey, 'Key not found' if provider_config.nil?
56
+
57
+ provider_config.provider.set(key, value)
58
+
59
+ cache&.set(key, value, expire_in: provider_config.settings[key][:cache])
60
+ end
61
+
62
+ # Fetch all keys that are configured for preloading. This will automatically
63
+ # cache the corresponding keys.
64
+ def preload
65
+ return if cache.nil?
66
+
67
+ providers.each_value do |provider_config|
68
+ next unless provider_config.provider_options.preload
69
+
70
+ if provider_config.provider_options.async_preload?
71
+ Parallel.each(provider_config.settings.keys) do |key|
72
+ load_key(provider_config, key)
73
+ end
74
+ else
75
+ provider_config.settings.each_key do |key|
76
+ load_key(provider_config, key)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Fetch a key with the provider configuration. If caching is configured, the key
83
+ # will be fetched from the cache.
84
+ #
85
+ # @param provider_config [CloudConfig::ProviderConfig] provider configuration
86
+ # @param key [String,Symbol] Key to fetch
87
+ #
88
+ # @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
+ provider_config.provider.get(key)
92
+ end
93
+ end
94
+
95
+ # Reset the {CloudConfig} configuration
96
+ def reset!
97
+ @cache = nil
98
+ @provider = nil
99
+ end
100
+ end
@@ -0,0 +1,4 @@
1
+ module CloudConfig
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end