Class: Dash::Commands::Proxy
Defined Under Namespace
Modules: CertTransfer
Constant Summary
collapse
- CONFIG_DIGEST_LABEL =
"org.dash.proxy-config-digest"
- LEGACY_CONFIG_DIGEST_LABEL =
Containers booted before the stage-3b rename carry the old key. New ones are
labelled with CONFIG_DIGEST_LABEL only, but reads fall back to the legacy key
so upgrading doesn't read as config drift and reboot every proxy for nothing.
Both the legacy constant and the fallback go away in stage 3d.
"org.kamal.proxy-config-digest"
- CONFIG_DIGEST_FORMAT =
"'{{ with index .Config.Labels \"#{CONFIG_DIGEST_LABEL}\" }}{{ . }}" \
"{{ else }}{{ index .Config.Labels \"#{LEGACY_CONFIG_DIGEST_LABEL}\" }}{{ end }}'"
CertTransfer::CERT_ARCHIVE_FILENAME, CertTransfer::CERT_IMPORT_STAGING_FILENAME, CertTransfer::CONTAINER_IMPORT_PATH
Constants inherited
from Base
Base::DOCKER_HEALTH_STATUS_FORMAT, Base::NO_HEALTHCHECK
Instance Attribute Summary collapse
Attributes inherited from Base
#config
Instance Method Summary
collapse
-
#boot_config ⇒ Object
-
#cache_purge(service, path_prefix: nil) ⇒ Object
-
#cache_stats(count: false, json: false) ⇒ Object
-
#cleanup_traefik ⇒ Object
-
#config_digest ⇒ Object
-
#container_id(only_running: false) ⇒ Object
-
#copy_legacy_config_volume(volume: Dash::Configuration::Proxy::CONFIG_VOLUME, legacy: Dash::Configuration::Proxy::LEGACY_CONFIG_VOLUME) ⇒ Object
Copies the pre-rename config volume into the new one, before anything starts.
-
#disable_restart ⇒ Object
Cancel the restart policy before draining: drain makes the proxy exit on its own, which - unlike docker stop - an active restart policy would undo.
-
#domains(subcommand, *args) ⇒ Object
retry takes a host, or --all; the rest take no arguments.
-
#drain(timeout: nil) ⇒ Object
-
#ensure_apps_config_directory ⇒ Object
-
#ensure_proxy_directory ⇒ Object
-
#follow_logs(host:, timestamps: true, grep: nil, grep_options: nil) ⇒ Object
-
#holder_container_id ⇒ Object
-
#info ⇒ Object
-
#initialize(config, host:) ⇒ Proxy
constructor
-
#list(name: container_name, json: false) ⇒ Object
-
#loadbalancer ⇒ Object
-
#logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) ⇒ Object
-
#mount_destinations ⇒ Object
One mount destination per line - what the running container was actually booted with, as opposed to what the current configuration would mount.
-
#next_container_name ⇒ Object
-
#port_holder? ⇒ Boolean
Zero-downtime handoff commands (proxy/run port_holder mode).
-
#promote_next_container ⇒ Object
-
#pull ⇒ Object
-
#read_boot_options ⇒ Object
-
#read_image ⇒ Object
-
#read_image_version ⇒ Object
-
#read_run_command ⇒ Object
-
#remove_container ⇒ Object
Docker ANDs multiple --filter label= values, so matching both the current and the pre-rename image title takes two commands rather than one filter with two values.
-
#remove_image ⇒ Object
-
#remove_legacy_container(timeout: nil) ⇒ Object
Stops and removes a pre-rename proxy container so the renamed one can claim ports 80/443.
-
#remove_legacy_holder_container ⇒ Object
-
#remove_proxy_directory ⇒ Object
-
#remove_proxy_secrets_file ⇒ Object
Static path rather than proxy_run_config.secrets_path: the file must be removable precisely when the run config (or its secrets) is gone.
-
#remove_stopped_container(name: container_name) ⇒ Object
-
#reset_boot_options ⇒ Object
-
#reset_image ⇒ Object
-
#reset_image_version ⇒ Object
-
#reset_run_command ⇒ Object
-
#run(digest: nil, name: nil) ⇒ Object
-
#run_holder ⇒ Object
-
#start ⇒ Object
-
#start_holder_or_run ⇒ Object
-
#start_or_run(digest: nil) ⇒ Object
-
#stop(name: container_name, timeout: nil) ⇒ Object
-
#version(name: container_name) ⇒ Object
name: so the doctor can also ask about the pre-rename container: during the stage-3c transition a host still runs kamal-proxy, and a check that only ever looks at dash-proxy concludes no proxy is running.
-
#wait_for_exit(name: container_name) ⇒ Object
#certs_archive_container_path, #certs_archive_host_path, #certs_import_host_path, #export_certs, #export_certs_offline, #import_certs, #remove_certs_archive, #remove_certs_import
Methods inherited from Base
#container_id_for, #ensure_docker_installed, #ensure_run_directory, #make_directory, #make_directory_for, #read_file, #remove_directory, #remove_file, #run_over_ssh
Constructor Details
#initialize(config, host:) ⇒ Proxy
Returns a new instance of Proxy.
18
19
20
21
|
# File 'lib/dash/commands/proxy.rb', line 18
def initialize(config, host:)
super(config)
@proxy_run_config = config.proxy_run(host)
end
|
Instance Attribute Details
#proxy_run_config ⇒ Object
Returns the value of attribute proxy_run_config.
5
6
7
|
# File 'lib/dash/commands/proxy.rb', line 5
def proxy_run_config
@proxy_run_config
end
|
Instance Method Details
#boot_config ⇒ Object
266
267
268
|
# File 'lib/dash/commands/proxy.rb', line 266
def boot_config
[ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)} #{substitute(read_run_command)}" ]
end
|
#cache_purge(service, path_prefix: nil) ⇒ Object
142
143
144
|
# File 'lib/dash/commands/proxy.rb', line 142
def cache_purge(service, path_prefix: nil)
docker :exec, container_name, "dash-proxy", :cache, :purge, service, *optionize({ "path-prefix": path_prefix }.compact)
end
|
#cache_stats(count: false, json: false) ⇒ Object
138
139
140
|
# File 'lib/dash/commands/proxy.rb', line 138
def cache_stats(count: false, json: false)
docker :exec, container_name, "dash-proxy", :cache, :stats, *optionize({ count: count || nil, json: json || nil }.compact)
end
|
#cleanup_traefik ⇒ Object
239
240
241
242
243
244
245
246
|
# File 'lib/dash/commands/proxy.rb', line 239
def cleanup_traefik
chain \
docker(:container, :stop, "traefik"),
combine(
docker(:container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=Traefik"),
docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik")
)
end
|
#config_digest ⇒ Object
118
119
120
|
# File 'lib/dash/commands/proxy.rb', line 118
def config_digest
docker :inspect, container_name, "--format", CONFIG_DIGEST_FORMAT
end
|
#container_id(only_running: false) ⇒ Object
122
123
124
|
# File 'lib/dash/commands/proxy.rb', line 122
def container_id(only_running: false)
container_id_for(container_name: container_name, only_running: only_running)
end
|
#copy_legacy_config_volume(volume: Dash::Configuration::Proxy::CONFIG_VOLUME, legacy: Dash::Configuration::Proxy::LEGACY_CONFIG_VOLUME) ⇒ Object
Copies the pre-rename config volume into the new one, before anything
starts. The volume holds the routing table and the ACME account and
certificate cache; losing it means re-issuing every certificate and
spending Let's Encrypt rate limits to get back where we were.
Runs in the dash-proxy image itself — already pulled by this point in the
boot sequence, and its ubuntu base has sh and cp. --user root because the
image's own user cannot write the destination volume; cp -a preserves the
uid, which the rename leaves at 1001.
The guard is negated and leads the chain, with || true last, because
shell && and || share precedence and associate left: written as
exists || legacy_exists && create && copy it would parse as
((exists || legacy_exists) && create) && copy and re-copy the legacy
volume over live state on every deploy. Leading with ! exists makes the
whole chain a single left-associative AND, which short-circuits correctly.
#disable_restart ⇒ Object
Cancel the restart policy before draining: drain makes the proxy exit on
its own, which - unlike docker stop - an active restart policy would undo.
184
185
186
|
# File 'lib/dash/commands/proxy.rb', line 184
def disable_restart
docker :update, "--restart=no", container_name
end
|
#domains(subcommand, *args) ⇒ Object
retry takes a host, or --all; the rest take no arguments.
218
219
220
|
# File 'lib/dash/commands/proxy.rb', line 218
def domains(subcommand, *args)
docker :exec, container_name, "dash-proxy", "domains", subcommand, *args
end
|
#drain(timeout: nil) ⇒ Object
188
189
190
|
# File 'lib/dash/commands/proxy.rb', line 188
def drain(timeout: nil)
docker :exec, container_name, "dash-proxy", :drain, *("--drain-timeout=#{timeout}s" if timeout)
end
|
#ensure_apps_config_directory ⇒ Object
256
257
258
|
# File 'lib/dash/commands/proxy.rb', line 256
def ensure_apps_config_directory
make_directory config.proxy_boot.apps_directory
end
|
#ensure_proxy_directory ⇒ Object
248
249
250
|
# File 'lib/dash/commands/proxy.rb', line 248
def ensure_proxy_directory
make_directory config.proxy_boot.host_directory
end
|
#follow_logs(host:, timestamps: true, grep: nil, grep_options: nil) ⇒ Object
210
211
212
213
214
215
|
# File 'lib/dash/commands/proxy.rb', line 210
def follow_logs(host:, timestamps: true, grep: nil, grep_options: nil)
run_over_ssh pipe(
docker(:logs, container_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
).join(" "), host: host
end
|
#holder_container_id ⇒ Object
178
179
180
|
# File 'lib/dash/commands/proxy.rb', line 178
def holder_container_id
container_id_for(container_name: proxy_run_config.holder_container_name, only_running: true)
end
|
#info ⇒ Object
105
106
107
|
# File 'lib/dash/commands/proxy.rb', line 105
def info
docker :ps, "--filter", "'name=^#{container_name}$'"
end
|
#list(name: container_name, json: false) ⇒ Object
134
135
136
|
# File 'lib/dash/commands/proxy.rb', line 134
def list(name: container_name, json: false)
docker :exec, name, "dash-proxy", :list, *("--json" if json)
end
|
#loadbalancer ⇒ Object
302
303
304
|
# File 'lib/dash/commands/proxy.rb', line 302
def loadbalancer
@loadbalancer ||= Dash::Commands::Loadbalancer.new(config, loadbalancer_config: DASH.loadbalancer_config)
end
|
#logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) ⇒ Object
204
205
206
207
208
|
# File 'lib/dash/commands/proxy.rb', line 204
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
pipe \
docker(:logs, container_name, ("--since #{since}" if since), ("--tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
end
|
#mount_destinations ⇒ Object
One mount destination per line - what the running container was actually
booted with, as opposed to what the current configuration would mount.
148
149
150
|
# File 'lib/dash/commands/proxy.rb', line 148
def mount_destinations
docker :inspect, container_name, "--format", "'{{range .Mounts}}{{println .Destination}}{{end}}'"
end
|
#next_container_name ⇒ Object
158
159
160
|
# File 'lib/dash/commands/proxy.rb', line 158
def next_container_name
"#{container_name}-next"
end
|
#port_holder? ⇒ Boolean
Zero-downtime handoff commands (proxy/run port_holder mode)
154
155
156
|
# File 'lib/dash/commands/proxy.rb', line 154
def port_holder?
proxy_run_config&.port_holder? || false
end
|
200
201
202
|
# File 'lib/dash/commands/proxy.rb', line 200
def promote_next_container
docker :container, :rename, next_container_name, container_name
end
|
#pull ⇒ Object
126
127
128
129
130
131
132
|
# File 'lib/dash/commands/proxy.rb', line 126
def pull
if proxy_run_config
docker :pull, proxy_run_config.image
else
docker :pull, "#{substitute(read_image)}:#{substitute(read_image_version)}"
end
end
|
#read_boot_options ⇒ Object
270
271
272
|
# File 'lib/dash/commands/proxy.rb', line 270
def read_boot_options
read_file(config.proxy_boot.options_file, default: config.proxy_boot.default_boot_options.join(" "))
end
|
#read_image ⇒ Object
274
275
276
|
# File 'lib/dash/commands/proxy.rb', line 274
def read_image
read_file(config.proxy_boot.image_file, default: config.proxy_boot.image_default)
end
|
#read_image_version ⇒ Object
#read_run_command ⇒ Object
282
283
284
|
# File 'lib/dash/commands/proxy.rb', line 282
def read_run_command
read_file(config.proxy_boot.run_command_file)
end
|
#remove_container ⇒ Object
Docker ANDs multiple --filter label= values, so matching both the current
and the pre-rename image title takes two commands rather than one filter
with two values. Without the legacy pass, dash proxy remove on a host that
has not yet been through the rename silently leaves the old container and
image behind. Stage 3d drops the legacy half.
#remove_legacy_container(timeout: nil) ⇒ Object
Stops and removes a pre-rename proxy container so the renamed one can claim
ports 80/443. No port-holder handoff spans two container names, which is
why this stage accepts a brief outage per host.
#remove_legacy_holder_container ⇒ Object
#remove_proxy_directory ⇒ Object
252
253
254
|
# File 'lib/dash/commands/proxy.rb', line 252
def remove_proxy_directory
remove_directory config.proxy_boot.host_directory
end
|
#remove_proxy_secrets_file ⇒ Object
Static path rather than proxy_run_config.secrets_path: the file must be
removable precisely when the run config (or its secrets) is gone.
#remove_stopped_container(name: container_name) ⇒ Object
196
197
198
|
# File 'lib/dash/commands/proxy.rb', line 196
def remove_stopped_container(name: container_name)
docker :container, :rm, name
end
|
#reset_boot_options ⇒ Object
286
287
288
|
# File 'lib/dash/commands/proxy.rb', line 286
def reset_boot_options
remove_file config.proxy_boot.options_file
end
|
#reset_image ⇒ Object
290
291
292
|
# File 'lib/dash/commands/proxy.rb', line 290
def reset_image
remove_file config.proxy_boot.image_file
end
|
#reset_image_version ⇒ Object
294
295
296
|
# File 'lib/dash/commands/proxy.rb', line 294
def reset_image_version
remove_file config.proxy_boot.image_version_file
end
|
#reset_run_command ⇒ Object
298
299
300
|
# File 'lib/dash/commands/proxy.rb', line 298
def reset_run_command
remove_file config.proxy_boot.run_command_file
end
|
#run(digest: nil, name: nil) ⇒ Object
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
# File 'lib/dash/commands/proxy.rb', line 23
def run(digest: nil, name: nil)
if proxy_run_config
docker \
:run,
"--name", name || container_name,
*proxy_run_config.network_args,
"--detach",
"--restart", "unless-stopped",
"--volume", "dash-proxy-config:/home/dash-proxy/.config/dash-proxy",
*config_digest_label_args(digest),
*proxy_run_config.docker_options_args,
*proxy_run_config.image,
*proxy_run_config.run_command
else
pipe boot_config, xargs(docker_run(digest: digest))
end
end
|
#run_holder ⇒ Object
162
163
164
165
166
167
168
169
170
171
172
|
# File 'lib/dash/commands/proxy.rb', line 162
def run_holder
docker \
:run,
"--name", proxy_run_config.holder_container_name,
"--network", "dash",
"--detach",
"--restart", "unless-stopped",
*proxy_run_config.holder_docker_args,
*proxy_run_config.image,
"dash-proxy", "hold"
end
|
#start ⇒ Object
93
94
95
|
# File 'lib/dash/commands/proxy.rb', line 93
def start
docker :container, :start, container_name
end
|
#start_holder_or_run ⇒ Object
174
175
176
|
# File 'lib/dash/commands/proxy.rb', line 174
def start_holder_or_run
combine docker(:container, :start, proxy_run_config.holder_container_name), run_holder, by: "||"
end
|
#start_or_run(digest: nil) ⇒ Object
101
102
103
|
# File 'lib/dash/commands/proxy.rb', line 101
def start_or_run(digest: nil)
combine start, run(digest: digest), by: "||"
end
|
#stop(name: container_name, timeout: nil) ⇒ Object
97
98
99
|
# File 'lib/dash/commands/proxy.rb', line 97
def stop(name: container_name, timeout: nil)
docker :container, :stop, *("--time #{timeout}" if timeout), name
end
|
#version(name: container_name) ⇒ Object
name: so the doctor can also ask about the pre-rename container: during
the stage-3c transition a host still runs kamal-proxy, and a check that only
ever looks at dash-proxy concludes no proxy is running.
112
113
114
115
116
|
# File 'lib/dash/commands/proxy.rb', line 112
def version(name: container_name)
pipe \
docker(:inspect, name, "--format '{{.Config.Image}}'"),
[ :awk, "-F:", "'{print \$NF}'" ]
end
|
#wait_for_exit(name: container_name) ⇒ Object
192
193
194
|
# File 'lib/dash/commands/proxy.rb', line 192
def wait_for_exit(name: container_name)
docker :wait, name
end
|