ravioli 0.1.3 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 572198c31192d87b63d13d1e672813f7c9e37c9cc3a9f9e17622b5f86c882b34
4
- data.tar.gz: 626a0d7ec6f697f00fabc011884fefec7fed2c0a55141638f3096a657fcb0a82
3
+ metadata.gz: a42c50f1ae58ea21c70956c3a2fda57084dc20042efc3e163c26b80a8ecc8c58
4
+ data.tar.gz: 2fafc358079f1bab16f25e36563dc5f843fedd7d85e32b8e34ca337b2c3c23ee
5
5
  SHA512:
6
- metadata.gz: cfb0f8416dd26cb6150a6d0c3cb93a908fd75d7571ccbccc138f5141ff5b888d75a90cfe9dd3438748519f6078ba300875600aa0b3f7cb980ab426c3e2223330
7
- data.tar.gz: e1316a4be7b03f9825f7324f70798c2c41ce937b04da209fc2df59a3fa4d1730efb94de12f74b53636ef6069faa27564bc9388951b7ed29f26b1ab73ed4f1400
6
+ metadata.gz: bf02967cc19dfba1c24a0db7da932999896c278635a8be0c45d6966d4d9e76d2517290770a8d608842b4978beb9c05621b532023ffd2bbd8eed484155c3327b8
7
+ data.tar.gz: 77bdfbaeb3210f7033e9837d7c52003118fc45edaec020595276f455260cb13a186c1c1239c3bf96e5e4965acb3c3f1b250fc0766e1f5d6957c89d662c633d5e
data/README.md CHANGED
@@ -216,7 +216,7 @@ Ravioli will then check for [encrypted credentials](https://guides.rubyonrails.o
216
216
  2. Then, it loads and applies `config/credentials/RAILS_ENV.yml.enc` over top of what it has already loaded
217
217
  3. Finally, IF `Rails.config.staging?` IS TRUE, it loads and applies `config/credentials/staging.yml.enc`
218
218
 
219
- 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.
220
220
 
221
221
  ### All put together, it does this:
222
222
 
@@ -417,47 +417,47 @@ staging:
417
417
 
418
418
  ### Encryption keys in ENV
419
419
 
420
- 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 fallback-specific way. Here's where it looks for each file:
420
+ Here are a few facts about credentials in Rails and how they're deployed:
421
421
 
422
- <table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody><tr><td>
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.
423
424
 
424
- `config/credentials.yml.enc`
425
+ **This means `RAILS_MASTER_KEY` MUST be the decryption key for your environment-specific credential file, if one exists.**
425
426
 
426
- </td><td>
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.
427
428
 
428
- `ENV["RAILS_BASE_KEY"]`
429
+ Here are a few examples
429
430
 
430
- </td><td>
431
+ <table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody>
431
432
 
432
- `ENV["RAILS_MASTER_KEY"]`
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>
433
438
 
434
- </td></tr><tr><td>
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>
435
444
 
436
- `config/credentials/production.yml.enc`
437
445
 
438
- </td><td>
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>
439
451
 
440
- `ENV["RAILS_PRODUCTION_KEY"]`
452
+ </tbody></table>
441
453
 
442
- </td><td>
443
-
444
- `ENV["RAILS_MASTER_KEY"]`
445
-
446
- </td></tr><tr><td>
447
-
448
- `config/credentials/staging.yml.enc` (only if running on staging)
449
-
450
- </td><td>
451
-
452
- `ENV["RAILS_STAGING_KEY"]`
453
-
454
- </td><td>
455
-
456
- `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.
457
455
 
458
- </td></tr></tbody></table>
456
+ #### TLDR:
459
457
 
460
- 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
+ 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`)
461
461
 
462
462
  ## License
463
463
 
data/lib/ravioli.rb CHANGED
@@ -40,4 +40,4 @@ module Ravioli
40
40
  end
41
41
  end
42
42
 
43
- require_relative "ravioli/engine" if defined?(Rails)
43
+ require_relative "ravioli/railtie" if defined?(Rails)
@@ -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 base config
108
- load_credentials(key_path: "config/master.key", env_name: "base")
109
-
110
- # Load any environment-specific configuration on top of it
111
- load_credentials("config/credentials/#{Rails.env}", key_path: "config/credentials/#{Rails.env}.key", env_name: "master")
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
- load_credentials("config/credentials/staging", key_path: "config/credentials/staging.key") if configuration.staging?
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, env_name: path.split("/").last)
144
- credentials = parse_credentials(path, env_name: env_name, key_path: key_path)
145
- configuration.append(credentials) if credentials.present?
146
- rescue => error
147
- warn "Could not decrypt `#{path}.yml.enc' with key file `#{key_path}' or `ENV[\"#{env_name}\"]'", error, critical: false
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
 
@@ -189,7 +213,7 @@ module Ravioli
189
213
  @reload_credentials.add(@current_credentials)
190
214
  end
191
215
 
192
- configuration.send(*args, &block)
216
+ configuration.fetch_env_key_for(args) { configuration.send(*args, &block) }
193
217
  end
194
218
  # rubocop:enable Style/MissingRespondToMissing
195
219
  # rubocop:enable Style/MethodMissingSuper
@@ -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.to_s
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
@@ -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(message) if defined? Rails
319
+ Rails.logger.try(:warn, message) if defined? Rails
292
320
  $stderr.write message # rubocop:disable Rails/Output
293
321
  end
294
322
  end
@@ -45,6 +45,11 @@ module Ravioli
45
45
  dig(*keys) || yield
46
46
  end
47
47
 
48
+ def fetch_env_key_for(keys, &block)
49
+ env_key = key_path_for(keys).join("_").upcase
50
+ ENV.fetch(env_key, &block)
51
+ end
52
+
48
53
  def pretty_print(printer = nil)
49
54
  table.pretty_print(printer)
50
55
  end
@@ -80,11 +85,6 @@ module Ravioli
80
85
  end
81
86
  end
82
87
 
83
- def fetch_env_key_for(keys, &block)
84
- env_key = key_path_for(keys).join("_").upcase
85
- ENV.fetch(env_key, &block)
86
- end
87
-
88
88
  def key_path_for(keys)
89
89
  Array(key_path) + Array(keys)
90
90
  end
@@ -1,15 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Ravioli
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ravioli
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.7"
5
5
  end
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.3
4
+ version: 0.1.7
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-06-28 00:00:00.000000000 Z
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/engine.rb
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: