complex_config 0.25.0 → 0.26.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.
- checksums.yaml +4 -4
- data/.github/workflows/static.yml +51 -0
- data/CHANGES.md +52 -0
- data/README.md +49 -11
- data/Rakefile +8 -0
- data/VERSION +1 -1
- data/bin/complex_config +10 -5
- data/complex_config.gemspec +5 -5
- data/lib/complex_config/provider.rb +24 -1
- data/lib/complex_config/settings.rb +25 -16
- data/lib/complex_config/version.rb +1 -1
- data/spec/complex_config/provider_spec.rb +12 -0
- data/spec/complex_config/settings_spec.rb +49 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 344002a57feb04b6b964c0a22190b91eb6516329583141521bb28b1151801a12
|
|
4
|
+
data.tar.gz: c4827ea899280f0a61fb12aff86e38873e108146a21fddac406417b0a8aac838
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81baebce37c27193f528b07c5b5104d3527de3ece471df7493664832b53a5c69a24c120200e5814e34eeeeac9c31c4fbc28ede81ad8957f59cf98533bc40569d
|
|
7
|
+
data.tar.gz: b22c324308a9b432a1134cb357ab98b8656f00c72052d462fcbe7c7c5972b4f7583f29d1fa332c94be80225bd44ebbc4137e77b6b1d7a9f9ab2c87b35b67ac55
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Simple workflow for deploying static content to GitHub Pages
|
|
2
|
+
name: Deploy static content to Pages
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
# Runs on pushes targeting the default branch
|
|
6
|
+
push:
|
|
7
|
+
branches: [ "master" ]
|
|
8
|
+
|
|
9
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
|
19
|
+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: "pages"
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# Single deploy job since we're just deploying
|
|
26
|
+
deploy:
|
|
27
|
+
environment:
|
|
28
|
+
name: github-pages
|
|
29
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
- name: Setup Pages
|
|
35
|
+
uses: actions/configure-pages@v5
|
|
36
|
+
- name: Setup Ruby
|
|
37
|
+
uses: ruby/setup-ruby@v1
|
|
38
|
+
with:
|
|
39
|
+
ruby-version: '3.4'
|
|
40
|
+
- name: Generate Documentation
|
|
41
|
+
run: |
|
|
42
|
+
gem install gem_hadar
|
|
43
|
+
bundle install
|
|
44
|
+
rake doc
|
|
45
|
+
- name: Upload artifact
|
|
46
|
+
uses: actions/upload-pages-artifact@v3
|
|
47
|
+
with:
|
|
48
|
+
path: 'doc'
|
|
49
|
+
- name: Deploy to GitHub Pages
|
|
50
|
+
id: deployment
|
|
51
|
+
uses: actions/deploy-pages@v4
|
data/CHANGES.md
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
# Changes
|
|
2
|
+
|
|
3
|
+
## 2026-07-10 v0.26.0
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Added raw data decryption and CLI piping support via
|
|
8
|
+
`ComplexConfig::Provider#decrypt_data`.
|
|
9
|
+
- Enhanced the `complex_config` CLI:
|
|
10
|
+
- Added `-k` flag to specify an explicit key file path.
|
|
11
|
+
- Added `-I` flag to enable streaming decryption from stdin directly to
|
|
12
|
+
stdout.
|
|
13
|
+
- Introduced a GitHub Actions workflow (`static.yml`) for automated
|
|
14
|
+
documentation deployment to GitHub Pages using Ruby **3.4**.
|
|
15
|
+
|
|
16
|
+
### Improvements & Changes
|
|
17
|
+
|
|
18
|
+
- Renamed `ComplexConfig::Settings#attributes_update_if_nil` to
|
|
19
|
+
`ComplexConfig::Settings#attributes_update_if_undefined` to better reflect
|
|
20
|
+
that it preserves existing keys, including explicit `nil` overrides. A
|
|
21
|
+
deprecation layer was added to the old method.
|
|
22
|
+
- Hardened the encryption CLI by replacing `Tempfile.open` with
|
|
23
|
+
`Tempfile.create` and adding a `.yml` suffix to temporary files for improved
|
|
24
|
+
editor syntax highlighting.
|
|
25
|
+
- Updated dependencies:
|
|
26
|
+
- `gem_hadar` updated from **~> 2.10** to **>= 2.17.1**.
|
|
27
|
+
- `s.rubygems_version` updated from **4.0.2** to **4.0.10**.
|
|
28
|
+
- Updated the Alpine build image to use stable Ruby **4.0** instead of
|
|
29
|
+
**4.0-rc**.
|
|
30
|
+
- Added changelog configuration to the `Rakefile` using `GemHadar`.
|
|
31
|
+
|
|
32
|
+
### Documentation
|
|
33
|
+
|
|
34
|
+
- Expanded `README.md` with:
|
|
35
|
+
- A guide on securely comparing encrypted secrets using process
|
|
36
|
+
substitution and the `-I` flag.
|
|
37
|
+
- Examples for automation and piping using input redirection (`<`).
|
|
38
|
+
- Critical security warnings regarding plain-text secrets in version
|
|
39
|
+
control.
|
|
40
|
+
- Updated instructions for the `edit` command and `recrypt` backup
|
|
41
|
+
strategies.
|
|
42
|
+
- A link to the API documentation.
|
|
43
|
+
- Added an `@example` block to `ComplexConfig::Provider#decrypt_config`
|
|
44
|
+
documenting the automatic appending of the `.enc` extension.
|
|
45
|
+
|
|
46
|
+
### Tests
|
|
47
|
+
|
|
48
|
+
- Added test cases in `spec/complex_config/provider_spec.rb` for
|
|
49
|
+
`ComplexConfig::Provider#decrypt_config`.
|
|
50
|
+
- Expanded `spec/complex_config/settings_spec.rb` to cover `attributes_update`,
|
|
51
|
+
`attributes_update_if_undefined`, and the associated deprecation warning.
|
|
52
|
+
|
|
1
53
|
## 2025-12-19 v0.25.0
|
|
2
54
|
|
|
3
55
|
- Added support for Ruby **4.0**-rc-alpine image in test matrix
|
data/README.md
CHANGED
|
@@ -6,6 +6,10 @@ This library makes your YAML configuration files available via a nice API. It
|
|
|
6
6
|
also supports different configurations for each `RAILS_ENV` environment and
|
|
7
7
|
using plugins to return more complex settings values. 🚀
|
|
8
8
|
|
|
9
|
+
## Documentation
|
|
10
|
+
|
|
11
|
+
Complete API documentation is available at: [GitHub.io](https://flori.github.io/complex_config/)
|
|
12
|
+
|
|
9
13
|
## Architecture Overview 🏗️
|
|
10
14
|
|
|
11
15
|
ComplexConfig follows a well-defined architectural pattern with clear
|
|
@@ -269,8 +273,8 @@ plain text.
|
|
|
269
273
|
|
|
270
274
|
```ruby
|
|
271
275
|
# Write encrypted configuration
|
|
272
|
-
ComplexConfig::Provider.write_config('database',
|
|
273
|
-
value: { password: 'secret_password' },
|
|
276
|
+
ComplexConfig::Provider.write_config('database',
|
|
277
|
+
value: { password: 'secret_password' },
|
|
274
278
|
encrypt: :random
|
|
275
279
|
)
|
|
276
280
|
# => Returns the random 128-bit master key as hexadecimal string.
|
|
@@ -334,22 +338,22 @@ easy access during development and deployment.
|
|
|
334
338
|
- **Secure File Operations**: Encrypts/decrypts configuration files with
|
|
335
339
|
automatic backup handling
|
|
336
340
|
- **Edit Support**: Opens encrypted files in your preferred editor (`EDITOR`
|
|
337
|
-
environment variable or `vi` by default)
|
|
341
|
+
environment variable or `vi` by default)
|
|
338
342
|
- **Key Management**: Generates new encryption keys and supports key rotation
|
|
339
343
|
- **Atomic Writes**: Uses secure file writing to prevent data corruption
|
|
340
344
|
|
|
341
345
|
#### Usage Examples 📋
|
|
342
346
|
|
|
343
347
|
```bash
|
|
348
|
+
# 🛡️ Safely edit an encrypted configuration file
|
|
349
|
+
complex_config edit config/database.yml.enc
|
|
350
|
+
|
|
344
351
|
# Encrypt a configuration file
|
|
345
352
|
complex_config encrypt config/database.yml
|
|
346
353
|
|
|
347
|
-
# Decrypt a configuration file
|
|
354
|
+
# Decrypt a configuration file
|
|
348
355
|
complex_config decrypt config/database.yml.enc
|
|
349
356
|
|
|
350
|
-
# Edit an encrypted configuration file
|
|
351
|
-
complex_config edit config/database.yml.enc
|
|
352
|
-
|
|
353
357
|
# Display decrypted content without writing to disk
|
|
354
358
|
complex_config display config/database.yml.enc
|
|
355
359
|
|
|
@@ -360,17 +364,51 @@ complex_config new_key
|
|
|
360
364
|
complex_config recrypt -o OLD_KEY -n NEW_KEY config/database.yml.enc
|
|
361
365
|
```
|
|
362
366
|
|
|
367
|
+
#### Automation & Piping 🚀
|
|
368
|
+
|
|
369
|
+
The `complex_config` tool can be used as a filter in shell pipelines by reading
|
|
370
|
+
encrypted data from standard input via the `-I` flag. This is ideal for CI/CD
|
|
371
|
+
pipelines or secret managers where keys are provided via environment variables
|
|
372
|
+
or explicit paths.
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Decrypt a file using redirection
|
|
376
|
+
< config/database.yml.enc complex_config decrypt -I
|
|
377
|
+
|
|
378
|
+
# Decrypt using an explicit key file instead of environment variables
|
|
379
|
+
< config/database.yml.enc complex_config decrypt -I -k config/database.yml.key
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### Advanced: Securely Comparing Encrypted Secrets 🧙♂️
|
|
383
|
+
|
|
384
|
+
For the ultimate security workflow, you can combine process substitution with
|
|
385
|
+
the piping feature to compare encrypted files without ever writing plain-text
|
|
386
|
+
secrets to your disk. This is particularly useful for diffing a current secret
|
|
387
|
+
against a previous version in Git history.
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Diff secrets in the current branch against those stored in the master branch
|
|
391
|
+
diff --color -u <(git show master:config/secrets.yml.enc | complex_config decrypt -I) <(complex_config decrypt -O config/secrets.yml.enc)
|
|
392
|
+
```
|
|
393
|
+
|
|
363
394
|
#### Security Considerations ⚠️
|
|
364
395
|
|
|
365
396
|
The script uses symmetric encryption for configuration files. When using
|
|
366
397
|
encrypted configurations:
|
|
367
398
|
|
|
368
|
-
|
|
399
|
+
|
|
400
|
+
1. **Avoid Manual Decryption**: Strictly avoid using `complex_config decrypt`
|
|
401
|
+
to create plain-text copies of secrets on your filesystem. Plain-text secret
|
|
402
|
+
files are frequently committed to version control by accident (e.g., via
|
|
403
|
+
`git add .`), leading to catastrophic security breaches. Always use
|
|
404
|
+
`complex_config edit`, which uses a tempfile and avoids the creation of a
|
|
405
|
+
unencrypted copy in the repository.
|
|
406
|
+
2. **Key Management**: Store your encryption keys securely (never in version
|
|
369
407
|
control)
|
|
370
|
-
|
|
408
|
+
3. **File Permissions**: Ensure encrypted `.enc` files have appropriate
|
|
371
409
|
permissions
|
|
372
|
-
|
|
373
|
-
files
|
|
410
|
+
4. **Backup Strategy**: The `recrypt` command automatically creates backup
|
|
411
|
+
files, you can delete them afterwards if everything worked.
|
|
374
412
|
|
|
375
413
|
#### Integration with ComplexConfig 🔗
|
|
376
414
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.26.0
|
data/bin/complex_config
CHANGED
|
@@ -17,7 +17,7 @@ require 'tempfile'
|
|
|
17
17
|
require 'fileutils'
|
|
18
18
|
include FileUtils
|
|
19
19
|
|
|
20
|
-
$opts = go 'o:n:IOh'
|
|
20
|
+
$opts = go 'o:n:k:IOh'
|
|
21
21
|
|
|
22
22
|
# The usage method displays help information and exits the program
|
|
23
23
|
#
|
|
@@ -44,7 +44,9 @@ def usage(msg: 'Displaying help', rc: 0)
|
|
|
44
44
|
Options are
|
|
45
45
|
|
|
46
46
|
-c CONFIG_DIR set CONFIG_DIR (default: "./config")
|
|
47
|
-
-
|
|
47
|
+
-k KEY_PATH the path to the keyfile used if any (decrypt -I)
|
|
48
|
+
-I read input from stdin instead of file (encrypt/decrypt)
|
|
49
|
+
for decrypt this option implies -O
|
|
48
50
|
-O write output to stdout instead of file (decrypt)
|
|
49
51
|
-h this help
|
|
50
52
|
|
|
@@ -87,7 +89,7 @@ when 'edit'
|
|
|
87
89
|
fn = fetch_filename
|
|
88
90
|
catch :did_not_change do
|
|
89
91
|
File.secure_write(fn + '.enc') do |f|
|
|
90
|
-
Tempfile.
|
|
92
|
+
Tempfile.create(%w[ complex_config .yml ]) do |t|
|
|
91
93
|
config = ComplexConfig::Provider.decrypt_config(fn)
|
|
92
94
|
t.write config
|
|
93
95
|
t.flush
|
|
@@ -104,10 +106,13 @@ when 'edit'
|
|
|
104
106
|
end
|
|
105
107
|
end
|
|
106
108
|
when 'decrypt'
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
if $opts[?I]
|
|
110
|
+
$stdout.puts ComplexConfig::Provider.decrypt_data($stdin.read, $opts[?k])
|
|
111
|
+
elsif $opts[?O]
|
|
112
|
+
fn = fetch_filename
|
|
109
113
|
$stdout.puts ComplexConfig::Provider.decrypt_config(fn)
|
|
110
114
|
else
|
|
115
|
+
fn = fetch_filename
|
|
111
116
|
File.exist?(fn) and usage msg: "decrypted config #{fn.inspect} already exists", rc: 1
|
|
112
117
|
File.secure_write(fn) do |f|
|
|
113
118
|
f.write ComplexConfig::Provider.decrypt_config(fn)
|
data/complex_config.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: complex_config 0.
|
|
2
|
+
# stub: complex_config 0.26.0 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "complex_config".freeze
|
|
6
|
-
s.version = "0.
|
|
6
|
+
s.version = "0.26.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]
|
|
@@ -13,17 +13,17 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.email = "flori@ping.de".freeze
|
|
14
14
|
s.executables = ["complex_config".freeze]
|
|
15
15
|
s.extra_rdoc_files = ["README.md".freeze, "lib/complex_config.rb".freeze, "lib/complex_config/config.rb".freeze, "lib/complex_config/encryption.rb".freeze, "lib/complex_config/errors.rb".freeze, "lib/complex_config/key_source.rb".freeze, "lib/complex_config/plugins.rb".freeze, "lib/complex_config/plugins/enable.rb".freeze, "lib/complex_config/plugins/money.rb".freeze, "lib/complex_config/plugins/uri.rb".freeze, "lib/complex_config/provider.rb".freeze, "lib/complex_config/provider/shortcuts.rb".freeze, "lib/complex_config/proxy.rb".freeze, "lib/complex_config/railtie.rb".freeze, "lib/complex_config/rude.rb".freeze, "lib/complex_config/settings.rb".freeze, "lib/complex_config/shortcuts.rb".freeze, "lib/complex_config/tree.rb".freeze, "lib/complex_config/version.rb".freeze]
|
|
16
|
-
s.files = ["CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/complex_config".freeze, "complex_config.gemspec".freeze, "config/products.yml".freeze, "lib/complex_config.rb".freeze, "lib/complex_config/config.rb".freeze, "lib/complex_config/encryption.rb".freeze, "lib/complex_config/errors.rb".freeze, "lib/complex_config/key_source.rb".freeze, "lib/complex_config/plugins.rb".freeze, "lib/complex_config/plugins/enable.rb".freeze, "lib/complex_config/plugins/money.rb".freeze, "lib/complex_config/plugins/uri.rb".freeze, "lib/complex_config/provider.rb".freeze, "lib/complex_config/provider/shortcuts.rb".freeze, "lib/complex_config/proxy.rb".freeze, "lib/complex_config/railtie.rb".freeze, "lib/complex_config/rude.rb".freeze, "lib/complex_config/settings.rb".freeze, "lib/complex_config/shortcuts.rb".freeze, "lib/complex_config/tree.rb".freeze, "lib/complex_config/version.rb".freeze, "spec/complex_config/config_spec.rb".freeze, "spec/complex_config/encryption_spec.rb".freeze, "spec/complex_config/key_source_spec.rb".freeze, "spec/complex_config/plugins_spec.rb".freeze, "spec/complex_config/provider_spec.rb".freeze, "spec/complex_config/settings_spec.rb".freeze, "spec/complex_config/shortcuts_spec.rb".freeze, "spec/config/broken_config.yml".freeze, "spec/config/config.yml".freeze, "spec/config/config_with_alias.yml".freeze, "spec/config/empty_config.yml".freeze, "spec/config/legacy_classes.yml".freeze, "spec/config/master.key".freeze, "spec/config/with-key-file.yml.enc".freeze, "spec/config/with-key-file.yml.key".freeze, "spec/config/without-key-file.yml.enc".freeze, "spec/spec_helper.rb".freeze]
|
|
16
|
+
s.files = [".github/workflows/static.yml".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/complex_config".freeze, "complex_config.gemspec".freeze, "config/products.yml".freeze, "lib/complex_config.rb".freeze, "lib/complex_config/config.rb".freeze, "lib/complex_config/encryption.rb".freeze, "lib/complex_config/errors.rb".freeze, "lib/complex_config/key_source.rb".freeze, "lib/complex_config/plugins.rb".freeze, "lib/complex_config/plugins/enable.rb".freeze, "lib/complex_config/plugins/money.rb".freeze, "lib/complex_config/plugins/uri.rb".freeze, "lib/complex_config/provider.rb".freeze, "lib/complex_config/provider/shortcuts.rb".freeze, "lib/complex_config/proxy.rb".freeze, "lib/complex_config/railtie.rb".freeze, "lib/complex_config/rude.rb".freeze, "lib/complex_config/settings.rb".freeze, "lib/complex_config/shortcuts.rb".freeze, "lib/complex_config/tree.rb".freeze, "lib/complex_config/version.rb".freeze, "spec/complex_config/config_spec.rb".freeze, "spec/complex_config/encryption_spec.rb".freeze, "spec/complex_config/key_source_spec.rb".freeze, "spec/complex_config/plugins_spec.rb".freeze, "spec/complex_config/provider_spec.rb".freeze, "spec/complex_config/settings_spec.rb".freeze, "spec/complex_config/shortcuts_spec.rb".freeze, "spec/config/broken_config.yml".freeze, "spec/config/config.yml".freeze, "spec/config/config_with_alias.yml".freeze, "spec/config/empty_config.yml".freeze, "spec/config/legacy_classes.yml".freeze, "spec/config/master.key".freeze, "spec/config/with-key-file.yml.enc".freeze, "spec/config/with-key-file.yml.key".freeze, "spec/config/without-key-file.yml.enc".freeze, "spec/spec_helper.rb".freeze]
|
|
17
17
|
s.homepage = "https://github.com/flori/complex_config".freeze
|
|
18
18
|
s.licenses = ["Apache-2.0".freeze]
|
|
19
19
|
s.rdoc_options = ["--title".freeze, "ComplexConfig -- configuration library".freeze, "--main".freeze, "README.md".freeze]
|
|
20
|
-
s.rubygems_version = "4.0.
|
|
20
|
+
s.rubygems_version = "4.0.10".freeze
|
|
21
21
|
s.summary = "configuration library".freeze
|
|
22
22
|
s.test_files = ["spec/complex_config/config_spec.rb".freeze, "spec/complex_config/encryption_spec.rb".freeze, "spec/complex_config/key_source_spec.rb".freeze, "spec/complex_config/plugins_spec.rb".freeze, "spec/complex_config/provider_spec.rb".freeze, "spec/complex_config/settings_spec.rb".freeze, "spec/complex_config/shortcuts_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
23
23
|
|
|
24
24
|
s.specification_version = 4
|
|
25
25
|
|
|
26
|
-
s.add_development_dependency(%q<gem_hadar>.freeze, ["
|
|
26
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.17.1".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])
|
|
@@ -187,6 +187,12 @@ class ComplexConfig::Provider
|
|
|
187
187
|
# The decrypt_config method retrieves decrypted configuration data from an
|
|
188
188
|
# encrypted file
|
|
189
189
|
#
|
|
190
|
+
# @example Decrypting a configuration file
|
|
191
|
+
# provider = ComplexConfig::Provider
|
|
192
|
+
# # Note: The method automatically appends '.enc' to the provided path
|
|
193
|
+
# decrypted_yaml = provider.decrypt_config('config/database.yml')
|
|
194
|
+
# puts decrypted_yaml if decrypted_yaml
|
|
195
|
+
#
|
|
190
196
|
# @param pathname [String, Pathname] the path to the encrypted configuration
|
|
191
197
|
# file
|
|
192
198
|
#
|
|
@@ -199,6 +205,23 @@ class ComplexConfig::Provider
|
|
|
199
205
|
decrypt_config_case(pathname).first
|
|
200
206
|
end
|
|
201
207
|
|
|
208
|
+
# The decrypt_data method decrypts raw ciphertext using the provider's key
|
|
209
|
+
# source
|
|
210
|
+
#
|
|
211
|
+
# This method bypasses filesystem lookups for the encrypted data itself,
|
|
212
|
+
# allowing decryption of raw bytes. It uses the provided pathname to resolve
|
|
213
|
+
# the encryption key; if no pathname is provided, it relies on environment
|
|
214
|
+
# variables or the master key.
|
|
215
|
+
#
|
|
216
|
+
# @param ciphertext [String] the encrypted data to decrypt
|
|
217
|
+
# @param pathname [String, nil] an optional path used to locate a key file (e.g., .key)
|
|
218
|
+
# @return [String, nil] the decrypted plain text, or nil if no valid key was found
|
|
219
|
+
def decrypt_data(ciphertext, pathname = nil)
|
|
220
|
+
ks = key_source(pathname)
|
|
221
|
+
return nil unless ks && ks.key
|
|
222
|
+
ComplexConfig::Encryption.new(ks.key_bytes).decrypt(ciphertext)
|
|
223
|
+
end
|
|
224
|
+
|
|
202
225
|
# The encrypt_config method encrypts configuration data using a key source
|
|
203
226
|
#
|
|
204
227
|
# @param pathname [String, Pathname] the path to the configuration file to be
|
|
@@ -264,7 +287,7 @@ class ComplexConfig::Provider
|
|
|
264
287
|
shared = shared.to_h
|
|
265
288
|
settings.each do |key, value|
|
|
266
289
|
if value.is_a? ComplexConfig::Settings
|
|
267
|
-
value.
|
|
290
|
+
value.attributes_update_if_undefined(shared)
|
|
268
291
|
elsif value.nil?
|
|
269
292
|
settings[key] = ComplexConfig::Settings.build(nil, shared.dup)
|
|
270
293
|
end
|
|
@@ -20,7 +20,6 @@ class ComplexConfig::Settings < BasicObject
|
|
|
20
20
|
include ::Tins::AskAndSend
|
|
21
21
|
|
|
22
22
|
class << self
|
|
23
|
-
|
|
24
23
|
# The [] method converts a hash-like object into a Settings object
|
|
25
24
|
#
|
|
26
25
|
# This method serves as a convenience accessor that delegates to the
|
|
@@ -202,8 +201,6 @@ class ComplexConfig::Settings < BasicObject
|
|
|
202
201
|
#
|
|
203
202
|
# @param other [Object] the source containing attributes to merge, which can
|
|
204
203
|
# be any object that responds to to_hash or to_ary
|
|
205
|
-
#
|
|
206
|
-
# @return [void] Returns nothing
|
|
207
204
|
def attributes_update(other)
|
|
208
205
|
unless other.is_a? self.class
|
|
209
206
|
other = self.class.from_hash(other)
|
|
@@ -211,19 +208,19 @@ class ComplexConfig::Settings < BasicObject
|
|
|
211
208
|
@table.update(other.table)
|
|
212
209
|
end
|
|
213
210
|
|
|
214
|
-
# The
|
|
215
|
-
# another source, updating only
|
|
211
|
+
# The attributes_update_if_undefined method merges configuration attributes from
|
|
212
|
+
# another source, updating only those that are completely missing (undefined)
|
|
213
|
+
# in this settings object.
|
|
216
214
|
#
|
|
217
|
-
# This
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
215
|
+
# This is specifically used for shared settings patterns where environment-specific
|
|
216
|
+
# configurations must be respected. Because it only updates missing keys, any
|
|
217
|
+
# attribute explicitly set—including an explicit `nil` value intended to disable
|
|
218
|
+
# a feature or clear a default—will be preserved and not overwritten by the
|
|
219
|
+
# provided shared configuration source.
|
|
221
220
|
#
|
|
222
221
|
# @param other [Object] the source containing attributes to merge, which can
|
|
223
222
|
# be any object that responds to to_hash or to_ary
|
|
224
|
-
|
|
225
|
-
# @return [void] Returns nothing
|
|
226
|
-
def attributes_update_if_nil(other)
|
|
223
|
+
def attributes_update_if_undefined(other)
|
|
227
224
|
unless other.is_a? self.class
|
|
228
225
|
other = self.class.from_hash(other)
|
|
229
226
|
end
|
|
@@ -232,6 +229,22 @@ class ComplexConfig::Settings < BasicObject
|
|
|
232
229
|
end
|
|
233
230
|
end
|
|
234
231
|
|
|
232
|
+
# The attributes_update_if_nil does the same attributes_update_if_undefined
|
|
233
|
+
# does.
|
|
234
|
+
#
|
|
235
|
+
# @deprecated Use #attributes_update_if_undefined instead. This method is kept
|
|
236
|
+
# for backward compatibility but now delegates to #attributes_update_if_undefined
|
|
237
|
+
# to ensure explicit `nil` overrides are respected.
|
|
238
|
+
#
|
|
239
|
+
# @param other [Object] the source containing attributes to merge, which can
|
|
240
|
+
# be any object that responds to to_hash or to_ary
|
|
241
|
+
def attributes_update_if_nil(other)
|
|
242
|
+
warn '[DEPRECATION] `%{method}` is deprecated. Please use `%{new_method}` instead.' % {
|
|
243
|
+
method: __method__, new_method: :attributes_update_if_undefined
|
|
244
|
+
}
|
|
245
|
+
attributes_update_if_undefined(other)
|
|
246
|
+
end
|
|
247
|
+
|
|
235
248
|
# The replace_attributes method replaces all attributes with those from the
|
|
236
249
|
# provided hash
|
|
237
250
|
#
|
|
@@ -447,7 +460,6 @@ class ComplexConfig::Settings < BasicObject
|
|
|
447
460
|
# string representation for display purposes
|
|
448
461
|
#
|
|
449
462
|
# @param q [PrettyPrint] the pretty printer object to use for formatting
|
|
450
|
-
# @return [void] Returns nothing
|
|
451
463
|
def pretty_print(q)
|
|
452
464
|
q.text inspect
|
|
453
465
|
end
|
|
@@ -599,9 +611,6 @@ class ComplexConfig::Settings < BasicObject
|
|
|
599
611
|
end
|
|
600
612
|
|
|
601
613
|
# The skip method raises a skip exception to bypass plugin execution
|
|
602
|
-
#
|
|
603
|
-
# @return [void] This method always raises a :skip exception and never
|
|
604
|
-
# returns normally
|
|
605
614
|
def skip
|
|
606
615
|
throw :skip
|
|
607
616
|
end
|
|
@@ -165,6 +165,18 @@ describe ComplexConfig::Provider do
|
|
|
165
165
|
ENV['RAILS_MASTER_KEY'] = nil
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
it 'can decrypt from pathname returning content' do
|
|
169
|
+
described_class.key = key
|
|
170
|
+
expect(described_class.key).to eq key.chomp
|
|
171
|
+
decrypted = described_class.decrypt_config(asset('without-key-file.yml'))
|
|
172
|
+
expect(decrypted).to eq <<~EOT
|
|
173
|
+
---
|
|
174
|
+
:development:
|
|
175
|
+
:foo:
|
|
176
|
+
:bar: baz
|
|
177
|
+
EOT
|
|
178
|
+
end
|
|
179
|
+
|
|
168
180
|
it 'can read when key is stored in file' do
|
|
169
181
|
expect(described_class['with-key-file'].development.foo.bar).to eq 'baz'
|
|
170
182
|
end
|
|
@@ -209,4 +209,53 @@ EOT
|
|
|
209
209
|
expect(settings.foo.replaced).to eq true
|
|
210
210
|
expect(settings.foo).to eq result
|
|
211
211
|
end
|
|
212
|
+
|
|
213
|
+
it 'can update attributes from a hash' do
|
|
214
|
+
settings.foo.attributes_update(updated: 'yes', baz: 'new')
|
|
215
|
+
expect(settings.foo.updated).to eq 'yes'
|
|
216
|
+
expect(settings.foo.baz).to eq 'new'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'can update attributes from another Settings object' do
|
|
220
|
+
other = described_class[val: 'new']
|
|
221
|
+
settings.foo.attributes_update(other)
|
|
222
|
+
expect(settings.foo.val).to eq 'new'
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'can update attributes only if they are missing (attributes_update_if_undefined)' do
|
|
226
|
+
# Current settings.foo has { bar: { baz: true }, nix: nil, qux: 'quux' }
|
|
227
|
+
updates = {
|
|
228
|
+
nix: 'filled',
|
|
229
|
+
qux: 'should_stay_quux',
|
|
230
|
+
new_key: 'added'
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
settings.foo.attributes_update_if_undefined(updates)
|
|
234
|
+
|
|
235
|
+
expect(settings.foo.nix).to be_nil
|
|
236
|
+
expect(settings.foo.qux).to eq 'quux' # Key exists and is not nil, should NOT be updated
|
|
237
|
+
expect(settings.foo.new_key).to eq 'added' # Key missing, should be added
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'does not overwrite existing values with attributes_update_if_undefined' do
|
|
241
|
+
s = described_class.new(existing: 'keep')
|
|
242
|
+
updates = { existing: 'change', other: 'add' }
|
|
243
|
+
|
|
244
|
+
s.attributes_update_if_undefined(updates)
|
|
245
|
+
|
|
246
|
+
expect(s.existing).to eq 'keep'
|
|
247
|
+
expect(s.other).to eq 'add'
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'calls attributes_update_if_undefined and warns when using deprecated attributes_update_if_nil' do
|
|
251
|
+
s = described_class.new(existing: 'keep')
|
|
252
|
+
updates = { existing: 'change', other: 'add' }
|
|
253
|
+
|
|
254
|
+
expect {
|
|
255
|
+
s.attributes_update_if_nil(updates)
|
|
256
|
+
}.to output(/DEPRECATION.*attributes_update_if_undefined/).to_stderr
|
|
257
|
+
|
|
258
|
+
expect(s.existing).to eq 'keep'
|
|
259
|
+
expect(s.other).to eq 'add'
|
|
260
|
+
end
|
|
212
261
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: complex_config
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Florian Frank
|
|
@@ -13,16 +13,16 @@ dependencies:
|
|
|
13
13
|
name: gem_hadar
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 2.17.1
|
|
19
19
|
type: :development
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 2.17.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rake
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -203,6 +203,7 @@ extra_rdoc_files:
|
|
|
203
203
|
- lib/complex_config/tree.rb
|
|
204
204
|
- lib/complex_config/version.rb
|
|
205
205
|
files:
|
|
206
|
+
- ".github/workflows/static.yml"
|
|
206
207
|
- CHANGES.md
|
|
207
208
|
- Gemfile
|
|
208
209
|
- LICENSE
|
|
@@ -269,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
269
270
|
- !ruby/object:Gem::Version
|
|
270
271
|
version: '0'
|
|
271
272
|
requirements: []
|
|
272
|
-
rubygems_version: 4.0.
|
|
273
|
+
rubygems_version: 4.0.10
|
|
273
274
|
specification_version: 4
|
|
274
275
|
summary: configuration library
|
|
275
276
|
test_files:
|