complex_config 0.22.3 → 0.23.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.
data/Rakefile CHANGED
@@ -11,10 +11,11 @@ GemHadar do
11
11
  description 'This library allows you to access configuration files via a simple interface'
12
12
  executables 'complex_config'
13
13
  test_dir 'spec'
14
- ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc',
15
- '.AppleDouble', '.DS_Store', 'errors.lst', 'tags'
14
+ ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage',
15
+ '.AppleDouble', '.DS_Store', 'errors.lst', 'tags', 'cscope.out', 'doc',
16
+ '.yardoc'
16
17
  package_ignore '.all_images.yml', '.utilsrc', '.rspec', '.tool-versions',
17
- '.gitignore'
18
+ '.gitignore', '.contexts'
18
19
 
19
20
  readme 'README.md'
20
21
  title "#{name.camelize} -- configuration library"
@@ -31,7 +32,8 @@ GemHadar do
31
32
  development_dependency 'rspec'
32
33
  development_dependency 'monetize'
33
34
  development_dependency 'debug'
34
- development_dependency 'all_images', '~> 0.8'
35
+ development_dependency 'all_images', '~> 0.8'
36
+ development_dependency 'context_spook', '~> 0.4'
35
37
  end
36
38
 
37
39
  task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.22.3
1
+ 0.23.0
data/bin/complex_config CHANGED
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # Complex Config Manager
4
+ #
5
+ # A command-line tool for managing encrypted configuration files.
6
+ #
7
+ # Features:
8
+ # - Edit, encrypt, decrypt, and display configuration files
9
+ # - Generate new encryption keys
10
+ # - Re-encrypt files with different keys
11
+ # - Secure file operations to prevent data corruption
2
12
 
3
13
  require 'complex_config/rude'
4
14
  require 'tins/xt'
@@ -9,6 +19,14 @@ include FileUtils
9
19
 
10
20
  $opts = go 'o:n:h'
11
21
 
22
+ # The usage method displays help information and exits the program
23
+ #
24
+ # This method outputs a formatted help message to the standard output,
25
+ # including usage instructions, available commands, and options. It then
26
+ # terminates the program with the specified exit code.
27
+ #
28
+ # @param msg [String] the help message to display
29
+ # @param rc [Integer] the exit code to use when terminating the program
12
30
  def usage(msg: 'Displaying help', rc: 0)
13
31
  puts <<~end
14
32
  #{msg}
@@ -32,6 +50,15 @@ def usage(msg: 'Displaying help', rc: 0)
32
50
  exit rc
33
51
  end
34
52
 
53
+ # The fetch_filename method processes and validates a configuration filename
54
+ #
55
+ # This method retrieves a filename from the command line arguments, verifies
56
+ # its existence, and optionally ensures it has the correct file extension
57
+ # suffix. It handles validation for encrypted configuration files and provides
58
+ # appropriate error messages when requirements are not met.
59
+ #
60
+ # @param suffix [TrueClass, FalseClass] whether to validate or remove the .enc suffix
61
+ # @return [String] the validated filename without the .enc suffix if requested
35
62
  def fetch_filename(suffix: true)
36
63
  fn = ARGV.shift.dup or usage msg: "config filename required", rc: 1
37
64
  if suffix
@@ -105,7 +132,10 @@ when 'recrypt'
105
132
  f.write(recrypted_text)
106
133
  mv encrypted_fn + '.enc', encrypted_fn + '.enc.bak'
107
134
  end
108
- puts "File was recrypted as #{(encrypted_fn + '.enc').inspect}. You can remove #{(encrypted_fn + '.enc.bak').inspect} now."
135
+ puts <<~EOT
136
+ File was recrypted as #{(encrypted_fn + '.enc').inspect}.
137
+ You can remove #{(encrypted_fn + '.enc.bak').inspect} now.
138
+ EOT
109
139
  else
110
140
  usage msg: "Unknown command #{command.inspect}", rc: 1
111
141
  end
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: complex_config 0.22.3 ruby lib
2
+ # stub: complex_config 0.23.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "complex_config".freeze
6
- s.version = "0.22.3".freeze
6
+ s.version = "0.23.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -23,13 +23,14 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.specification_version = 4
25
25
 
26
- s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.4".freeze])
26
+ s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.6".freeze])
27
27
  s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
28
28
  s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
29
29
  s.add_development_dependency(%q<rspec>.freeze, [">= 0".freeze])
