container_config 0.1.0

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
+ SHA256:
3
+ metadata.gz: a335fbf082edd704a0fad5393a1211ad20b0533611f688a174112533469c43e9
4
+ data.tar.gz: 71cde1c1a6a7f539ff6d9359ea98b594d35a39d19d06148e32c0a2ad1fff25a9
5
+ SHA512:
6
+ metadata.gz: bbb9e521366c1f740a76d9d473c2eb95a6c9e9a5be2c09856523ec63112a03e6c509cd1820bd7886522de4286bc276b7b9508a4f6120225b2c7082945e37c09c
7
+ data.tar.gz: ebd3318980dfe673eb1b4c526f7b5ffb9b7741709376a323a7d01ca521a7f9fbfb93f6b253213cfa1bf75372fed65d13a528a430aa4dbe6b3d14784e380d5802
@@ -0,0 +1,35 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.7.2
14
+ - name: Run unit tests and code linting
15
+ run: |
16
+ gem install bundler -v 2.2.9
17
+ bundle install
18
+ bundle exec rake
19
+ bundle exec yard
20
+
21
+ - name: Deploy documentation
22
+ if: github.ref == 'refs/heads/main'
23
+ uses: crazy-max/ghaction-github-pages@v2.2.0
24
+ with:
25
+ target_branch: gh-pages
26
+ build_dir: doc
27
+ env:
28
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
29
+
30
+ - name: Release Gem
31
+ if: contains(github.ref, 'refs/tags/v')
32
+ uses: dawidd6/action-publish-gem@v1.2.0
33
+ with:
34
+ github_token: ${{secrets.GITHUB_TOKEN}}
35
+ api_key: ${{secrets.RUBYGEMS_API_KEY}}
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+ NewCops: enable
4
+
5
+ Style/StringLiterals:
6
+ Enabled: true
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ Enabled: true
11
+ EnforcedStyle: double_quotes
12
+
13
+ Layout/LineLength:
14
+ Max: 150
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - spec/**/*.rb
19
+
20
+ Metrics/AbcSize:
21
+ Max: 25
22
+
23
+ Metrics/MethodLength:
24
+ Max: 20
25
+
26
+ Lint/BooleanSymbol:
27
+ Exclude:
28
+ - spec/container_config/coercer/symbol_spec.rb
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-03-05
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in container_config.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ container_config (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ diff-lcs (1.4.4)
11
+ parallel (1.20.1)
12
+ parser (3.0.0.0)
13
+ ast (~> 2.4.1)
14
+ rainbow (3.0.0)
15
+ rake (13.0.3)
16
+ regexp_parser (2.1.1)
17
+ rexml (3.2.4)
18
+ rspec (3.10.0)
19
+ rspec-core (~> 3.10.0)
20
+ rspec-expectations (~> 3.10.0)
21
+ rspec-mocks (~> 3.10.0)
22
+ rspec-core (3.10.1)
23
+ rspec-support (~> 3.10.0)
24
+ rspec-expectations (3.10.1)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.10.0)
27
+ rspec-mocks (3.10.2)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.10.0)
30
+ rspec-support (3.10.2)
31
+ rubocop (1.11.0)
32
+ parallel (~> 1.10)
33
+ parser (>= 3.0.0.0)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ regexp_parser (>= 1.8, < 3.0)
36
+ rexml
37
+ rubocop-ast (>= 1.2.0, < 2.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (>= 1.4.0, < 3.0)
40
+ rubocop-ast (1.4.1)
41
+ parser (>= 2.7.1.5)
42
+ rubocop-rake (0.5.1)
43
+ rubocop
44
+ rubocop-rspec (2.2.0)
45
+ rubocop (~> 1.0)
46
+ rubocop-ast (>= 1.1.0)
47
+ ruby-progressbar (1.11.0)
48
+ unicode-display_width (2.0.0)
49
+ yard (0.9.26)
50
+
51
+ PLATFORMS
52
+ x86_64-linux
53
+
54
+ DEPENDENCIES
55
+ container_config!
56
+ rake (~> 13.0)
57
+ rspec (~> 3.0)
58
+ rubocop (~> 1.7)
59
+ rubocop-rake (~> 0.5)
60
+ rubocop-rspec (~> 2.2)
61
+ yard (~> 0.9)
62
+
63
+ BUNDLED WITH
64
+ 2.2.9
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Matthew Newell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Container Config
2
+
3
+ `ContainerConfig` loads values from environment variables, secrets, application credentials, and any other desired value sources within Ruby applications. Rails is not required, but this gem will integrate with Rails if it is available.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application"s Gemfile:
8
+
9
+ ```ruby
10
+ gem "container_config"
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install container_config
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ # Retrieve the value of the POSTGRES_USER environment variable, secret mount, or Rails credential
25
+ ConfigLoader.load("POSTGRES_USER")
26
+
27
+ # Retrieve the value of the POSTGRES_PORT environment variable, secret mount, or Rails credential as an integer with a default value of 5432
28
+ ConfigLoader.load("POSTGRES_PORT", type: :integer, default: 5432)
29
+
30
+ # Retrieve the value of the POSTGRES_PASSWORD environment variable, secret mount, or Rails credential and raise an exception if it cannot be found
31
+ ConfigLoader.load("POSTGRES_PASSWORD", required: true)
32
+ ```
33
+
34
+ Full documentation is available in the [ContainerConfig GitHub Pages](https://wheatevo.github.io/container_config/).
35
+
36
+ ### Extending ContainerConfig
37
+
38
+ `ContainerConfig` may be extended by adding more value providers to gather configuration data or by adding more type coercers to coerce retrieved data into desired types.
39
+
40
+ #### Value Providers
41
+
42
+ More value providers may be added by creating a new class that inherits from `ContainerConfig::Provider::Base` and implementing the `name` and `load` methods. The `name` method should return a `String` name for your value provider and the `load` method should receive a `String` `key`, `*dig_keys`, and `**options` and return the found value or `nil`.
43
+
44
+ You may optionally call `super` from `load` to enable logging by default.
45
+
46
+ ```ruby
47
+ # Define a new value provider that returns the abstract from a DuckDuckGo instant result search (https://duckduckgo.com/api)
48
+ require "container_config"
49
+ require "uri"
50
+ require "net/http"
51
+ require "json"
52
+
53
+ class DuckDuckGoAbstractProvider < ContainerConfig::Provider::Base
54
+ def name
55
+ "DuckDuckGo Wikipedia abstract provider"
56
+ end
57
+
58
+ def load(key, *dig_keys, **options)
59
+ super
60
+
61
+ response = Net::HTTP.get(URI.parse("https://api.duckduckgo.com/?q=#{URI.encode_www_form([key])}&format=json"))
62
+ JSON.parse(response)["Abstract"]
63
+ end
64
+ end
65
+
66
+ # Add the DuckDuckGo value provider to the array of existing providers
67
+ ContainerConfig.providers << DuckDuckGoAbstractProvider.new
68
+
69
+ # Use the DuckDuckGo value provider
70
+ ContainerConfig.load("rockhopper penguins")
71
+ # => "The rockhopper penguins are three closely related taxa of crested penguins..."
72
+ ```
73
+
74
+ #### Type Coercers
75
+
76
+ More supported types may be added by creating a new class that inherits from `ContainerConfig::Coercer::Base` and implementing the `name`, `type`, and `coerce` methods. The `name` method should return a `String` name for your type coercer, the `type` method should return a `Symbol` type for your type coercer, and the `coerce` method should receive a single `Object` `value` and return your coerced type.
77
+
78
+ ```ruby
79
+ # Define a new type coercer that simply prepends "MyType: " to the string representation of a value
80
+ require "container_config"
81
+
82
+ class MyTypeCoercer < ContainerConfig::Coercer::Base
83
+ def name
84
+ "My Type"
85
+ end
86
+
87
+ def type
88
+ :my_type
89
+ end
90
+
91
+ def coerce(value)
92
+ "MyType: #{value}"
93
+ end
94
+ end
95
+
96
+ # Add the type coercer to the array of existing type coercers
97
+ ContainerConfig.coercers << MyTypeCoercer.new
98
+
99
+ # Use the type coercer (KEY has value "key_value")
100
+ ContainerConfig.load("KEY", type: :my_type)
101
+ # => "MyType: key_value"
102
+ ```
103
+
104
+ ## Development
105
+
106
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
107
+
108
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
109
+
110
+ ## Contributing
111
+
112
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wheatevo/container_config.
113
+
114
+ ## License
115
+
116
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "container_config"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/container_config/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "container_config"
7
+ spec.version = ContainerConfig::VERSION
8
+ spec.authors = ["Matthew Newell"]
9
+ spec.email = ["matthewtnewell@gmail.com"]
10
+
11
+ spec.summary = "Loads container configuration values from environment variables, secrets, and credentials."
12
+ spec.description = "Loads container configuration values from environment variables, secrets, and credentials."
13
+ spec.homepage = "https://github.com/wheatevo/container_config"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/wheatevo/container_config"
19
+ spec.metadata["changelog_uri"] = "https://github.com/wheatevo/container_config/blob/master/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ # Development dependencies
31
+ spec.add_development_dependency "rake", "~> 13.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ spec.add_development_dependency "rubocop", "~> 1.7"
34
+ spec.add_development_dependency "rubocop-rake", "~> 0.5"
35
+ spec.add_development_dependency "rubocop-rspec", "~> 2.2"
36
+ spec.add_development_dependency "yard", "~> 0.9"
37
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "container_config/version"
4
+ require "container_config/logger"
5
+ require "container_config/provider"
6
+ require "container_config/coercer"
7
+ require "container_config/redis"
8
+
9
+ # Contains methods for loading and parsing container configuration
10
+ module ContainerConfig
11
+ #
12
+ # Loads a configuration setting from environment variables, mounted secrets, or the application credentials
13
+ #
14
+ # @param [String] key Configuration key to load
15
+ # @param [Array] dig_keys Variable keys to use to load from providers that accept a dig structure
16
+ # defaults to the lowercase key split by underscores
17
+ # "MY_PASSWORD" => ["my", "password"]
18
+ # @param [Hash] options Options Hash
19
+ # @option options [Boolean] :required whether to raise an exception if the setting cannot be found
20
+ # @option options [String] :default default value if the configuration setting cannot be found
21
+ # @option options [String] :secret_mount_directory directory where secret files are mounted
22
+ # @option options [Boolean] :coerce_nil where to coerce nil values (defaults to true)
23
+ # @option options [Boolean] :cache whether to cache the retrieved value and to return that value in future calls
24
+ # future calls must also specify cache: true to prevent the value from being re-read
25
+ # @option options [Symbol] :type type to use such as :boolean, :integer, :string, :symbol,
26
+ # :ssl_verify_mode, :ssl_certificate, or :ssl_key
27
+ # @option options [Array] :enum valid values for the configuration parameter, raises an exception
28
+ # if the value is not in the enum
29
+ #
30
+ # @return [Object] configuration setting value
31
+ #
32
+ def self.load(key, *dig_keys, **options)
33
+ logger.debug { "Loading configuration value for #{key}" }
34
+ options[:required] ||= options[:raise] || false
35
+ dig_keys = key.downcase.split("_") if dig_keys.empty?
36
+
37
+ return cache[key] if options[:cache] && cache.key?(key)
38
+
39
+ config_value = ContainerConfig::Provider.load_value(key, *dig_keys, **options)
40
+ handle_empty_value(config_value, key, **options)
41
+ config_value = ContainerConfig::Coercer.coerce_value(config_value, options[:type], **options)
42
+ handle_enum(config_value, **options)
43
+
44
+ cache[key] = config_value if options[:cache]
45
+
46
+ config_value
47
+ end
48
+
49
+ #
50
+ # Clears all entries from the configuration cache
51
+ #
52
+ def self.clear_cache
53
+ @cache = {}
54
+ end
55
+
56
+ #
57
+ # Gets the configuration cache
58
+ #
59
+ # @return [Hash] configuration cache
60
+ #
61
+ def self.cache
62
+ @cache ||= {}
63
+ end
64
+
65
+ #
66
+ # Gets the list of configuration value coercers
67
+ #
68
+ # @return [Array<ContainerConfig::Coercer::Base>] current value coercers
69
+ #
70
+ def self.coercers
71
+ @coercers ||= ContainerConfig::Coercer.default_coercers
72
+ end
73
+
74
+ #
75
+ # Sets the list of configuration value coercers
76
+ #
77
+ # @param [Array<ContainerConfig::Coercer::Base>] coercers new value coercers
78
+ #
79
+ def self.coercers=(coercers)
80
+ @coercers = coercers
81
+ end
82
+
83
+ #
84
+ # Gets the list of configuration value providers
85
+ #
86
+ # @return [Array<ContainerConfig::Provider::Base>] current value providers
87
+ #
88
+ def self.providers
89
+ @providers ||= ContainerConfig::Provider.default_providers
90
+ end
91
+
92
+ #
93
+ # Sets the list of configuration value providers
94
+ #
95
+ # @param [Array<ContainerConfig::Provider::Base>] providers new value providers
96
+ #
97
+ def self.providers=(providers)
98
+ @providers = providers
99
+ end
100
+
101
+ #
102
+ # Gets the container config logger
103
+ #
104
+ # @return [ContainerConfig::Logger] current logger
105
+ #
106
+ def self.logger
107
+ @logger ||= ContainerConfig::Logger.new($stdout, level: Logger::INFO)
108
+ end
109
+
110
+ #
111
+ # Sets the container config logger
112
+ #
113
+ # @param [::Logger] logger logger to set
114
+ #
115
+ def self.logger=(logger)
116
+ if logger.nil?
117
+ self.logger.level = Logger::FATAL
118
+ return self.logger
119
+ end
120
+ @logger = logger
121
+ end
122
+
123
+ #
124
+ # Gets the container config log formatter
125
+ #
126
+ # @return [::Logger::Formatter] current log formatter
127
+ #
128
+ def self.log_formatter
129
+ @log_formatter ||= logger.formatter
130
+ end
131
+
132
+ #
133
+ # Sets the container config log formatter
134
+ #
135
+ # @param [::Logger::Formatter] log_formatter new log formatter
136
+ #
137
+ def self.log_formatter=(log_formatter)
138
+ @log_formatter = log_formatter
139
+ logger.formatter = log_formatter
140
+ end
141
+
142
+ #
143
+ # Whether this is in the context of a Rails application
144
+ #
145
+ # @return [Boolean] true if in a Rails application, false otherwise
146
+ #
147
+ def self.rails_app?
148
+ defined?(::Rails) && ::Rails.respond_to?(:application)
149
+ end
150
+
151
+ class << self
152
+ private
153
+
154
+ def handle_empty_value(config_value, key, **options)
155
+ if config_value.nil? || config_value.to_s.empty?
156
+ provider_list = providers.map(&:name).join(", ")
157
+ logger.warn { "Could not find value for #{key} in providers: #{provider_list}" }
158
+ raise MissingRequiredValue, "Could not find value for #{key} in providers: #{provider_list}!" if options[:required]
159
+ else
160
+ logger.debug { "Configuration value for #{key} loaded" }
161
+ end
162
+ end
163
+
164
+ def handle_enum(config_value, **options)
165
+ return if !options[:enum] || options[:enum].include?(config_value)
166
+
167
+ valid_values = options[:enum].join(", ")
168
+ raise InvalidEnumValue, "Config value #{config_value.inspect} is invalid. Valid values: #{valid_values}"
169
+ end
170
+ end
171
+
172
+ # General ContainerConfig error
173
+ class Error < StandardError; end
174
+
175
+ # Error raised when a required value is missing when loading a configuration value
176
+ class MissingRequiredValue < Error; end
177
+
178
+ # Error raised when a derived class is missing an override method
179
+ class MissingOverride < Error; end
180
+
181
+ # Error raised when a configuration value is not within the provided enum
182
+ class InvalidEnumValue < Error; end
183
+ end
184
+
185
+ require "container_config/rails" if ContainerConfig.rails_app?