-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dond
executable file
·389 lines (338 loc) · 12.5 KB
/
dond
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#!/usr/bin/env bash
if [[ "${DOND_SHIM_DEBUG:-false}" == true ]]; then
set -o xtrace
fi
# Exit on any kind of errors
# https://unix.stackexchange.com/questions/23026
set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
set -o functrace
shopt -s inherit_errexit
function echo_error() {
echo "ERROR(dond-shim):" "${@}" >&2
}
function error() {
echo_error "${@}"
uncaught_error=false
exit 1
}
# Ensure the user knows that the error was originated from the shim
function handle_error_trap() {
if [[ "${uncaught_error:-true}" == true ]]; then
echo_error "Uncaught error at line ${LINENO}"
fi
}
trap handle_error_trap ERR
# Finds all docker options that take a value from the --help output and
# stores them in the docker_options_with_value array.
function set_docker_options_with_value() {
local command=("${@}")
local help_output
help_output="$("${docker_path}" "${command[@]}" --help)"
local help_lines=()
if [[ -n "${help_output}" ]]; then
readarray -t help_lines <<<"${help_output}"
fi
unset help_output
docker_options_with_value=()
for line in "${help_lines[@]}"; do
# second group is the short option (optional)
# third group is the long option
if [[ "${line}" =~ ^[[:space:]]+((-[a-zA-Z]),[[:space:]])?(--[a-z0-9-]+)[[:space:]][a-z]+[[:space:]]+.*$ ]]; then
if [[ -n "${BASH_REMATCH[2]}" ]]; then
docker_options_with_value+=("${BASH_REMATCH[2]}")
fi
docker_options_with_value+=("${BASH_REMATCH[3]}")
fi
done
}
# Runs the docker command with the given arguments.
function run_docker() {
if [[ "${print_command}" == true ]]; then
echo "${docker_path}" "${@}"
exit 0
else
DOND_SHIM_SKIP=true exec "${docker_path}" "${@}"
fi
}
# Gets the current/parent container id on the host.
function set_container_id() {
local result
local mount_info_lines=()
readarray -t mount_info_lines </proc/self/mountinfo
for line in "${mount_info_lines[@]}"; do
if [[ "${line}" =~ /([a-z0-9]{12,128})/resolv.conf" " ]]; then
result="${BASH_REMATCH[1]}"
fi
done
unset mount_info_lines
# Sanity check
if [[ "${result}" =~ ^[a-z0-9]{12,128}$ ]]; then
readonly container_id="${result}"
else
error "Could not get parent container id"
fi
}
# Gets the root directory of the current/parent container on the host
# filesystem.
function set_container_root_on_host() {
local result
if [[ -z "${mock_container_root_on_host}" ]]; then
result="$(
"${docker_path}" inspect --format '{{.GraphDriver.Data.MergedDir}}' "${container_id}"
)"
else
result="${mock_container_root_on_host}"
fi
# Sanity check
if [[ "${result}" =~ ^(/[^/]+)+$ ]]; then
readonly container_root_on_host="${result}"
else
error "Could not get parent container root on host"
fi
}
# Reads the mounts of the current/parent container and stores them in the
# parent_container_mounts array.
function set_parent_container_mounts() {
local inspect_output
inspect_output=$(
"${docker_path}" inspect \
--format '{{range .Mounts}}{{if or (eq .Type "bind") (eq .Type "volume")}}{{printf "%s:%s\n" .Source .Destination}}{{end}}{{end}}' \
"${container_id}"
)
if [[ -n "${inspect_output}" ]]; then
readarray -t parent_container_mounts <<<"${inspect_output}"
else
parent_container_mounts=()
fi
unset inspect_output
readonly parent_container_mounts
}
# Performs the necessary transformations to the volume/mount argument.
function fix_volume_arg() {
local arg_type=""
if [[ "${volume_arg}" =~ ^(/[^:]+):([^:]+)(:([^:]+))?$ ]]; then
arg_type="volume"
local source="${BASH_REMATCH[1]}"
local destination="${BASH_REMATCH[2]}"
if [[ -n "${BASH_REMATCH[4]}" ]]; then
local mode_suffix=":${BASH_REMATCH[4]}"
else
local mode_suffix=""
fi
elif [[ "${volume_arg}" =~ (^|,)type\=bind(,|$) ]]; then
# There must be a better way of doing this
if [[ "${volume_arg}" =~ (^|,)((source|src)\=(/[^,]+))(,|$) ]]; then
local source_key="${BASH_REMATCH[3]}"
local source="${BASH_REMATCH[4]}"
if [[ "${volume_arg}" =~ (^|,)((destination|dst|target)\=(/[^,]+))(,|$) ]]; then
arg_type="mount"
local destination_key="${BASH_REMATCH[3]}"
local destination="${BASH_REMATCH[4]}"
fi
fi
fi
if [[ -z "${arg_type}" ]]; then
# Leave volume_arg as is if it does not match any patterns
return
fi
local fixed_source=""
# Fetch data only once and if needed
if [[ "${container_data_fetched}" == false ]]; then
set_container_id
set_container_root_on_host
set_parent_container_mounts
container_data_fetched=true
fi
# Check mounts of the parent container to identify whether the source
# is from the host filesystem or from the container itself. If it is
# from the host filesystem, then we need to transform the mount to
# match the mount from the parent container.
for container_volume in "${parent_container_mounts[@]}"; do
local container_volume_source="${container_volume%%":"*}"
local container_volume_destination="${container_volume#*":"}"
if [[ -z "${fixed_source}" ]]; then
if [[ "${source}" == "${container_volume_destination}" ]]; then
fixed_source="${container_volume_source}"
elif [[ "${source}" == "${container_volume_destination}/"* ]]; then
fixed_source="${container_volume_source}${source#"${container_volume_destination}"}"
fi
fi
# Check if there is some container_volume mounted within the source
# Example:
# First container mounts --volume "${PWD}/testfile:/home/rootless/testfile"
# Second container mounts --volume /home/rootless:/wd
# Then the second container should have an extra mount --volume "${PWD}/testfile:/wd/testfile"
if [[ "${container_volume_destination}" == "${source}/"* ]]; then
# Convert /home/rootless/testfile (container_volume_destination) to /wd/testfile (destination path)
if [[ "${arg_type}" == "volume" ]]; then
next_extra_args+=(--volume "${container_volume_source}:${destination}/${container_volume_destination#"${source}/"}${mode_suffix}")
elif [[ "${arg_type}" == "mount" ]]; then
# Use replace to generate an argument with the same options as the original
local fixed_arg
fixed_arg="${volume_arg//"${source_key}=${source}"/"${source_key}=${container_volume_source}"}"
fixed_arg="${fixed_arg//"${destination_key}=${destination}"/"${destination_key}=${destination}/${container_volume_destination#"${source}/"}"}"
next_extra_args+=(--mount "${fixed_arg}")
fi
fi
done
# If it was not possible to find a matching container_volume, then
# we mount relative to the container root directory on the host
# filesystem.
if [[ -z "${fixed_source}" ]]; then
fixed_source="${container_root_on_host}${source}"
fi
if [[ "${arg_type}" == "volume" ]]; then
volume_arg="${fixed_source}:${destination}${mode_suffix}"
elif [[ "${arg_type}" == "mount" ]]; then
# Use replace to avoid removing other options and also to keep the order
volume_arg="${volume_arg//"${source_key}=${source}"/"${source_key}=${fixed_source}"}"
fi
}
script_path="$(realpath "$0")"
readonly script_path
# Parse supported environment variables
readonly mock_container_root_on_host="${DOND_SHIM_MOCK_CONTAINER_ROOT_ON_HOST:-}"
readonly print_command="${DOND_SHIM_PRINT_COMMAND:-false}"
if [[ -n "${DOND_SHIM_DOCKER_PATH:-}" ]]; then
# If DOND_SHIM_DOCKER_PATH is set, then use it to call the docker
readonly docker_path="${DOND_SHIM_DOCKER_PATH}"
elif [[ "${0}" == *"/docker" ]]; then
# If this shim is named docker, then we expect the original docker to be
# named docker.orig
readonly docker_path="docker.orig"
else
# If this shim is not named docker, then we can simply call docker
readonly docker_path="docker"
fi
# Ensure docker_path is different from this script to avoid infinite loop
if [[ "${script_path}" == "${docker_path}" ]]; then
error "docker_path (${docker_path}) points to this script (${script_path})"
fi
# Ensure docker_path actually exists
if ! command -v "${docker_path}" >/dev/null; then
error "docker_path (${docker_path}) points to a non-existing file or command"
fi
# Save original arguments
original_args=("$@")
readonly original_args
# Avoid infinite loop if this script calls itself at a different path
if [[ "${DOND_SHIM_SKIP:-false}" == true ]]; then
exec "${docker_path}" "${original_args[@]}"
fi
# Exit early if the original command does not have at least 3 args, like
# run --volume=/tmp:/tmp ubuntu
if [[ "${#original_args[@]}" -lt 3 ]]; then
run_docker "${original_args[@]}"
fi
# We need to identify which arguments are global, which are for the container
# run/create command and which are for the image. That's to avoid transforming
# arguments that are not meant to be transformed (we should only transform
# arguments that are meant to be passed to the container run/create command).
# Example: --host whatever container run
global_args=()
# Example: --volume /tmp:/tmp ubuntu
command_args=()
# Example: bash -c "echo hello"
image_args=()
skip_next_arg=false
next_arg_type="global"
docker_command=()
global_options_with_value_fetched=false
command_options_with_value_fetched=false
first_global_positional_arg_found=false
for arg in "${original_args[@]}"; do
if [[ "${next_arg_type}" == "global" ]]; then
global_args+=("${arg}")
if [[ "${skip_next_arg}" == true ]]; then
skip_next_arg=false
continue
fi
if [[ "${arg}" == "-"* ]]; then
# Only parse global options before the first positional command, because
# docker does not allow an option between container and run/create like
# this: docker container --host whatever run
if [[ "${first_global_positional_arg_found}" == false ]]; then
# Only fetch docker global options when needed and only once
if [[ "${global_options_with_value_fetched}" == false ]]; then
set_docker_options_with_value
global_options_with_value_fetched=true
fi
# Skip next argument if it is a global option that accepts a value
for option in "${docker_options_with_value[@]}"; do
if [[ "${arg}" == "${option}" ]]; then
skip_next_arg=true
break
fi
done
fi
elif [[ "${arg}" == "run" || "${arg}" == "create" ]]; then
docker_command+=("${arg}")
next_arg_type="command"
elif [[ "${arg}" == "container" ]]; then
docker_command+=("${arg}")
first_global_positional_arg_found=true
else
# Skip if command is not run, create, container run, or container create
run_docker "${original_args[@]}"
fi
elif [[ "${next_arg_type}" == "command" ]]; then
command_args+=("${arg}")
if [[ "${skip_next_arg}" == true ]]; then
skip_next_arg=false
continue
fi
if [[ "${arg}" == "-"* ]]; then
# Only fetch docker command options when needed and only once
if [[ "${command_options_with_value_fetched}" == false ]]; then
set_docker_options_with_value "${docker_command[@]}"
command_options_with_value_fetched=true
fi
for option in "${docker_options_with_value[@]}"; do
if [[ "${arg}" == "${option}" ]]; then
skip_next_arg=true
continue
fi
done
else
# First non-option argument is the image
next_arg_type="image"
fi
elif [[ "${next_arg_type}" == "image" ]]; then
image_args+=("${arg}")
fi
done
readonly global_args command_args image_args
unset next_arg_type skip_next_arg docker_command global_options_with_value_fetched \
command_options_with_value_fetched first_global_positional_arg_found \
docker_options_with_value
# Finally it's time to transform the volume|mount arguments
container_data_fetched=false
fix_next_arg=false
fixed_args=()
next_extra_args=()
for arg in "${command_args[@]}"; do
if [[ "${fix_next_arg}" == true ]]; then
fix_next_arg=false
volume_arg="${arg}"
fix_volume_arg
arg="${volume_arg}"
elif [[ "${arg}" == "-v" || "${arg}" == "--volume" || "${arg}" == "--mount" ]]; then
fix_next_arg=true
elif [[ "${arg}" == "-v="* || "${arg}" == "--volume="* ]]; then
option_name="${arg%%"="*}"
volume_arg="${arg#*"="}"
fix_volume_arg
arg="${option_name}=${volume_arg}"
elif [[ "${arg}" == "--mount="* ]]; then
option_name="${arg%%"="*}"
volume_arg="${arg#*"="}"
fix_volume_arg
arg="${option_name}=${volume_arg}"
fi
fixed_args+=("${arg}" "${next_extra_args[@]}")
next_extra_args=()
done
run_docker "${global_args[@]}" "${fixed_args[@]}" "${image_args[@]}"