moby-derp 0.7.1 → 0.8.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: f4f3f67740bc9579d1b4e2af6e95801e2f019f65b2fddcc495e5ef1c24084565
4
- data.tar.gz: a78bf5f24219288581f7bc24137b372e2c9738a9ba2245266b176c800e8db0f8
3
+ metadata.gz: f655f86d8be3982dcf24c11734361071cb040093e22ca89fb59bfb7ac786a8ad
4
+ data.tar.gz: 39a54635e4cebed9f54a2eaeb5124e0b62435a582b3ef3d52e018530c9683efb
5
5
  SHA512:
6
- metadata.gz: 724548d882433981999f8c6ba64ba48b3e227de6697a23fb2bd01819acf50a711fd880f685825099e25864aad0736fdc55cd7098ed4618eae83a38bf6a63d472
7
- data.tar.gz: be5f791cc9b56efb0be63fc56bd6d788ad4d6617aed29360452a8c5c44d7d83228734edb3c0ad0c8bbbfda58c680e71bf65503ced36db2547876efa5e95bff93
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/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  /.yardoc
5
5
  /.bundle
6
6
  /coverage
7
+ /test
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+
3
+ cache: bundler
4
+
5
+ rvm:
6
+ - 2.6
7
+ - 2.5
8
+ - 2.4
9
+ - 2.3
10
+
11
+ gemfile:
12
+ - Gemfile
data/README.md CHANGED
@@ -84,7 +84,7 @@ removed. This is used as the prefix for the name of all containers in the pod.
84
84
  Some aspects of `moby-derp`'s operation are security-sensitive, and thus shouldn't
85
85
  be able to be modified by the ordinary user. There is a
86
86
  system-wide configuration file for this purpose, by default located at
87
- `/etc/moby-derp.yml`.
87
+ `/etc/moby-derp.conf`.
88
88
 
89
89
  Its structure is quite simple. A full example looks like this:
90
90
 
@@ -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 simply use `moby-derp` in most circumstances.
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 name to variable
57
- # values. For passing big strings, you may want to get familiar with
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
- # Embedding your private key like this isn't necessarily a particularly
62
- # good idea, but as an example of where you might need multiline strings,
63
- # it works very well.
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
@@ -322,8 +358,8 @@ expose:
322
358
  # in the `moby-derp` README).
323
359
  #
324
360
  publish:
325
- - :80
326
- - :1234-1237
361
+ - ":80"
362
+ - ":1234-1237"
327
363
 
328
364
  # If you have a burning desire to have all exposed ports automatically published
329
365
  # to (not-so-)randomly chosen ephemeral ports, you can set this option to `true`.
@@ -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, :mounts,
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
@@ -61,6 +65,10 @@ module MobyDerp
61
65
  raise ConfigurationError,
62
66
  "image is not a valid image reference"
63
67
  end
68
+
69
+ if @image.match(Docker::Image::IMAGE_REFERENCE)[9].nil?
70
+ @image += ":latest"
71
+ end
64
72
  end
65
73
 
66
74
  def validate_update_image
@@ -84,6 +92,13 @@ module MobyDerp
84
92
  end
85
93
  end
86
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
+
87
102
  def validate_environment
88
103
  validate_hash(:environment)
89
104
 
@@ -93,6 +108,27 @@ module MobyDerp
93
108
  end
94
109
  end
95
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
+
96
132
  def validate_mounts
97
133
  unless @mounts.is_a?(Array)
98
134
  raise ConfigurationError,
@@ -29,7 +29,7 @@ module Docker
29
29
  cred = hunt_for_image_domain_cred(ref, docker_config.fetch("auths", {}))
30
30
 
31
31
  if cred
32
- user, pass = JSON.parse(cred.fetch("auth", "null"))&.unpack("m")&.first&.split(":", 2)
32
+ user, pass = cred["auth"]&.unpack("m")&.first&.split(":", 2)
33
33
 
34
34
  if user && pass
35
35
  { username: user, password: pass, serveraddress: image_domain(ref) }
data/lib/moby_derp/pod.rb CHANGED
@@ -25,7 +25,7 @@ module MobyDerp
25
25
 
26
26
  if c.info["Labels"]["org.hezmatt.moby-derp.pod-name"] == name &&
27
27
  !c.info["Labels"]["org.hezmatt.moby-derp.root-container-id"].nil? &&
28
- !desired_container_names.include?(c_name.split(".", 2).last)
28
+ !desired_container_names.include?(c_name)
29
29
  @logger.info(logloc) { "Removing stale container #{c_name}" }
30
30
  c.stop
31
31
  c.delete
@@ -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
- raise MobyDerp::ContainerError,
42
- "error while running container #{cfg.name}: #{ex.message}",
43
- ex.backtrace
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
@@ -57,8 +57,8 @@ module MobyDerp
57
57
  @containers = @config.fetch("containers")
58
58
  validate_containers
59
59
 
60
- @logger.debug(logloc) { "Hostname is #{Socket.gethostname}" }
61
- @hostname = @config.fetch("hostname", "#{@name.gsub("_", "-")}-#{Socket.gethostname}")
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.7.1
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: 2020-07-01 00:00:00.000000000 Z
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,7 +186,9 @@ executables:
186
186
  extensions: []
187
187
  extra_rdoc_files: []
188
188
  files:
189
+ - ".github/workflows/ci.yml"
189
190
  - ".gitignore"
191
+ - ".travis.yml"
190
192
  - ".yardopts"
191
193
  - CODE_OF_CONDUCT.md
192
194
  - CONTRIBUTING.md
@@ -209,6 +211,7 @@ files:
209
211
  - smoke_tests/exposed.bats
210
212
  - smoke_tests/minimal.bats
211
213
  - smoke_tests/no_file.bats
214
+ - smoke_tests/restart_always.bats
212
215
  - smoke_tests/root_labels.bats
213
216
  - smoke_tests/test_helper.bash
214
217
  homepage: http://github.com/mpalmer/moby-derp
@@ -229,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
232
  - !ruby/object:Gem::Version
230
233
  version: '0'
231
234
  requirements: []
232
- rubygems_version: 3.0.3
235
+ rubygems_version: 3.2.5
233
236
  signing_key:
234
237
  specification_version: 4
235
238
  summary: A simple management system for a pod of moby containers