30
30
  s.add_development_dependency(%q<monetize>.freeze, [">= 0".freeze])
31
31
  s.add_development_dependency(%q<debug>.freeze, [">= 0".freeze])
32
32
  s.add_development_dependency(%q<all_images>.freeze, ["~> 0.8".freeze])
33
+ s.add_development_dependency(%q<context_spook>.freeze, ["~> 0.4".freeze])
33
34
  s.add_runtime_dependency(%q<json>.freeze, [">= 0".freeze])
34
35
  s.add_runtime_dependency(%q<tins>.freeze, ["~> 1".freeze])
35
36
  s.add_runtime_dependency(%q<mize>.freeze, ["~> 0.6".freeze])
@@ -1,10 +1,39 @@
1
1
  module ComplexConfig
2
- Config = Struct.new('Config', :config_dir, :env, :deep_freeze, :plugins) do
2
+ # Configuration class for setting up ComplexConfig behavior
3
+ #
4
+ # This class provides a structured way to configure the ComplexConfig system,
5
+ # including environment settings, deep freezing behavior, and plugin registration.
6
+ #
7
+ # @example Basic configuration
8
+ # ComplexConfig.configure do |config|
9
+ # config.deep_freeze = false
10
+ # config.env = 'production'
11
+ # end
12
+ #
13
+ # @example Adding custom plugins
14
+ # ComplexConfig.configure do |config|
15
+ # config.add_plugin -> id do
16
+ # if base64_string = ask_and_send("#{id}_base64")
17
+ # Base64.decode64(base64_string)
18
+ # else
19
+ # skip
20
+ # end
21
+ # end
22
+ # end
23
+ class Config < Struct.new(:config_dir, :env, :deep_freeze, :plugins)
24
+ # Initializes a new configuration instance
3
25
  def initialize(*)
4
26
  super
5
27
  self.plugins = []
6
28
  end
7
29
 
30
+ # Applies the configuration to a provider
31
+ #
32
+ # This method sets all configuration attributes on the provider and
33
+ # registers any plugins. It's called internally by ComplexConfig.configure.
34
+ #
35
+ # @param provider [ComplexConfig::Provider] The provider to configure
36
+ # @return [self] Returns self for chaining
8
37
  def configure(provider)
9
38
  each_pair do |name, value|
10
39
  value.nil? and next
@@ -17,12 +46,28 @@ module ComplexConfig
17
46
  self
18
47
  end
19
48
 
49
+ # Adds a plugin to the configuration
50
+ #
51
+ # Plugins are lambda expressions that can provide custom behavior for
52
+ # configuration attributes.
53
+ #
54
+ # @param code [Proc] The plugin code to add
55
+ # @return [self] Returns self for chaining
20
56
  def add_plugin(code)
21
57
  plugins << code
22
58
  self
23
59
  end
24
60
  end
25
61
 
62
+ # Configures the ComplexConfig system with the provided settings
63
+ #
64
+ # This is the main entry point for configuring ComplexConfig. It creates a
65
+ # configuration object, yields it to the provided block for customization,
66
+ # and applies the configuration to the provider.
67
+ #
68
+ # @yield [config] Yields the configuration object for setup
69
+ # @yieldparam config [Config] The configuration object to modify
70
+ # @return [Config] The configured object
26
71
  def self.configure
27
72
  config = Config.new
28
73
  yield config
@@ -1,7 +1,27 @@
1
1
  require "openssl"
2
2
  require "base64"
3
3
 
4
+ # A class that provides encryption and decryption services using AES-128-GCM
5
+ #
6
+ # This class handles the encryption and decryption of configuration data using
7
+ # OpenSSL's AES-128-GCM cipher mode. It manages the encryption key validation,
8
+ # initialization vector generation, and authentication tag handling required
9
+ # for secure encrypted communication.
10
+ #
11
+ # @see ComplexConfig::EncryptionError
12
+ # @see ComplexConfig::EncryptionKeyInvalid
13
+ # @see ComplexConfig::DecryptionFailed
4
14
  class ComplexConfig::Encryption
15
+ # Initializes a new encryption instance with the specified secret key
16
+ #
17
+ # This method sets up the encryption object by validating the secret key
18
+ # length and preparing the OpenSSL cipher for AES-128-GCM encryption
19
+ # operations
20
+ #
21
+ # @param secret [String] the encryption key to use, must be exactly 16 bytes
22
+ # long
23
+ # @raise [ComplexConfig::EncryptionKeyInvalid] if the secret key is not
24
+ # exactly 16 bytes in length
5
25
  def initialize(secret)
