ravioli 0.1.2 → 0.1.6
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/README.md +29 -30
- data/lib/ravioli.rb +1 -1
- data/lib/ravioli/builder.rb +54 -26
- data/lib/ravioli/{engine.rb → railtie.rb} +8 -9
- data/lib/ravioli/staging_inquirer.rb +26 -0
- data/lib/ravioli/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0f1dc00885f0dfdf291b2e81d158f093808d5200c062ebec79a534332b88f9b
|
4
|
+
data.tar.gz: 6cf7a6e99487f216c3ca1ca0de08d57e7b2b0b2b7a2a5b69c7a23ee58912297b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32c99eb0bd607e0ea8a700397edf49db62079b03c5e54684e4f53302abafdbbb368a0cfc014ccffd2e67f2a2beff2b6110c65cda437c620267be3af8e0389587
|
7
|
+
data.tar.gz: aa552b5b5f826d14fb483b9af2c56de0003fca8d7500dcc97ba842568c91be7b01dfe77bea53ef5775b278ca338913a855a08d0c5a84fb8b4f1a663ea9aa5ee9
|
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# Ravioli.rb 🍝
|
2
2
|
|
3
|
-
|
4
3
|
**Grab a fork and twist your configuration spaghetti in a single, delicious dumpling!**
|
5
4
|
|
6
5
|
Ravioli combines all of your app's runtime configuration into a unified, simple interface. **It combines YAML or JSON configuration files, encrypted Rails credentials, and ENV vars into one easy-to-consume interface** so you can focus on writing code and not on where configuration comes from.
|
@@ -217,7 +216,7 @@ Ravioli will then check for [encrypted credentials](https://guides.rubyonrails.o
|
|
217
216
|
2. Then, it loads and applies `config/credentials/RAILS_ENV.yml.enc` over top of what it has already loaded
|
218
217
|
3. Finally, IF `Rails.config.staging?` IS TRUE, it loads and applies `config/credentials/staging.yml.enc`
|
219
218
|
|
220
|
-
This allows you to use your secure credentials stores without duplicating information; you can simply layer environment-specific values over top of
|
219
|
+
This allows you to use your secure credentials stores without duplicating information; you can simply layer environment-specific values over top of a "root" `config/credentials.yml.enc` file.
|
221
220
|
|
222
221
|
### All put together, it does this:
|
223
222
|
|
@@ -418,47 +417,47 @@ staging:
|
|
418
417
|
|
419
418
|
### Encryption keys in ENV
|
420
419
|
|
421
|
-
|
422
|
-
|
423
|
-
<table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody><tr><td>
|
424
|
-
|
425
|
-
`config/credentials.yml.enc`
|
426
|
-
|
427
|
-
</td><td>
|
428
|
-
|
429
|
-
`ENV["RAILS_BASE_KEY"]`
|
420
|
+
Here are a few facts about credentials in Rails and how they're deployed:
|
430
421
|
|
431
|
-
|
422
|
+
1. Rails assumes you want to use the file that matches your environment, if it exists (e.g. `RAILS_ENV=production` will look for `config/credentials/production.yml.enc`)
|
423
|
+
2. Rails does _not_ support environment-specfic keys, but it _does_ now aggressively loads credentials at boot time.
|
432
424
|
|
433
|
-
`
|
425
|
+
**This means `RAILS_MASTER_KEY` MUST be the decryption key for your environment-specific credential file, if one exists.**
|
434
426
|
|
435
|
-
|
427
|
+
But, because Ravioli merges environment-specific credentials over top of the root credentials file, you'll need to provide encryption keys for two (or, if you have a staging setup, three) different files in ENV vars. As such, Ravioli looks for decryption keys in a way that mirrors Rails' assumptions, but allows progressive layering of credentials.
|
436
428
|
|
437
|
-
|
429
|
+
Here are a few examples
|
438
430
|
|
439
|
-
</
|
431
|
+
<table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody>
|
440
432
|
|
441
|
-
|
433
|
+
<tr>
|
434
|
+
<td><code>config/credentials.yml.enc</code></td>
|
435
|
+
<td><code>ENV["RAILS_MASTER_KEY"]</code></td>
|
436
|
+
<td><code>ENV["RAILS_ROOT_KEY"]</code></td>
|
437
|
+
</tr>
|
442
438
|
|
443
|
-
|
439
|
+
<tr>
|
440
|
+
<td><code>config/credentials/#{RAILS_ENV}.yml.enc</code></td>
|
441
|
+
<td><code>ENV["RAILS_MASTER_KEY"]</code></td>
|
442
|
+
<td><code>ENV["RAILS_#{RAILS_ENV}_KEY"]</code></td>
|
443
|
+
</tr>
|
444
444
|
|
445
|
-
`ENV["RAILS_MASTER_KEY"]`
|
446
445
|
|
447
|
-
|
446
|
+
<tr>
|
447
|
+
<td><code>config/credentials/staging.yml.enc</code></td>
|
448
|
+
<td><code>ENV["RAILS_MASTER_KEY"]</code></td>
|
449
|
+
<td><code>ENV["RAILS_STAGING_KEY"]</code></td>
|
450
|
+
</tr>
|
448
451
|
|
449
|
-
|
452
|
+
</tbody></table>
|
450
453
|
|
451
|
-
|
452
|
-
|
453
|
-
`ENV["RAILS_STAGING_KEY"]`
|
454
|
-
|
455
|
-
</td><td>
|
456
|
-
|
457
|
-
`ENV["RAILS_MASTER_KEY"]`
|
454
|
+
Credentials are loaded in that order, too, so that you can have a base setup on `config/credentials.yml.enc`, overlay that with production-specific stuff from `config/credentials/production.yml.enc`, and then short-circuit or redirect some stuff in `config/credentials/staging.yml.enc` for staging environments.
|
458
455
|
|
459
|
-
|
456
|
+
#### TLDR:
|
460
457
|
|
461
|
-
|
458
|
+
1. Set `RAILS_MASTER_KEY` to the key for your specific environment
|
459
|
+
2. Set `RAILS_STAGING_KEY` to the key for your staging credentials (if deploying to staging AND you have staging-specific credentials)
|
460
|
+
3. Set `RAILS_ROOT_KEY` to the key for your root credentials (if you have anything in `config/credentials.yml.enc`)
|
462
461
|
|
463
462
|
## License
|
464
463
|
|
data/lib/ravioli.rb
CHANGED
data/lib/ravioli/builder.rb
CHANGED
@@ -81,15 +81,6 @@ module Ravioli
|
|
81
81
|
def add_staging_flag!(is_staging = Rails.env.production? && ENV["STAGING"].present?)
|
82
82
|
is_staging = is_staging.present?
|
83
83
|
configuration.staging = is_staging
|
84
|
-
Rails.env.class_eval <<-EOC, __FILE__, __LINE__ + 1
|
85
|
-
def staging?
|
86
|
-
config = Rails.try(:config)
|
87
|
-
return false unless config&.is_a?(Ravioli::Configuration)
|
88
|
-
|
89
|
-
config.staging?
|
90
|
-
end
|
91
|
-
EOC
|
92
|
-
is_staging
|
93
84
|
end
|
94
85
|
|
95
86
|
# Iterates through the config directory (including nested folders) and
|
@@ -104,14 +95,28 @@ module Ravioli
|
|
104
95
|
|
105
96
|
# Loads Rails encrypted credentials that it can. Checks for corresponding private key files, or ENV vars based on the {Ravioli::Builder credentials preadmlogic}
|
106
97
|
def auto_load_credentials!
|
107
|
-
# Load the
|
108
|
-
load_credentials(
|
109
|
-
|
110
|
-
|
111
|
-
|
98
|
+
# Load the root config (supports using the master key or `RAILS_ROOT_KEY`)
|
99
|
+
load_credentials(
|
100
|
+
key_path: "config/master.key",
|
101
|
+
env_names: %w[master root],
|
102
|
+
)
|
103
|
+
|
104
|
+
# Load any environment-specific configuration on top of it. Since Rails will try
|
105
|
+
# `RAILS_MASTER_KEY` from the environment, we assume the same
|
106
|
+
load_credentials(
|
107
|
+
"config/credentials/#{Rails.env}",
|
108
|
+
key_path: "config/credentials/#{Rails.env}.key",
|
109
|
+
env_names: ["master"],
|
110
|
+
)
|
112
111
|
|
113
112
|
# Apply staging configuration on top of THAT, if need be
|
114
|
-
|
113
|
+
if configuration.staging?
|
114
|
+
load_credentials(
|
115
|
+
"config/credentials/staging",
|
116
|
+
env_names: %w[staging master],
|
117
|
+
key_path: "config/credentials/staging.key",
|
118
|
+
)
|
119
|
+
end
|
115
120
|
end
|
116
121
|
|
117
122
|
# When the builder is done working, lock the configuration and return it
|
@@ -140,11 +145,30 @@ module Ravioli
|
|
140
145
|
end
|
141
146
|
|
142
147
|
# Load secure credentials using a key either from a file or the ENV
|
143
|
-
def load_credentials(path = "credentials", key_path: path,
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
+
def load_credentials(path = "credentials", key_path: path, env_names: path.split("/").last)
|
149
|
+
error = nil
|
150
|
+
env_names = Array(env_names).map { |env_name| parse_env_name(env_name) }
|
151
|
+
env_names.each do |env_name|
|
152
|
+
credentials = parse_credentials(path, env_name: env_name, key_path: key_path)
|
153
|
+
if credentials.present?
|
154
|
+
configuration.append(credentials)
|
155
|
+
return credentials
|
156
|
+
end
|
157
|
+
rescue => e
|
158
|
+
error = e
|
159
|
+
end
|
160
|
+
|
161
|
+
if error
|
162
|
+
attempted_names = ["key file `#{key_path}'"]
|
163
|
+
attempted_names.push(*env_names.map { |env_name| "`ENV[\"#{env_name}\"]'" })
|
164
|
+
attempted_names = attempted_names.to_sentence(two_words_connector: " or ", last_word_connector: ", or ")
|
165
|
+
warn(
|
166
|
+
"Could not decrypt `#{path}.yml.enc' with #{attempted_names}",
|
167
|
+
error,
|
168
|
+
critical: false,
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
148
172
|
{}
|
149
173
|
end
|
150
174
|
|
@@ -233,13 +257,17 @@ module Ravioli
|
|
233
257
|
{name => config}
|
234
258
|
end
|
235
259
|
|
260
|
+
def parse_env_name(env_name)
|
261
|
+
env_name = env_name.to_s
|
262
|
+
env_name.match?(/^RAILS_/) ? env_name : "RAILS_#{env_name.upcase}_KEY"
|
263
|
+
end
|
264
|
+
|
236
265
|
def parse_credentials(path, key_path: path, env_name: path.split("/").last)
|
237
266
|
@current_credentials = path
|
238
|
-
env_name = env_name
|
239
|
-
env_name = "RAILS_#{env_name.upcase}_KEY" unless env_name.upcase == env_name
|
267
|
+
env_name = parse_env_name(env_name)
|
240
268
|
key_path = path_to_config_file_path(key_path, extnames: "key", quiet: true)
|
241
|
-
options = {key_path: key_path}
|
242
|
-
options[:env_key] = ENV[env_name].present? ? env_name : SecureRandom.hex(6)
|
269
|
+
options = {key_path: key_path.to_s}
|
270
|
+
options[:env_key] = ENV[env_name].present? ? env_name : "__RAVIOLI__#{SecureRandom.hex(6)}"
|
243
271
|
|
244
272
|
path = path_to_config_file_path(path, extnames: "yml.enc")
|
245
273
|
(Rails.application.encrypted(path, **options)&.config || {}).tap do
|
@@ -255,7 +283,7 @@ module Ravioli
|
|
255
283
|
def parse_yaml_config_file(path)
|
256
284
|
contents = File.read(path)
|
257
285
|
erb = ERB.new(contents).tap { |renderer| renderer.filename = path.to_s }
|
258
|
-
YAML.safe_load(erb.result, aliases: true)
|
286
|
+
YAML.safe_load(erb.result, [Symbol], aliases: true)
|
259
287
|
end
|
260
288
|
|
261
289
|
def path_to_config_file_path(path, extnames: EXTNAMES, quiet: false)
|
@@ -288,7 +316,7 @@ module Ravioli
|
|
288
316
|
if @strict && critical
|
289
317
|
raise BuildError.new(message, error)
|
290
318
|
else
|
291
|
-
Rails.logger.warn
|
319
|
+
Rails.logger.try(:warn, message) if defined? Rails
|
292
320
|
$stderr.write message # rubocop:disable Rails/Output
|
293
321
|
end
|
294
322
|
end
|
@@ -1,15 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
class Engine < ::Rails::Engine
|
5
|
-
# Bootstrap Ravioli onto the Rails app
|
6
|
-
initializer "ravioli", before: :load_environment_config do |app|
|
7
|
-
Rails.extend Ravioli::RailsConfig unless Rails.respond_to?(:config)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
3
|
+
require_relative "./staging_inquirer"
|
12
4
|
|
5
|
+
module Ravioli
|
13
6
|
module RailsConfig
|
14
7
|
def config
|
15
8
|
Ravioli.default || Ravioli.build(namespace: Rails.application&.class&.module_parent, strict: Rails.env.production?) do |config|
|
@@ -19,4 +12,10 @@ module Ravioli
|
|
19
12
|
end
|
20
13
|
end
|
21
14
|
end
|
15
|
+
|
16
|
+
class Railtie < ::Rails::Railtie
|
17
|
+
# Bootstrap Ravioli onto the Rails app
|
18
|
+
Rails.env.class.prepend Ravioli::StagingInquirer
|
19
|
+
Rails.extend Ravioli::RailsConfig unless Rails.respond_to?(:config)
|
20
|
+
end
|
22
21
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ravioli
|
4
|
+
# A module that we mix in to the `Rails.env` inquirer class to add some extra staging-related
|
5
|
+
# metadata
|
6
|
+
module StagingInquirer
|
7
|
+
# Add a `name` method to `Rails.env` that will return "staging" for staging environments, and
|
8
|
+
# otherwise the string's value
|
9
|
+
def name
|
10
|
+
staging? ? "staging" : to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add a `strict:` keyword to reduce `Rails.env.production && !Rails.env.staging` calls
|
14
|
+
def production?(strict: false)
|
15
|
+
is_production = super()
|
16
|
+
return is_production unless strict && is_production
|
17
|
+
|
18
|
+
is_production && !staging?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Override staging inquiries to check against the current configuration
|
22
|
+
def staging?
|
23
|
+
Rails.try(:config)&.staging?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ravioli/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ravioli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Flip Sasser
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -270,7 +270,8 @@ files:
|
|
270
270
|
- lib/ravioli.rb
|
271
271
|
- lib/ravioli/builder.rb
|
272
272
|
- lib/ravioli/configuration.rb
|
273
|
-
- lib/ravioli/
|
273
|
+
- lib/ravioli/railtie.rb
|
274
|
+
- lib/ravioli/staging_inquirer.rb
|
274
275
|
- lib/ravioli/version.rb
|
275
276
|
homepage: https://github.com/flipsasser/ravioli
|
276
277
|
licenses:
|