moby-derp 0.7.2 → 0.8.0
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/.github/workflows/ci.yml +40 -0
- data/README.md +9 -2
- data/example.yml +46 -10
- data/lib/moby_derp/container.rb +20 -2
- data/lib/moby_derp/container_config.rb +35 -3
- data/lib/moby_derp/pod.rb +3 -3
- data/lib/moby_derp/pod_config.rb +2 -2
- data/lib/moby_derp/system_config.rb +4 -6
- data/smoke_tests/restart_always.bats +24 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f655f86d8be3982dcf24c11734361071cb040093e22ca89fb59bfb7ac786a8ad
|
4
|
+
data.tar.gz: 39a54635e4cebed9f54a2eaeb5124e0b62435a582b3ef3d52e018530c9683efb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3209066a78a89998bd549568d65345e8ec86c26f31b1860084cfe4d1325f1e8a8388c392915a701ff54fe1949c572de4d2ac7ec2efd2285670a1ba789eba4c5
|
7
|
+
data.tar.gz: b964a1d45ebb633437fb29e656ae48e9743cda5bc768605098289b10a0a12a13ec5c6ed142397a14bc08b6b0c3bb94bef4255f0b9f911863abb0db9a04e17774
|
@@ -0,0 +1,40 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request: {}
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
name: "moby-derp-test"
|
12
|
+
runs-on: ${{ matrix.os }}
|
13
|
+
timeout-minutes: 60
|
14
|
+
|
15
|
+
strategy:
|
16
|
+
fail-fast: false
|
17
|
+
|
18
|
+
matrix:
|
19
|
+
os: [ubuntu-latest]
|
20
|
+
ruby: ["2.7"]
|
21
|
+
|
22
|
+
steps:
|
23
|
+
- uses: actions/checkout@master
|
24
|
+
with:
|
25
|
+
fetch-depth: 1
|
26
|
+
|
27
|
+
- name: Setup ruby
|
28
|
+
uses: actions/setup-ruby@v1
|
29
|
+
with:
|
30
|
+
ruby-version: ${{ matrix.ruby }}
|
31
|
+
|
32
|
+
- name: Setup bundler
|
33
|
+
run: |
|
34
|
+
gem install bundler -v 2.1.4 --no-doc
|
35
|
+
|
36
|
+
- name: Setup gems
|
37
|
+
run: bundle install --jobs 4
|
38
|
+
|
39
|
+
- name: RSpec
|
40
|
+
run: bundle exec rspec
|
data/README.md
CHANGED
@@ -124,6 +124,12 @@ The keys are:
|
|
124
124
|
"localhost"; so pointing to your local caching resolver using `127.0.0.1`
|
125
125
|
will not end in happiness and puppies.
|
126
126
|
|
127
|
+
* **`host_hostname`**: the hostname to use when generating the container
|
128
|
+
hostname. This might be useful, for example, if moby-derp is running
|
129
|
+
in a container by itself, with a hostname that reflects its management
|
130
|
+
function, but the containers that are managed conceptually belong to
|
131
|
+
the host.
|
132
|
+
|
127
133
|
If you wish to modify the location of the `moby-derp` system-wide configuration
|
128
134
|
file, you can do so by setting the `MOBY_DERP_SYSTEM_CONFIG_FILE` environment
|
129
135
|
variable. Note, however, that it is a terrible idea to let ordinary users control
|
@@ -169,7 +175,8 @@ caveats:
|
|
169
175
|
# Security
|
170
176
|
|
171
177
|
This section discusses the security model and guarantees of `moby-derp`. It
|
172
|
-
isn't necessary to
|
178
|
+
isn't necessary to understand this section if you simply want to use
|
179
|
+
`moby-derp` in most circumstances.
|
173
180
|
|
174
181
|
The fundamental principle of `moby-derp` is that users are given control over
|
175
182
|
a certain portion of the container and filesystem namespace, by virtue of their
|
@@ -233,7 +240,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
233
240
|
Unless otherwise stated, everything in this repo is covered by the following
|
234
241
|
copyright notice:
|
235
242
|
|
236
|
-
Copyright (C) 2019 Matt Palmer <matt@hezmatt.org>
|
243
|
+
Copyright (C) 2019, 2023, 2024 Matt Palmer <matt@hezmatt.org>
|
237
244
|
|
238
245
|
This program is free software: you can redistribute it and/or modify it
|
239
246
|
under the terms of the GNU General Public License version 3, as
|
data/example.yml
CHANGED
@@ -48,24 +48,60 @@ containers:
|
|
48
48
|
#
|
49
49
|
command: '--foo=bar --wombat'
|
50
50
|
|
51
|
+
# Occasionally a container image will have defined an entrypoint that isn't
|
52
|
+
# appropriate for your situation. Although fixing the image is the preferable
|
53
|
+
# option, if that isn't feasible, you can instead override the image's
|
54
|
+
# entrypoint here.
|
55
|
+
entrypoint: '/usr/local/bin/alternate-init'
|
56
|
+
|
51
57
|
# The 12-factor app concept says that all configuration should be passed
|
52
58
|
# via the environment. If that is your bag, then this section will make
|
53
59
|
# you very happy.
|
54
60
|
#
|
55
61
|
environment:
|
56
|
-
# The environment is specified as a map of variable
|
57
|
-
# values.
|
58
|
-
# YAML's many string quoting and escaping modes.
|
62
|
+
# The environment is specified as a map of environment variable names to
|
63
|
+
# environment variable values.
|
59
64
|
#
|
60
65
|
APP_ENV: production
|
61
|
-
|
62
|
-
#
|
63
|
-
#
|
66
|
+
|
67
|
+
# Environment variable values must be strings by the time YAML has
|
68
|
+
# finished with them. That means that values YAML would normally interpret
|
69
|
+
# as other types must be quoted.
|
70
|
+
HAS_BUGS: 'false'
|
71
|
+
FINAL_ANSWER: '42'
|
72
|
+
|
73
|
+
# For passing big strings, you may want to get familiar with
|
74
|
+
# YAML's many string quoting and escaping modes.
|
75
|
+
#
|
76
|
+
COMPLEX_CONFIG: |
|
77
|
+
{
|
78
|
+
"foo": "bar",
|
79
|
+
"baz": [1, 1, 2, 3, 5, 8, 13]
|
80
|
+
}
|
81
|
+
|
82
|
+
# Read an environment variable value from a file.
|
83
|
+
#
|
84
|
+
# The keys are environment variable names, like any other, but the values
|
85
|
+
# those environment variables will have are read from the file given as the
|
86
|
+
# value of the entry, specified relative to the pod root directory on the host.
|
87
|
+
#
|
88
|
+
# The intended use case for this feature is for injecting secrets, where the
|
89
|
+
# value of the secret is written to a file on the host by some means outside the
|
90
|
+
# usual deployment mechanisms (so the data never goes into the repo), but can
|
91
|
+
# be accessed at runtime via the environment.
|
92
|
+
#
|
93
|
+
# Note that if you specify the same environment variable name both in here and
|
94
|
+
# in the `environment` map, the results are undefined. So don't do that.
|
95
|
+
#
|
96
|
+
# These environment files are read when `moby-derp` runs, and so only need be
|
97
|
+
# readable by the user that `moby-derp` runs as (typically `root`).
|
98
|
+
#
|
99
|
+
environment_files:
|
100
|
+
# This will set an environment variable named `PRIVATE_KEY` with the value
|
101
|
+
# read from `$pod_root/secret/private.key`, which presumably an administrator
|
102
|
+
# has previously written.
|
64
103
|
#
|
65
|
-
PRIVATE_KEY:
|
66
|
-
-----BEGIN PRIVATE KEY-----
|
67
|
-
AAAAsd...
|
68
|
-
-----END PRIVATE KEY-----
|
104
|
+
PRIVATE_KEY: secrets/private.key
|
69
105
|
|
70
106
|
# Persisting data past the lifetime of a particular container is the job
|
71
107
|
# of mounts. Here you can specify filesystem locations on the host
|
data/lib/moby_derp/container.rb
CHANGED
@@ -19,7 +19,7 @@ module MobyDerp
|
|
19
19
|
|
20
20
|
def run
|
21
21
|
container_name = @root_container ? @pod.name : @config.name
|
22
|
-
@logger.debug(logloc) { "Calculated container name is #{container_name} (@root_container: #{@root_container.inspect}, @config.name: #{@config.name}, @pod.name: #{@pod.name}" }
|
22
|
+
@logger.debug(logloc) { "Calculated container name is #{container_name} (@root_container: #{@root_container.inspect}, @config.name: #{@config.name}, @pod.name: #{@pod.name})" }
|
23
23
|
|
24
24
|
begin
|
25
25
|
existing_container = Docker::Container.get(container_name)
|
@@ -29,6 +29,12 @@ module MobyDerp
|
|
29
29
|
if existing_container.info["Config"]["Labels"]["org.hezmatt.moby-derp.config-hash"] == params_hash(container_creation_parameters)
|
30
30
|
# Container is up-to-date
|
31
31
|
@logger.info(logloc) { "Container #{container_name} is up-to-date" }
|
32
|
+
@logger.debug(logloc) { "Container #{container_name} has restart=#{@config.restart}, state is #{existing_container.info.dig("State", "Status")}" }
|
33
|
+
|
34
|
+
if @config.restart == "always" && existing_container.info.dig("State", "Status") != "running"
|
35
|
+
existing_container.start
|
36
|
+
end
|
37
|
+
|
32
38
|
return existing_container.id
|
33
39
|
end
|
34
40
|
|
@@ -85,9 +91,11 @@ module MobyDerp
|
|
85
91
|
def container_creation_parameters
|
86
92
|
{}.tap do |params|
|
87
93
|
if @root_container
|
94
|
+
params["Hostname"] = @pod.hostname
|
88
95
|
params["HostConfig"] = {
|
89
96
|
"NetworkMode" => @pod.network_name,
|
90
97
|
"Init" => true,
|
98
|
+
"IpcMode" => "shareable"
|
91
99
|
}
|
92
100
|
params["MacAddress"] = container_mac_address
|
93
101
|
if network_uses_ipv6? && user_defined_network?
|
@@ -113,7 +121,7 @@ module MobyDerp
|
|
113
121
|
params["HostConfig"]["RestartPolicy"] = parsed_restart_policy
|
114
122
|
params["HostConfig"]["Mounts"] = merged_mounts.map { |mount| mount_structure(mount) }
|
115
123
|
|
116
|
-
params["Env"] = @pod.common_environment.merge(@config.environment).map { |k, v| "#{k}=#{v}" }
|
124
|
+
params["Env"] = @pod.common_environment.merge(read_environment_files).merge(@config.environment).map { |k, v| "#{k}=#{v}" }
|
117
125
|
params["Volumes"] = {}
|
118
126
|
|
119
127
|
params["name"] = @root_container ? @pod.name : @config.name
|
@@ -123,6 +131,10 @@ module MobyDerp
|
|
123
131
|
params["StopSignal"] = @config.stop_signal
|
124
132
|
params["StopTimeout"] = @config.stop_timeout
|
125
133
|
|
134
|
+
if !@config.entrypoint.nil?
|
135
|
+
params["Entrypoint"] = [@config.entrypoint]
|
136
|
+
end
|
137
|
+
|
126
138
|
if @config.user
|
127
139
|
params["User"] = @config.user
|
128
140
|
end
|
@@ -165,6 +177,12 @@ module MobyDerp
|
|
165
177
|
end
|
166
178
|
end
|
167
179
|
|
180
|
+
def read_environment_files
|
181
|
+
@config.environment_files.map do |var, filename|
|
182
|
+
[var, File.read(File.join(@pod.mount_root, filename))]
|
183
|
+
end.to_h
|
184
|
+
end
|
185
|
+
|
168
186
|
def hash_labelled(params)
|
169
187
|
params.tap do |params|
|
170
188
|
config_hash = params_hash(params)
|
@@ -7,8 +7,8 @@ require "shellwords"
|
|
7
7
|
|
8
8
|
module MobyDerp
|
9
9
|
class ContainerConfig
|
10
|
-
attr_reader :name, :image, :update_image, :command, :environment, :
|
11
|
-
:labels, :readonly, :stop_signal, :stop_timeout, :user, :restart, :limits,
|
10
|
+
attr_reader :name, :image, :update_image, :command, :entrypoint, :environment, :environment_files,
|
11
|
+
:mounts, :labels, :readonly, :stop_signal, :stop_timeout, :user, :restart, :limits,
|
12
12
|
:startup_health_check
|
13
13
|
|
14
14
|
def initialize(system_config:,
|
@@ -17,7 +17,9 @@ module MobyDerp
|
|
17
17
|
image:,
|
18
18
|
update_image: true,
|
19
19
|
command: [],
|
20
|
+
entrypoint: nil,
|
20
21
|
environment: {},
|
22
|
+
environment_files: {},
|
21
23
|
mounts: [],
|
22
24
|
labels: {},
|
23
25
|
readonly: false,
|
@@ -30,14 +32,16 @@ module MobyDerp
|
|
30
32
|
)
|
31
33
|
@system_config, @pod_config, @name, @image = system_config, pod_config, "#{pod_config.name}.#{container_name}", image
|
32
34
|
|
33
|
-
@update_image, @command, @environment, @mounts, @labels = update_image, command, environment, mounts, labels
|
35
|
+
@update_image, @command, @entrypoint, @environment, @environment_files, @mounts, @labels = update_image, command, entrypoint, environment, environment_files, mounts, labels
|
34
36
|
@readonly, @stop_signal, @stop_timeout, @user, @restart = readonly, stop_signal, stop_timeout, user, restart
|
35
37
|
@limits, @startup_health_check = limits, startup_health_check
|
36
38
|
|
37
39
|
validate_image
|
38
40
|
validate_update_image
|
39
41
|
validate_command
|
42
|
+
validate_entrypoint
|
40
43
|
validate_environment
|
44
|
+
validate_environment_files
|
41
45
|
validate_mounts
|
42
46
|
validate_labels
|
43
47
|
validate_readonly
|
@@ -88,6 +92,13 @@ module MobyDerp
|
|
88
92
|
end
|
89
93
|
end
|
90
94
|
|
95
|
+
def validate_entrypoint
|
96
|
+
unless @entrypoint.nil? || @entrypoint.is_a?(String)
|
97
|
+
raise ConfigurationError,
|
98
|
+
"entrypoint must be a string"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
91
102
|
def validate_environment
|
92
103
|
validate_hash(:environment)
|
93
104
|
|
@@ -97,6 +108,27 @@ module MobyDerp
|
|
97
108
|
end
|
98
109
|
end
|
99
110
|
|
111
|
+
def validate_environment_files
|
112
|
+
validate_hash(:environment_files)
|
113
|
+
|
114
|
+
if (bad_vars = @environment_files.keys.select { |k| k =~ /=/ }) != []
|
115
|
+
raise ConfigurationError,
|
116
|
+
"environment variable names cannot include equals signs: #{bad_vars.inspect}"
|
117
|
+
end
|
118
|
+
|
119
|
+
@environment_files.each do |k, v|
|
120
|
+
if v =~ %r{(\A|/)\.\.($|/)}
|
121
|
+
raise ConfigurationError,
|
122
|
+
"path traversal detected in #{k} -- this ain't a Fortinet, mate!"
|
123
|
+
end
|
124
|
+
|
125
|
+
if v =~ %r{\A(/|~)}
|
126
|
+
raise ConfigurationError,
|
127
|
+
"environment file for #{k} can only be a relative path"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
100
132
|
def validate_mounts
|
101
133
|
unless @mounts.is_a?(Array)
|
102
134
|
raise ConfigurationError,
|
data/lib/moby_derp/pod.rb
CHANGED
@@ -38,9 +38,9 @@ module MobyDerp
|
|
38
38
|
begin
|
39
39
|
MobyDerp::Container.new(pod: self, container_config: cfg).run
|
40
40
|
rescue MobyDerp::ContainerError => ex
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
@logger.error(logloc) {
|
42
|
+
(["Error while running container #{cfg.name}: #{ex.message}"] + ex.backtrace.map { |l| " #{l}" }).join("\n")
|
43
|
+
}
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
data/lib/moby_derp/pod_config.rb
CHANGED
@@ -57,8 +57,8 @@ module MobyDerp
|
|
57
57
|
@containers = @config.fetch("containers")
|
58
58
|
validate_containers
|
59
59
|
|
60
|
-
@logger.debug(logloc) { "Hostname is #{
|
61
|
-
@hostname = @config.fetch("hostname", "#{@name.gsub("_", "-")}-#{
|
60
|
+
@logger.debug(logloc) { "Hostname is #{@system_config.host_hostname}" }
|
61
|
+
@hostname = @config.fetch("hostname", "#{@name.gsub("_", "-")}-#{@system_config.host_hostname}")
|
62
62
|
|
63
63
|
@common_environment = @config.fetch("common_environment", {})
|
64
64
|
@common_labels = @config.fetch("common_labels", {})
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require_relative "./config_file"
|
2
2
|
|
3
|
+
require "socket"
|
4
|
+
|
3
5
|
module MobyDerp
|
4
6
|
class SystemConfig < ConfigFile
|
5
7
|
attr_reader :mount_root, :port_whitelist, :network_name, :use_host_resolv_conf,
|
6
|
-
:cpu_count, :cpu_bits
|
8
|
+
:cpu_count, :cpu_bits, :host_hostname
|
7
9
|
|
8
10
|
def initialize(config_data_or_filename, moby_info, logger)
|
9
11
|
@logger = logger
|
@@ -17,6 +19,7 @@ module MobyDerp
|
|
17
19
|
raise ArgumentError, "Unsupported type for config_data_or_filename parameter"
|
18
20
|
end
|
19
21
|
|
22
|
+
@host_hostname = @config["host_hostname"] || Socket.gethostname
|
20
23
|
@mount_root = @config["mount_root"]
|
21
24
|
@port_whitelist = stringify_keys(@config["port_whitelist"] || {})
|
22
25
|
@network_name = @config["network_name"] || "bridge"
|
@@ -46,11 +49,6 @@ module MobyDerp
|
|
46
49
|
raise ConfigurationError,
|
47
50
|
"use_host_resolv_conf must be true or false"
|
48
51
|
end
|
49
|
-
|
50
|
-
unless File.directory?(@mount_root)
|
51
|
-
raise ConfigurationError,
|
52
|
-
"mount_root #{@mount_root} must exist and be a directory"
|
53
|
-
end
|
54
52
|
end
|
55
53
|
|
56
54
|
private
|
@@ -0,0 +1,24 @@
|
|
1
|
+
load test_helper
|
2
|
+
|
3
|
+
@test "Restart always" {
|
4
|
+
config_file <<-'EOF'
|
5
|
+
containers:
|
6
|
+
bob:
|
7
|
+
image: busybox:latest
|
8
|
+
command: sleep 600
|
9
|
+
restart: always
|
10
|
+
common_labels:
|
11
|
+
moby-derp-smoke-test: ayup
|
12
|
+
EOF
|
13
|
+
|
14
|
+
run $MOBY_DERP_BIN $TEST_CONFIG_FILE
|
15
|
+
|
16
|
+
[ "$status" = "0" ]
|
17
|
+
|
18
|
+
docker stop mdst.bob
|
19
|
+
run $MOBY_DERP_BIN $TEST_CONFIG_FILE
|
20
|
+
|
21
|
+
echo $output
|
22
|
+
container_running "mdst"
|
23
|
+
container_running "mdst.bob"
|
24
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: moby-derp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docker-api
|
@@ -186,6 +186,7 @@ executables:
|
|
186
186
|
extensions: []
|
187
187
|
extra_rdoc_files: []
|
188
188
|
files:
|
189
|
+
- ".github/workflows/ci.yml"
|
189
190
|
- ".gitignore"
|
190
191
|
- ".travis.yml"
|
191
192
|
- ".yardopts"
|
@@ -210,6 +211,7 @@ files:
|
|
210
211
|
- smoke_tests/exposed.bats
|
211
212
|
- smoke_tests/minimal.bats
|
212
213
|
- smoke_tests/no_file.bats
|
214
|
+
- smoke_tests/restart_always.bats
|
213
215
|
- smoke_tests/root_labels.bats
|
214
216
|
- smoke_tests/test_helper.bash
|
215
217
|
homepage: http://github.com/mpalmer/moby-derp
|
@@ -230,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
232
|
- !ruby/object:Gem::Version
|
231
233
|
version: '0'
|
232
234
|
requirements: []
|
233
|
-
rubygems_version: 3.
|
235
|
+
rubygems_version: 3.2.5
|
234
236
|
signing_key:
|
235
237
|
specification_version: 4
|
236
238
|
summary: A simple management system for a pod of moby containers
|