6
26
  @secret = secret
7
27
  @secret.size != 16 and raise ComplexConfig::EncryptionKeyInvalid,
@@ -9,6 +29,19 @@ class ComplexConfig::Encryption
9
29
  @cipher = OpenSSL::Cipher.new('aes-128-gcm')
10
30
  end
11
31
 
32
+ # The encrypt method encodes text using AES-128-GCM encryption
33
+ #
34
+ # This method takes a text input and encrypts it using the OpenSSL cipher
35
+ # configured with AES-128-GCM mode. It generates a random initialization
36
+ # vector and authentication tag, then combines the encrypted data, IV, and
37
+ # auth tag into a base64-encoded string separated by '--'
38
+ #
39
+ # @param text [Object] the object to encrypt, which will be marshaled before
40
+ # encryption
41
+ #
42
+ # @return [String] the base64-encoded encrypted string including the
43
+ # encrypted data, initialization vector, and authentication tag separated
44
+ # by '--'
12
45
  def encrypt(text)
13
46
  @cipher.encrypt
14
47
  @cipher.key = @secret
@@ -25,6 +58,15 @@ class ComplexConfig::Encryption
25
58
  ].map { |v| base64_encode(v) }.join('--')
26
59
  end
27
60
 
61
+ # The decrypt method decodes encrypted text using AES-128-GCM decryption
62
+ #
63
+ # @param text [String] the base64-encoded encrypted string containing the
64
+ # encrypted data, initialization vector, and authentication tag separated
65
+ # by '--'
66
+ #
67
+ # @return [Object] the decrypted and marshaled object
68
+ # @raise [ComplexConfig::DecryptionFailed] if the authentication tag is
69
+ # invalid or decryption fails with the provided key
28
70
  def decrypt(text)
29
71
  encrypted, iv, auth_tag = text.split('--').map { |v| base64_decode(v) }
30
72
 
@@ -47,10 +89,26 @@ class ComplexConfig::Encryption
47
89
 
48
90
  private
49
91
 
92
+ # The base64_encode method encodes binary data into a Base64 string format
93
+ #
94
+ # This method takes binary data as input and converts it into a
95
+ # Base64-encoded string representation using the strict encoding mode, which
96
+ # ensures that the output contains only valid Base64 characters and raises an
97
+ # error for invalid input
98
+ #
99
+ # @param x [Object] the binary data to encode, typically a String or binary buffer
100
+ # @return [String] the Base64-encoded representation of the input data
50
101
  def base64_encode(x)
51
102
  ::Base64.strict_encode64(x)
52
103
  end
53
104
 
105
+ # The base64_decode method decodes a Base64-encoded string back into its
106
+ # original binary data format.
107
+ #
108
+ # @param x [String] the Base64-encoded string to decode, which will have
109
+ # leading/trailing whitespace stripped
110
+ # @return [String] the decoded binary data as a string
111
+ # @see base64_encode for the corresponding encoding method
54
112
  def base64_decode(x)
55
113
  ::Base64.strict_decode64(x.strip)
56
114
  end
@@ -1,5 +1,27 @@
1
1
  module ComplexConfig
2
+
3
+ # Abstract base class for ComplexConfig exceptions
4
+ #
5
+ # This class serves as the root of the exception hierarchy for the
6
+ # ComplexConfig library. All custom exceptions raised by ComplexConfig should
7
+ # inherit from this class.
8
+ #
9
+ # @abstract
2
10
  class ComplexConfigError < StandardError
11
+ # Wraps an exception with a custom error class from the ComplexConfig
12
+ # hierarchy
13
+ #
14
+ # This method takes an exception and wraps it with a new error class that
15
+ # is derived from the ComplexConfig error hierarchy. It allows for more
16
+ # specific error handling by converting one type of exception into another
17
+ # while preserving the original message and backtrace.
18
+ #
19
+ # @param klass [Class, String] The error class to wrap the exception with,
20
+ # either as a Class object or a string name that can be resolved via
21
+ # ComplexConfig.const_get
22
+ # @param e [StandardError] The original exception to be wrapped
23
+ # @return [ComplexConfig::ComplexConfigError] A new instance of the specified
24
+ # error class containing the original exception's message and backtrace
3
25
  def self.wrap(klass, e)
4
26
  Class === klass or klass = ComplexConfig.const_get(klass)
5
27
  error = klass.new(e.message)
@@ -8,24 +30,90 @@ module ComplexConfig
8
30
  end
9
31
  end
10
32
 
33
+ # An exception raised when an expected configuration attribute is missing
34
+ #
35
+ # This error is triggered when code attempts to access a configuration
36
+ # attribute that has not been defined or set within the configuration system.
37
+ # It inherits from ComplexConfigError, making it part of the library's
38
+ # standardized exception hierarchy for consistent error handling.
39
+ #
40
+ # @see ComplexConfigError
11
41
  class AttributeMissing < ComplexConfigError
12
42
  end
13
43
 
44
+ # An exception raised when a required configuration file is missing
45
+ #
46
+ # This error is triggered when the system attempts to access a configuration
47
+ # file that cannot be found at the expected location. It inherits from
48
+ # ComplexConfigError, making it part of the library's standardized exception
49
+ # hierarchy for consistent error handling.
50
+ #
51
+ # @see ComplexConfigError
52
+ # @see ComplexConfig::Provider#config
53
+ # @see ComplexConfig::Provider#decrypt_config_case
14
54
  class ConfigurationFileMissing < ComplexConfigError
15
55
  end
16
56
 
57
+ # An exception raised when a configuration file has invalid syntax
58
+ #
59
+ # This error is triggered when the system encounters YAML syntax errors in
60
+ # configuration files that prevent them from being properly parsed. It
61
+ # inherits from ComplexConfigError, making it part of the library's
62
+ # standardized exception hierarchy for consistent error handling.
63
+ #
64
+ # @see ComplexConfigError
65
+ # @see ComplexConfig::Provider#config
66
+ # @see ComplexConfig::Provider#decrypt_config_case
17
67
  class ConfigurationSyntaxError < ComplexConfigError
18
68
  end
19
69
 
70
+ # An abstract base class for encryption-related errors in the ComplexConfig
71
+ # library.
72
+ #
73
+ # This class serves as the root of the encryption exception hierarchy,
74
+ # providing a common base for all encryption-specific exceptions raised by
75
+ # ComplexConfig.
76
+ #
77
+ # @abstract
78
+ # @see ComplexConfigError
20
79
  class EncryptionError < ComplexConfigError
21
80
  end
22
81
 
82
+ # An exception raised when an encryption key has an invalid format or length
83
+ #
84
+ # This error is triggered when an encryption key does not meet the required
85
+ # specifications, such as having an incorrect byte length. It inherits from
86
+ # EncryptionError, making it part of the library's standardized exception
87
+ # hierarchy for consistent error handling.
88
+ #
89
+ # @see EncryptionError
90
+ # @see ComplexConfig::Encryption
23
91
  class EncryptionKeyInvalid < EncryptionError
24
92
  end
25
93
 
94
+ # An exception raised when a required encryption key is missing
95
+ #
96
+ # This error is triggered when the system attempts to access an encryption key
97
+ # that cannot be found through any of the configured sources. It inherits from
98
+ # EncryptionError, making it part of the library's standardized exception
99
+ # hierarchy for consistent error handling.
100
+
101
+ # @see EncryptionError
102
+ # @see ComplexConfig::Encryption
103
+ # @see ComplexConfig::KeySource
26
104
  class EncryptionKeyMissing < EncryptionError
27
105
  end
28
106
 
107
+ # An exception raised when decryption operations fail
108
+ #
109
+ # This error is triggered when the system encounters issues during the
110
+ # decryption process, such as invalid authentication tags or cipher errors
111
+ # that prevent successful decryption. It inherits from EncryptionError,
112
+ # making it part of the library's standardized exception hierarchy for
113
+ # consistent error handling.
114
+ #
115
+ # @see EncryptionError
116
+ # @see ComplexConfig::Encryption#decrypt
29
117
  class DecryptionFailed < EncryptionError
30
118
  end
31
119
  end
@@ -1,4 +1,18 @@
1
+ # A key source class that provides encryption keys from various sources
2
+ #
3
+ # The KeySource class is responsible for managing and retrieving encryption
4
+ # keys from different possible sources such as files, environment variables, or
5
+ # direct values. It supports multiple configuration options and ensures that
6
+ # only one source is used at a time.
1
7
  class ComplexConfig::KeySource
8
+ # The initialize method sets up the key source with one of several possible settings.
9
+ #
10
+ # @param pathname [String, nil] The path to a key file
11
+ # @param env_var [String, nil] The name of an environment variable containing the key
12
+ # @param var [String, nil] A string value representing the key
13
+ # @param master_key_pathname [String, nil] The path to a master key file
14
+ #
15
+ # @raise [ArgumentError] if more than one setting is provided
2
16
  def initialize(pathname: nil, env_var: nil, var: nil, master_key_pathname: nil)
3
17
  settings = [ pathname, env_var, var, master_key_pathname ].compact.size
4
18
  if settings > 1
@@ -10,10 +24,22 @@ class ComplexConfig::KeySource
10
24
  pathname, env_var, var, master_key_pathname
11
25
  end
12
26
 
27
+ # The master_key? method checks whether a master key pathname has been set.
28
+ #
29
+ # @return [TrueClass, FalseClass] true if a master key pathname is
30
+ # configured, false otherwise
13
31
  def master_key?
14
32
  !!@master_key_pathname
15
33
  end
16
34
 
35
+ # The key method retrieves the encryption key from the configured source.
36
+ #
37
+ # This method attempts to obtain the encryption key by checking various
38
+ # possible sources in order: a direct value, an environment variable,
39
+ # a master key file, or a key file associated with a pathname.
40
+ #
41
+ # @return [String, nil] the encryption key as a string if found, or nil if no
42
+ # key source is configured or accessible
17
43
  def key
18
44
  if @var
19
45
  @var.ask_and_send(:chomp)
@@ -27,6 +53,12 @@ class ComplexConfig::KeySource
27
53
  rescue Errno::ENOENT, Errno::ENOTDIR
28
54
  end
29
55
 
56
+ # The key_bytes method converts the encryption key to bytes format.
57
+ #
58
+ # This method takes the encryption key value and packs it into a binary byte
59
+ # representation using hexadecimal encoding.
60
+ #
61
+ # @return [String] the encryption key as a binary string of bytes
30
62
  def key_bytes
31
63
  [ key ].pack('H*')
32
64
  end
@@ -1,5 +1,26 @@
1
1
  class ComplexConfig::Provider
2
+ # A module that provides convenient shortcuts for accessing configuration
3
+ # data
4
+ #
5
+ # This module defines methods that create proxy objects for easy
6
+ # configuration access with lazy evaluation and environment-specific lookups.
7
+ # It's designed to be included in classes that need quick access to
8
+ # configuration settings without explicit environment handling.
9
+ #
10
+ # @see ComplexConfig::Provider::Shortcuts
2
11
  module Shortcuts
12
+ # The complex_config_with_env method provides access to configuration data
13
+ # with explicit environment targeting.
14
+ #
15
+ # @param name [String, nil] The name of the configuration to access, or nil
16
+ # to return a proxy object
17
+ #
18
+ # @param env [String] The environment name to use for configuration
19
+ # lookups, defaults to the current provider environment
20
+ #
21
+ # @return [ComplexConfig::Settings, ComplexConfig::Proxy] Returns either
22
+ # the configuration settings for the specified name and environment, or a
23
+ # proxy object if no name is provided
3
24
  def complex_config_with_env(name = nil, env = ComplexConfig::Provider.env)
4
25
  if name
5
26
  ComplexConfig::Provider[name][env.to_s]
@@ -8,8 +29,21 @@ class ComplexConfig::Provider
8
29
  end
9
30
  end
10
31
 
32
+ # Alias for {complex_config_with_env}
33
+ # Provides a shorter syntax for accessing configuration with environment
34
+ # targeting.
35
+ #
36
+ # @see complex_config_with_env
11
37
  alias cc complex_config_with_env
12
38
 
39
+ # The complex_config method provides access to configuration data with
40
+ # optional name parameter.
41
+ #
42
+ # @param name [String, nil] The name of the configuration to access, or nil
43
+ # to return a proxy object
44
+ # @return [ComplexConfig::Settings, ComplexConfig::Proxy] Returns either
45
+ # the configuration settings for the specified name, or a proxy object if
46
+ # no name is provided
13
47
  def complex_config(name = nil)
14
48
  if name
15
49
  ComplexConfig::Provider[name]