on_container 0.0.3 → 0.0.9
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 +5 -5
- data/README.md +65 -1
- data/lib/on_container/dev/active_record_ops.rb +1 -9
- data/lib/on_container/dev/rails.rb +5 -1
- data/lib/on_container/dev/setup_ops.rb +9 -2
- data/lib/on_container/load_env_secrets.rb +54 -0
- data/lib/on_container/ops/service_connection_checks.rb +41 -0
- data/lib/on_container/version.rb +1 -1
- data/on_container.gemspec +1 -1
- metadata +8 -14
- data/lib/on_container/step_down_from_root.rb +0 -55
- data/spec/on_container_spec.rb +0 -5
- data/spec/spec_helper.rb +0 -14
- data/spec/step_down_from_root_spec.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0b3faf030bfb28175d373fcaf06d219b8e89eae377a881e0655b0ea71a4248d9
|
4
|
+
data.tar.gz: 4b0a1a13bfea4e50a199b92caf15cf8c53361e03f4312057a0c10f7e33fa8e8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff47344c4ca8270ffc7b3aebc028aecc7bc1858bcb50016301c65c21e2ec94dfe0b4a334d18e7c4fa5722f319fd14c72808246094d707dcae15c3f5e8625d9e9
|
7
|
+
data.tar.gz: 166f0b25ea8db2af892eeeabffe15d96419d57688f9f6531d5f6fb88f7b7a9b6551a00e5049264cf6245eff9566bfa270404cd1af58fec6315593cc8b5a8fcb2
|
data/README.md
CHANGED
@@ -22,7 +22,71 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
25
|
+
### Development Routines & Docker Entrypoint Scripts
|
26
|
+
|
27
|
+
We use some routines included in this gem to create compelling development entrypoint scripts for docker development containers.
|
28
|
+
|
29
|
+
In this example, we'll be using the `on_container/dev/rails` routine bundle to create our dev entrypoint:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
#!/usr/bin/env ruby
|
33
|
+
|
34
|
+
# frozen_string_literal: true
|
35
|
+
|
36
|
+
require 'on_container/dev/rails'
|
37
|
+
|
38
|
+
set_given_or_default_command
|
39
|
+
|
40
|
+
# `on_setup_lock_acquired` prevents multiple app containers from running
|
41
|
+
# the setup process concurrently:
|
42
|
+
on_setup_lock_acquired do
|
43
|
+
ensure_project_gems_are_installed
|
44
|
+
ensure_project_node_packages_are_installed
|
45
|
+
|
46
|
+
wait_for_service_to_accept_connections 'tcp://postgres:5432'
|
47
|
+
setup_activerecord_database unless activerecord_database_ready?
|
48
|
+
|
49
|
+
remove_rails_pidfile if rails_server?
|
50
|
+
end if command_requires_setup?
|
51
|
+
|
52
|
+
execute_given_or_default_command
|
53
|
+
```
|
54
|
+
|
55
|
+
### Loading secrets into environment variables, and inserting credentials into URL environment variables
|
56
|
+
|
57
|
+
When using Docker Swarm, the secrets are loaded as files mounted into the container's filesystem.
|
58
|
+
|
59
|
+
The `on_container/load_env_secrets` runs a couple of routines that reads these files into environment variables.
|
60
|
+
|
61
|
+
For our Rails example app, we added the following line to the `config/boot.rb` file:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# frozen_string_literal: true
|
65
|
+
|
66
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
67
|
+
|
68
|
+
require 'on_container/load_env_secrets' # Load secrets injected by Kubernetes/Swarm
|
69
|
+
|
70
|
+
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
71
|
+
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
|
72
|
+
```
|
73
|
+
|
74
|
+
The `on_container/load_env_secrets` also merges any credential available in environment variables into any matching
|
75
|
+
`_URL` environment variable. For example, consider the following environment variables:
|
76
|
+
|
77
|
+
```shell
|
78
|
+
DATABASE_URL=postgres://postgres:5432/?encoding=unicode
|
79
|
+
DATABASE_USER=postgres
|
80
|
+
DATABASE_PASS=3x4mpl3P455w0rd
|
81
|
+
```
|
82
|
+
|
83
|
+
The routine will merge `DATABASE_USER` and `DATABASE_PASS` into `DATABASE_URL`:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
puts ENV['DATABASE_URL']
|
87
|
+
> postgres://postgres:3x4mpl3P455w0rd@postgres:5432/?encoding=unicode
|
88
|
+
```
|
89
|
+
|
26
90
|
|
27
91
|
## Development
|
28
92
|
|
@@ -26,15 +26,7 @@ module OnContainer
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def establish_activerecord_database_connection
|
29
|
-
unless defined?(ActiveRecord)
|
30
|
-
require 'rubygems'
|
31
|
-
require 'bundler'
|
32
|
-
|
33
|
-
Bundler.setup(:default)
|
34
|
-
|
35
|
-
require 'active_record'
|
36
|
-
end
|
37
|
-
|
29
|
+
require 'active_record' unless defined?(ActiveRecord)
|
38
30
|
ActiveRecord::Base.establish_connection activerecord_config
|
39
31
|
ActiveRecord::Base.connection_pool.with_connection { |connection| }
|
40
32
|
end
|
@@ -12,4 +12,8 @@ include OnContainer::Dev::SetupOps
|
|
12
12
|
include OnContainer::Dev::BundlerOps
|
13
13
|
include OnContainer::Dev::NodeModulesOps
|
14
14
|
include OnContainer::Dev::ActiveRecordOps
|
15
|
-
include OnContainer::Dev::ContainerCommandOps
|
15
|
+
include OnContainer::Dev::ContainerCommandOps
|
16
|
+
|
17
|
+
require 'on_container/ops/service_connection_checks'
|
18
|
+
|
19
|
+
include OnContainer::Ops::ServiceConnectionChecks
|
@@ -21,12 +21,19 @@ module OnContainer
|
|
21
21
|
puts 'Waiting for app setup to finish...'
|
22
22
|
sleep app_setup_wait
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def on_setup_lock_acquired
|
26
26
|
wait_setup while File.exist?(app_setup_lock_path)
|
27
|
-
|
27
|
+
|
28
28
|
lock_setup
|
29
|
+
|
30
|
+
%w[HUP INT QUIT TERM EXIT].each do |signal_string|
|
31
|
+
Signal.trap(signal_string) { unlock_setup }
|
32
|
+
end
|
33
|
+
|
29
34
|
yield
|
35
|
+
|
36
|
+
ensure
|
30
37
|
unlock_setup
|
31
38
|
end
|
32
39
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Reads the specified secret paths (i.e. Docker Secrets) into environment
|
4
|
+
# variables:
|
5
|
+
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/object'
|
8
|
+
|
9
|
+
# Process only a known list of env vars that can filled by reading a file (i.e.
|
10
|
+
# a docker secret):
|
11
|
+
Dir["#{ENV.fetch('SECRETS_PATH', '/run/secrets/')}*"].each do |secret_filepath|
|
12
|
+
next unless File.file?(secret_filepath)
|
13
|
+
|
14
|
+
secret_envvarname = File.basename(secret_filepath, '.*').upcase
|
15
|
+
|
16
|
+
# Skip if variable is already set - already-set variables have precedence over
|
17
|
+
# the secret files:
|
18
|
+
next if ENV.key?(secret_envvarname) && ENV[secret_envvarname].present?
|
19
|
+
|
20
|
+
ENV[secret_envvarname] = File.read(secret_filepath).strip
|
21
|
+
end
|
22
|
+
|
23
|
+
# For each *_URL environment variable where there's also a *_(USER|USERNAME) or
|
24
|
+
# *_(PASS|PASSWORD), update the URL environment variable with the given
|
25
|
+
# credentials. For example:
|
26
|
+
#
|
27
|
+
# DATABASE_URL: postgres://postgres:5432/demo_production
|
28
|
+
# DATABASE_USERNAME: lalito
|
29
|
+
# DATABASE_PASSWORD: lepass
|
30
|
+
#
|
31
|
+
# Results in the following updated DATABASE_URL:
|
32
|
+
# DATABASE_URL = postgres://lalito:lepass@postgres:5432/demo_production
|
33
|
+
require 'uri' if (url_keys = ENV.keys.select { |key| key =~ /_URL/ }).any?
|
34
|
+
|
35
|
+
url_keys.each do |url_key|
|
36
|
+
credential_pattern_string = url_key.gsub('_URL', '_(USER(NAME)?|PASS(WORD)?)')
|
37
|
+
credential_pattern = Regexp.new "\\A#{credential_pattern_string}\\z"
|
38
|
+
credential_keys = ENV.keys.select { |key| key =~ credential_pattern }
|
39
|
+
next unless credential_keys.any?
|
40
|
+
|
41
|
+
uri = URI(ENV[url_key])
|
42
|
+
|
43
|
+
credential_keys.each do |credential_key|
|
44
|
+
credential_value = URI.encode_www_form_component ENV[credential_key]
|
45
|
+
case credential_key
|
46
|
+
when /USER/ then uri.user = credential_value
|
47
|
+
when /PASS/ then uri.password = credential_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ENV[url_key] = uri.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
# STDERR.puts ENV.inspect
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module OnContainer
|
8
|
+
module Ops
|
9
|
+
module ServiceConnectionChecks
|
10
|
+
def service_accepts_connections?(service_uri, seconds_to_wait = 30)
|
11
|
+
uri = URI(service_uri)
|
12
|
+
|
13
|
+
Timeout::timeout(seconds_to_wait) do
|
14
|
+
TCPSocket.new(uri.host, uri.port).close
|
15
|
+
rescue Errno::ECONNREFUSED
|
16
|
+
retry
|
17
|
+
end
|
18
|
+
|
19
|
+
true
|
20
|
+
|
21
|
+
rescue => e
|
22
|
+
puts "Connection to #{uri.to_s} failed: '#{e.inspect}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def wait_for_service_to_accept_connections(service_uri, seconds_to_wait = 30, exit_on_fail = true)
|
26
|
+
wait_loop = Thread.new do
|
27
|
+
loop do
|
28
|
+
sleep(5)
|
29
|
+
puts "Waiting for #{service_uri} to accept connections..."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if service_accepts_connections?(service_uri, seconds_to_wait)
|
34
|
+
return wait_loop.exit
|
35
|
+
else
|
36
|
+
exit 1 if exit_on_fail
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/on_container/version.rb
CHANGED
data/on_container.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: on_container
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roberto Quintanilla
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '2.1'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '2.1'
|
27
27
|
description: A small collection of scripts and routines to help ruby development within
|
28
28
|
containers
|
29
29
|
email:
|
@@ -45,12 +45,10 @@ files:
|
|
45
45
|
- lib/on_container/dev/rails.rb
|
46
46
|
- lib/on_container/dev/rails_ops.rb
|
47
47
|
- lib/on_container/dev/setup_ops.rb
|
48
|
-
- lib/on_container/
|
48
|
+
- lib/on_container/load_env_secrets.rb
|
49
|
+
- lib/on_container/ops/service_connection_checks.rb
|
49
50
|
- lib/on_container/version.rb
|
50
51
|
- on_container.gemspec
|
51
|
-
- spec/on_container_spec.rb
|
52
|
-
- spec/spec_helper.rb
|
53
|
-
- spec/step_down_from_root_spec.rb
|
54
52
|
homepage: https://github.com/IcaliaLabs/on-container-for-ruby
|
55
53
|
licenses:
|
56
54
|
- MIT
|
@@ -74,13 +72,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
72
|
- !ruby/object:Gem::Version
|
75
73
|
version: '0'
|
76
74
|
requirements: []
|
77
|
-
|
78
|
-
rubygems_version: 2.5.2.3
|
75
|
+
rubygems_version: 3.1.4
|
79
76
|
signing_key:
|
80
77
|
specification_version: 4
|
81
78
|
summary: A small collection of scripts and routines to help ruby development within
|
82
79
|
containers
|
83
|
-
test_files:
|
84
|
-
- spec/on_container_spec.rb
|
85
|
-
- spec/spec_helper.rb
|
86
|
-
- spec/step_down_from_root_spec.rb
|
80
|
+
test_files: []
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'etc'
|
4
|
-
|
5
|
-
module OnContainer
|
6
|
-
class StepDownFromRoot
|
7
|
-
attr_reader :curent_user, :target_user
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@curent_user = Etc.getpwuid
|
11
|
-
end
|
12
|
-
|
13
|
-
def target_user
|
14
|
-
@target_user ||= Etc.getpwuid(developer_uid)
|
15
|
-
end
|
16
|
-
|
17
|
-
def perform
|
18
|
-
return unless root_user?
|
19
|
-
return warn_no_developer_uid unless developer_uid?
|
20
|
-
|
21
|
-
switch_to_developer_user
|
22
|
-
end
|
23
|
-
|
24
|
-
def root_user?
|
25
|
-
curent_user.name == 'root'
|
26
|
-
end
|
27
|
-
|
28
|
-
def developer_uid?
|
29
|
-
developer_uid > 0
|
30
|
-
end
|
31
|
-
|
32
|
-
def developer_uid
|
33
|
-
@developer_uid ||= ENV.fetch('DEVELOPER_UID', '').to_i
|
34
|
-
end
|
35
|
-
|
36
|
-
protected
|
37
|
-
|
38
|
-
def switch_to_developer_user
|
39
|
-
target_user_name = target_user.name
|
40
|
-
puts "Switching from 'root' user to '#{target_user_name}'..."
|
41
|
-
Kernel.exec 'su-exec', target_user_name, $0, *$*
|
42
|
-
end
|
43
|
-
|
44
|
-
def warn_no_developer_uid
|
45
|
-
puts "The 'DEVELOPER_UID' environment variable is not set... " \
|
46
|
-
'still running as root!'
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.perform
|
50
|
-
new.perform
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
OnContainer::StepDownFromRoot.perform
|
data/spec/on_container_spec.rb
DELETED
data/spec/spec_helper.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require "bundler/setup"
|
2
|
-
require "on_container"
|
3
|
-
|
4
|
-
RSpec.configure do |config|
|
5
|
-
# Enable flags like --only-failures and --next-failure
|
6
|
-
config.example_status_persistence_file_path = ".rspec_status"
|
7
|
-
|
8
|
-
# Disable RSpec exposing methods globally on `Module` and `main`
|
9
|
-
config.disable_monkey_patching!
|
10
|
-
|
11
|
-
config.expect_with :rspec do |c|
|
12
|
-
c.syntax = :expect
|
13
|
-
end
|
14
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'on_container/step_down_from_root'
|
4
|
-
|
5
|
-
RSpec.describe OnContainer::StepDownFromRoot do
|
6
|
-
let(:root_user) do
|
7
|
-
instance_double "struct Etc::Passwd",
|
8
|
-
name: 'root',
|
9
|
-
passwd: 'x',
|
10
|
-
uid: 0,
|
11
|
-
gid: 0,
|
12
|
-
gecos: 'root',
|
13
|
-
dir: '/root',
|
14
|
-
shell: '/bin/bash'
|
15
|
-
end
|
16
|
-
|
17
|
-
let(:developer_user) do
|
18
|
-
instance_double "struct Etc::Passwd",
|
19
|
-
name: 'developer',
|
20
|
-
passwd: 'x',
|
21
|
-
uid: 1000,
|
22
|
-
gid: 1000,
|
23
|
-
gecos: 'Developer User,,,',
|
24
|
-
dir: '/usr/src',
|
25
|
-
shell: '/bin/bash'
|
26
|
-
end
|
27
|
-
|
28
|
-
let(:example_current_user) { root_user }
|
29
|
-
let(:example_target_user) { developer_user }
|
30
|
-
let(:example_developer_uid) { '1000' }
|
31
|
-
|
32
|
-
before do
|
33
|
-
allow(ENV).to receive(:fetch).with('DEVELOPER_UID', '') { example_developer_uid }
|
34
|
-
allow(Etc).to receive(:getpwuid) { example_current_user }
|
35
|
-
allow(Etc).to receive(:getpwuid).with(example_target_user.uid) { example_target_user }
|
36
|
-
allow(Kernel).to receive(:exec).with('su-exec', example_target_user.name, any_args)
|
37
|
-
end
|
38
|
-
|
39
|
-
describe '#perform' do
|
40
|
-
it 'changes to the target user' do
|
41
|
-
subject.perform
|
42
|
-
expect(Kernel).to have_received(:exec).with 'su-exec', example_target_user.name, any_args
|
43
|
-
end
|
44
|
-
|
45
|
-
context 'without a developer uid' do
|
46
|
-
let(:example_developer_uid) { '' }
|
47
|
-
|
48
|
-
example 'does not change the current user' do
|
49
|
-
subject.perform
|
50
|
-
expect(Kernel).not_to have_received(:exec)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
context 'when not as root' do
|
55
|
-
let(:example_current_user) { developer_user }
|
56
|
-
|
57
|
-
example 'does not change the current user' do
|
58
|
-
subject.perform
|
59
|
-
expect(Kernel).not_to have_received(:exec)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|