berkeley_library-docker 0.1.1 → 0.2.0.pre.rc2

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: 75df4187f362680f782f8ce0e4d7a5aa438c4f86f44d1337c0495253e1b4733a
4
- data.tar.gz: 51fdf2f4e9f29080bda4d7fe93096c0d314def1649f6a7da70290324ca7f642a
3
+ metadata.gz: 5fccf11c3452690216d316ab64be7b2e57dc82a51b9d384ec9b1feb0419f7947
4
+ data.tar.gz: dad729f07dcdc468b82d2ff72d10cfc19899bbea8ef67d06158c1f94722cb0ae
5
5
  SHA512:
6
- metadata.gz: 24714d86ebb5416d303acca6d31f46e1d3e4c7514afbf2fcee166638c7ba25c64cbc5e1d4fdbc49e97babc5568198d32c09909ab1bc43c0652c56a3140a9ff98
7
- data.tar.gz: 31b41f95fe42a25a725c9d8702d00e4a922c41afb8df1b9503ed3efab626a206511ffbe69e925c8d26a0c6779afbb977c4427c4c81ced59e4edb1273c9baced2
6
+ metadata.gz: 3fcb0b7e40466acef102e32c380db573b7463123d91b5673961654fa449e77a39fcf6f5fce75d9b6d1ab97ce048d7398a90cc431dd02a4b6fffac6377d493d35
7
+ data.tar.gz: 9a27223955e09e2d76eeecde8b3958821b749676d3d948508a89cb24c4c03b4bf432b2b94b58acfcf6c7208f462a30951ad062bd0e91326ab5520e94f1fa8e8f
data/README.md CHANGED
@@ -7,14 +7,20 @@ require 'berkeley_library/docker'
7
7
  BerkeleyLibrary::Docker::Secret.load_secrets!
8
8
  ```
9
9
 
10
- Secrets are loaded from `/run/secrets` by default. You can override this by passing another path (or glob pattern) to the `load_secrets!` method, or by setting the `UCBLIB_SECRETS_PATH` environment variable. For example:
10
+ ---
11
+
12
+ > **Note on (Rails) load order:** This gem loads the environment using Rails' `before_configuration` hook, a la the [dotenv](https://github.com/bkeepers/dotenv#note-on-load-order) gem. Manually call `BerkeleyLibrary::Docker::Secret.load_secrets!` if you need to load secrets earlier.
13
+
14
+ ---
15
+
16
+ Secrets are loaded from `/run/secrets` by default. You can override this by passing another path (or glob pattern) to the `load_secrets!` method, or by setting the `UCBLIB_SECRETS_PATTERN` environment variable. For example:
11
17
 
12
18
  ```ruby
13
19
  # Load any file under /tmp/secrets
14
20
  BerkeleyLibrary::Docker::Secret.load_secrets! '/tmp/secrets/**/*'
15
21
 
16
22
  # Load only a specific file
17
- ENV['UCBLIB_SECRETS_PATH'] = '/run/secrets/DB_PASSWORD'
23
+ ENV['UCBLIB_SECRETS_PATTERN'] = '/run/secrets/DB_PASSWORD'
18
24
  BerkeleyLibrary::Docker::Secret.load_secrets!
