moby-derp 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|