berkeley_library-docker 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75df4187f362680f782f8ce0e4d7a5aa438c4f86f44d1337c0495253e1b4733a
4
- data.tar.gz: 51fdf2f4e9f29080bda4d7fe93096c0d314def1649f6a7da70290324ca7f642a
3
+ metadata.gz: 0df499a525ca0b96a1f8526f3103c3ad9519a121ec79592fd4986726754f96fc
4
+ data.tar.gz: c52371eb4221d3863a721d42f56f86c0b05440518b2628d772639f7e3379591b
5
5
  SHA512:
6
- metadata.gz: 24714d86ebb5416d303acca6d31f46e1d3e4c7514afbf2fcee166638c7ba25c64cbc5e1d4fdbc49e97babc5568198d32c09909ab1bc43c0652c56a3140a9ff98
7
- data.tar.gz: 31b41f95fe42a25a725c9d8702d00e4a922c41afb8df1b9503ed3efab626a206511ffbe69e925c8d26a0c6779afbb977c4427c4c81ced59e4edb1273c9baced2
6
+ metadata.gz: d8c12114df17c6783cf912616b2a67615951b7a5e9e03055fd0d01b9b2531ed5d2b34c8c8998a4a6905c3c085045b2512f21ce414ab581ba6a9e866ddacca330
7
+ data.tar.gz: e9b2aad0be73c30564ab78e9e0298d6bcf41b3cc2c01369f6c13d0b6f152d25a3822cbffc494c3e794f732332400f1c05ba102cc6a6addadffce74a78a7e26ab
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'.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
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