cloud-config 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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