19
25
  ```
20
26
 
@@ -7,7 +7,7 @@ module BerkeleyLibrary
7
7
  SUMMARY = 'Utility functions for Dockerizing Ruby apps'.freeze
8
8
  DESCRIPTION = 'Utility functions for making Ruby apps "just work" in Docker containers.'.freeze
9
9
  LICENSE = 'MIT'.freeze
10
- VERSION = '0.1.1'.freeze
10
+ VERSION = '0.2.0-rc2'.freeze
11
11
  HOMEPAGE = 'https://github.com/BerkeleyLibrary/docker'.freeze
12
12
 
13
13
  private_class_method :new
@@ -2,10 +2,28 @@ require 'berkeley_library/docker/secret'
2
2
 
3
3
  module BerkeleyLibrary
4
4
  module Docker
5
- class Railtie < Rails::Railtie
6
- NAME = 'berkeley_library-docker.load_secrets'.freeze
5
+ class Railtie < ::Rails::Railtie
6
+ include Logging
7
7
 
8
- initializer(NAME, after: :initialize_logger) { Secret.load_secrets! }
8
+ def secrets
9
+ @secrets ||= {}
10
+ end
11
+
12
+ def load_secrets!
13
+ @secrets = Secret.load_secrets!
14
+ end
15
+
16
+ private
17
+
18
+ config.before_configuration { load_secrets! }
19
+
20
+ # @note Logging occurs later because the Rails logger, which we use to
21
+ # bootstrap ours, isn't initialized until after `before_configuration`.
22
+ initializer 'berkeley_library-docker.log_loaded_secrets' do
23
+ secrets.each do |_, secret|
24
+ log_info "Loaded secret: #{secret.inspect}"
25
+ end
26
+ end
9
27
  end
10
28
  end
11
29
  end
@@ -1,4 +1,6 @@
1
1
  require 'berkeley_library/docker/logging'
2
+ require 'digest'
3
+ require 'time'
2
4
 
3
5
  module BerkeleyLibrary
4
6
  module Docker
@@ -6,31 +8,46 @@ module BerkeleyLibrary
6
8
  class << self
7
9
  include Logging
8
10
 
9
- PATH_OVERRIDE_ENVVAR = 'UCBLIB_SECRETS_PATH'
10
- DEFAULT_SECRETS_PATH = '/run/secrets'
11
+ PATH_OVERRIDE_ENVVAR = 'UCBLIB_SECRETS_PATTERN'
12
+ DEFAULT_SECRETS_PATTERN = '/run/secrets/*'
11
13
 
12
- def load_secrets!(glob = nil)
13
- files_from(glob).each(&method(:load_secret!))
14
+ def load_secrets!(glob = nil, reload = false)
15
+ glob = normalize_glob(glob)
16
+
17
+ files_from(glob).each_with_object({}) do |path, new_secrets|
18
+ secret_name = File.basename(path)
19
+ secret_value = File.read(path).strip
20
+
21
+ ENV[secret_name] = secret_value
22
+
23
+ new_secrets[secret_name] = {
24
+ name: secret_name,
25
+ file: path,
26
+ checksum: "sha256:#{Digest::SHA256.hexdigest(secret_value)}",
27
+ glob: glob,
28
+ timestamp: Time.now.to_i,
29
+ }
30
+ end
14
31
  end
15
32
 
16
33
  private
17
34
 
18
- def load_secret!(filepath)
19
- secret = File.basename(filepath)
20
- ENV[secret] = File.read(filepath).strip
21
- log_info "Loaded secret `ENV['#{secret}']` from #{filepath}"
35
+ def files_from(glob)
36
+ Dir[glob].filter_map { |fname| fname if File.file? fname }
22
37
  end
23
38
 
24
- def files_from(glob = nil)
25
- glob ||= ENV[PATH_OVERRIDE_ENVVAR] || DEFAULT_SECRETS_PATH
26
- glob = glob.strip
39
+ def normalize_glob(glob)
40
+ (glob || ENV[PATH_OVERRIDE_ENVVAR] || DEFAULT_SECRETS_PATTERN)
41
+ .then(&:strip)
42
+ .then(&method(:ensure_dir_asterisk))
43
+ end
27
44
 
28
- if File.directory?(glob) && !glob.end_with?('*')
29
- glob = File.join(glob, '*')
45
+ def ensure_dir_asterisk(glob)
46
+ if !glob.end_with?('*') && File.directory?(glob)
47
+ File.join(glob, '*')
48
+ else
49
+ glob
30
50
  end
31
-
32
- log_info "Searching '#{glob}' for secrets files"
33
- Dir[glob].filter_map { |fname| fname if File.file? fname }
34
51
  end
35
52
  end
36
53
  end
@@ -1,36 +1,36 @@
1
- require 'rails' # require Rails first to mimic load order
2
- require 'berkeley_library/docker/railtie'
3
1
  require 'spec_helper'
2
+ require 'rails'
3
+ require 'berkeley_library/docker/railtie'
4
+ require 'logger'
4
5
 
5
6
  module BerkeleyLibrary
6
7
  module Docker
7
8
  describe Railtie do
8
- class TestApp < Rails::Application; end
9
+ # @note Simply defining the app class causes railtie
10
+ # `before_configuration` blocks to run, and we only get to define one
11
+ # application, hence this one test needs to handle everything.
12
+ it 'causes rails to load the environment' do
13
+ with_secret('FOOBAR', 'BAZ') do
14
+ with_secret('CLIENT_KEY', 'd33db55f') do
15
+ logger = spy Logger.new(STDOUT)
9
16
 
10
- let(:app) { TestApp.create }
11
- let(:initializers) { app.initializers.tsort_each.collect(&:name) }
12
- let(:railtie_name) { BerkeleyLibrary::Docker::Railtie::NAME }
17
+ expect(ENV['CLIENT_KEY']).to be nil
13
18
 
14
- it 'has the expected initializer name' do
15
- expect(railtie_name).to eq 'berkeley_library-docker.load_secrets'
16
- end
19
+ # Use Class.new so that `logger` is within scope
20
+ @klass = Class.new(Rails::Application) do
21
+ Rails.logger = logger
22
+ config.client_key = ENV['CLIENT_KEY']
23
+ end
17
24
 
18
- it 'causes rails to load the environment' do
19
- with_secret('API_TOKEN', 'd33db55f') do
20
- app = TestApp.create
21
- expect(ENV['API_TOKEN']).to be nil
25
+ expect(ENV['CLIENT_KEY']).to eq 'd33db55f'
22
26
 
23
- app.initialize!
24
- expect(ENV['API_TOKEN']).to eq 'd33db55f'
25
- end
26
- end
27
+ app = @klass.create
28
+ app.initialize!
27
29
 
28
- it 'loads after the logger and before configuration' do
29
- railtie_index = initializers.find_index(railtie_name)
30
- logger_index = initializers.find_index(:initialize_logger)
31
- config_index = initializers.find_index(:load_config_initializers)
32
-
33
- expect(railtie_index).to be_between(logger_index, config_index)
30
+ expect(app.config.client_key).to eq 'd33db55f'
31
+ expect(logger).to have_received(:info).twice.with(/Loaded secret:.*/)
32
+ end
33
+ end
34
34
  end
35
35
  end
36
36
  end
@@ -7,42 +7,45 @@ module BerkeleyLibrary
7
7
  describe Secret do
8
8
  it 'loads secrets into the environment' do
9
9
  with_secret('DB_PASSWORD', 'f00BarbAz') do
10
- expect(ENV['DB_PASSWORD']).to be nil
11
- Secret.load_secrets!
12
- expect(ENV['DB_PASSWORD']).to eq 'f00BarbAz'
10
+ expect { Secret.load_secrets! }
11
+ .to change { ENV['DB_PASSWORD'] }
12
+ .from(nil).to('f00BarbAz')
13
13
  end
14
14
  end
15
15
 
16
16
  it 'reads multiline secrets' do
17
17
  with_secret('SSH_PRIVATE_KEY', "d33db55f\nd33db55f") do
18
- expect(ENV['SSH_PRIVATE_KEY']).to be nil
19
- Secret.load_secrets!
20
- expect(ENV['SSH_PRIVATE_KEY']).to eq "d33db55f\nd33db55f"
18
+ expect { Secret.load_secrets! }
19
+ .to change { ENV['SSH_PRIVATE_KEY'] }
20
+ .from(nil).to("d33db55f\nd33db55f")
21
21
  end
22
22
  end
23
23
 
24
24
  it 'strips trailing whitespace' do
25
25
  with_secret('API_TOKEN', "d33db55f\n") do
26
- expect(ENV['API_TOKEN']).to be nil
27
- Secret.load_secrets!
28
- expect(ENV['API_TOKEN']).to eq 'd33db55f'
26
+ expect { Secret.load_secrets! }
27
+ .to change { ENV['API_TOKEN'] }
28
+ .from(nil).to('d33db55f')
29
29
  end
30
30
  end
31
31
 
32
32
  it 'ignores subdirectories in the secrets directory' do
33
33
  FileUtils.mkdir_p(File.join(SPEC_SECRETS_PATH, 'some-dir'))
34
- expect{ Secret.load_secrets! }.not_to raise_error
34
+ expect { Secret.load_secrets! }.not_to raise_error
35
+ expect { Secret.load_secrets! }.not_to change { ENV }
35
36
  end
36
37
 
37
- it 'searches ENV["UCBLIB_SECRETS_PATH"] by default' do
38
- with_secret('MASTER_KEY', 'd33db55f', '/tmp/super-secrets') do
39
- Secret.load_secrets!
40
- expect(ENV['MASTER_KEY']).to be nil
38
+ it 'searches ENV["UCBLIB_SECRETS_PATTERN"] by default' do
39
+ secrets_subdir = File.join(SPEC_SECRETS_PATH, 'super-secrets')
41
40
 
42
- ENV['UCBLIB_SECRETS_PATH'] = '/tmp/super-secrets'
41
+ with_secret('MASTER_KEY', 'd33db55f', secrets_subdir) do
42
+ expect { Secret.load_secrets! }.not_to change { ENV }
43
43
 
44
- Secret.load_secrets!
45
- expect(ENV['MASTER_KEY']).to eq 'd33db55f'
44
+ ENV['UCBLIB_SECRETS_PATTERN'] = secrets_subdir
45
+
46
+ expect { Secret.load_secrets! }
47
+ .to change { ENV['MASTER_KEY'] }
48
+ .from(nil).to('d33db55f')
46
49
  end
47
50
  end
48
51
  end
@@ -1,6 +1,6 @@
1
+ require 'spec_helper'
1
2
  require 'rails' # require Rails first to mimic load order
2
3
  require 'berkeley_library/docker'
3
- require 'spec_helper'
4
4
 
5
5
  module BerkeleyLibrary
6
6
  describe Docker do
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require_relative './spec_utils.rb'
2
+ require 'tmpdir'
2
3
 
3
- # Avoid issues with `/run` being read-only on MacOS
4
- SPEC_SECRETS_PATH = ENV['UCBLIB_SECRETS_PATH'] = '/tmp/secrets'
4
+ # Avoid issues with `/run` being read-only on MacOS. Also ensures that
5
+ # tests always start with a clean secrets directory.
6
+ SPEC_SECRETS_PATH = ENV['UCBLIB_SECRETS_PATTERN'] = Dir.mktmpdir
5
7
 
6
8
  RSpec.configure do |config|
7
9
  config.include SpecUtils
data/spec/spec_utils.rb CHANGED
@@ -3,12 +3,12 @@ require 'fileutils'
3
3
 
4
4
  module SpecUtils
5
5
  def rollback_environment(&block)
6
- old_envvars = ENV.keys
7
-
8
- begin
9
- yield
10
- ensure
11
- ENV.select! { |k, _| old_envvars.include? k }
6
+ ENV.to_h.tap do |old_env|
7
+ begin
8
+ yield
9
+ ensure
10
+ ENV.replace(old_env)
11
+ end
12
12
  end
13
13
  end
14
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berkeley_library-docker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0.pre.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Schmidt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-14 00:00:00.000000000 Z
11
+ date: 2022-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -120,9 +120,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
120
  version: 2.7.6
121
121
  required_rubygems_version: !ruby/object:Gem::Requirement
122
122
  requirements:
123
- - - ">="
123
+ - - ">"
124
124
  - !ruby/object:Gem::Version
125
- version: '0'
125
+ version: 1.3.1
126
126
  requirements: []
127
127
  rubygems_version: 3.1.6
128
128
  signing_key: