forked from containers/skopeo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.bash
382 lines (335 loc) · 12.9 KB
/
helpers.bash
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
#!/bin/bash
SKOPEO_BINARY=${SKOPEO_BINARY:-$(dirname ${BASH_SOURCE})/../skopeo}
# Default timeout for a skopeo command.
SKOPEO_TIMEOUT=${SKOPEO_TIMEOUT:-300}
# Default image to run as a local registry
REGISTRY_FQIN=${SKOPEO_TEST_REGISTRY_FQIN:-docker.io/library/registry:2}
###############################################################################
# BEGIN setup/teardown
# Provide common setup and teardown functions, but do not name them such!
# That way individual tests can override with their own setup/teardown,
# while retaining the ability to include these if they so desire.
function standard_setup() {
# Argh. Although BATS provides $BATS_TMPDIR, it's just /tmp!
# That's bloody worthless. Let's make our own, in which subtests
# can write whatever they like and trust that it'll be deleted
# on cleanup.
TESTDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-/tmp} skopeo_bats.XXXXXX)
}
function standard_teardown() {
if [[ -n $TESTDIR ]]; then
rm -rf $TESTDIR
fi
}
# Individual .bats files may override or extend these
function setup() {
standard_setup
}
function teardown() {
standard_teardown
}
# END setup/teardown
###############################################################################
# BEGIN standard helpers for running skopeo and testing results
#################
# run_skopeo # Invoke skopeo, with timeout, using BATS 'run'
#################
#
# This is the preferred mechanism for invoking skopeo:
#
# * we use 'timeout' to abort (with a diagnostic) if something
# takes too long; this is preferable to a CI hang.
# * we log the command run and its output. This doesn't normally
# appear in BATS output, but it will if there's an error.
# * we check exit status. Since the normal desired code is 0,
# that's the default; but the first argument can override:
#
# run_skopeo 125 nonexistent-subcommand
# run_skopeo '?' some-other-command # let our caller check status
#
# Since we use the BATS 'run' mechanism, $output and $status will be
# defined for our caller.
#
function run_skopeo() {
# Number as first argument = expected exit code; default 0
expected_rc=0
case "$1" in
[0-9]) expected_rc=$1; shift;;
[1-9][0-9]) expected_rc=$1; shift;;
[12][0-9][0-9]) expected_rc=$1; shift;;
'?') expected_rc= ; shift;; # ignore exit code
esac
# Remember command args, for possible use in later diagnostic messages
MOST_RECENT_SKOPEO_COMMAND="skopeo $*"
# stdout is only emitted upon error; this echo is to help a debugger
echo "\$ $SKOPEO_BINARY $*"
run timeout --foreground --kill=10 $SKOPEO_TIMEOUT ${SKOPEO_BINARY} "$@"
# without "quotes", multiple lines are glommed together into one
if [ -n "$output" ]; then
echo "$output"
fi
if [ "$status" -ne 0 ]; then
echo -n "[ rc=$status ";
if [ -n "$expected_rc" ]; then
if [ "$status" -eq "$expected_rc" ]; then
echo -n "(expected) ";
else
echo -n "(** EXPECTED $expected_rc **) ";
fi
fi
echo "]"
fi
if [ "$status" -eq 124 -o "$status" -eq 137 ]; then
# FIXME: 'timeout -v' requires coreutils-8.29; travis seems to have
# an older version. If/when travis updates, please add -v
# to the 'timeout' command above, and un-comment this out:
# if expr "$output" : ".*timeout: sending" >/dev/null; then
echo "*** TIMED OUT ***"
false
fi
if [ -n "$expected_rc" ]; then
if [ "$status" -ne "$expected_rc" ]; then
die "exit code is $status; expected $expected_rc"
fi
fi
}
#################
# log_and_run # log a command for later debugging, then run it
#################
#
# When diagnosing a test failure, it can be really nice to see the
# more important commands that have been run in test setup: openssl,
# podman registry, other complex commands that can give one a boost
# when trying to reproduce problems. This simple wrapper takes a
# command as its arg, echoes it to stdout (with a '$' prefix),
# then runs the command. BATS does not show stdout unless there's
# an error. Use this judiciously.
#
function log_and_run() {
echo "\$ $*"
"$@"
}
#########
# die # Abort with helpful message
#########
function die() {
echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2
echo "#| FAIL: $*" >&2
echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2
false
}
###################
# expect_output # Compare actual vs expected string; fail if mismatch
###################
#
# Compares $output against the given string argument. Optional second
# argument is descriptive text to show as the error message (default:
# the command most recently run by 'run_skopeo'). This text can be
# useful to isolate a failure when there are multiple identical
# run_skopeo invocations, and the difference is solely in the
# config or setup; see, e.g., run.bats:run-cmd().
#
# By default we run an exact string comparison; use --substring to
# look for the given string anywhere in $output.
#
# By default we look in "$output", which is set in run_skopeo().
# To override, use --from="some-other-string" (e.g. "${lines[0]}")
#
# Examples:
#
# expect_output "this is exactly what we expect"
# expect_output "foo=bar" "description of this particular test"
# expect_output --from="${lines[0]}" "expected first line"
#
function expect_output() {
# By default we examine $output, the result of run_skopeo
local actual="$output"
local check_substring=
# option processing: recognize --from="...", --substring
local opt
for opt; do
local value=$(expr "$opt" : '[^=]*=\(.*\)')
case "$opt" in
--from=*) actual="$value"; shift;;
--substring) check_substring=1; shift;;
--) shift; break;;
-*) die "Invalid option '$opt'" ;;
*) break;;
esac
done
local expect="$1"
local testname="${2:-${MOST_RECENT_SKOPEO_COMMAND:-[no test name given]}}"
if [ -z "$expect" ]; then
if [ -z "$actual" ]; then
return
fi
expect='[no output]'
elif [ "$actual" = "$expect" ]; then
return
elif [ -n "$check_substring" ]; then
if [[ "$actual" =~ $expect ]]; then
return
fi
fi
# This is a multi-line message, which may in turn contain multi-line
# output, so let's format it ourselves, readably
local -a actual_split
readarray -t actual_split <<<"$actual"
printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2
printf "#| FAIL: $testname\n" >&2
printf "#| expected: '%s'\n" "$expect" >&2
printf "#| actual: '%s'\n" "${actual_split[0]}" >&2
local line
for line in "${actual_split[@]:1}"; do
printf "#| > '%s'\n" "$line" >&2
done
printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2
false
}
#######################
# expect_line_count # Check the expected number of output lines
#######################
#
# ...from the most recent run_skopeo command
#
function expect_line_count() {
local expect="$1"
local testname="${2:-${MOST_RECENT_SKOPEO_COMMAND:-[no test name given]}}"
local actual="${#lines[@]}"
if [ "$actual" -eq "$expect" ]; then
return
fi
printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2
printf "#| FAIL: $testname\n" >&2
printf "#| Expected %d lines of output, got %d\n" $expect $actual >&2
printf "#| Output was:\n" >&2
local line
for line in "${lines[@]}"; do
printf "#| >%s\n" "$line" >&2
done
printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2
false
}
# END standard helpers for running skopeo and testing results
###############################################################################
# BEGIN helpers for starting/stopping registries
####################
# start_registry # Run a local registry container
####################
#
# Usage: start_registry [OPTIONS] NAME
#
# OPTIONS
# --port=NNNN Port to listen on (default: 5000)
# --testuser=XXX Require authentication; this is the username
# --testpassword=XXX ...and the password (these two go together)
# --with-cert Create a cert for running with TLS (not working)
# --enable-delete Set allowing registry deletions (default: false)
#
# NAME is the container name to assign.
#
start_registry() {
local port=5000
local testuser=
local testpassword=
local create_cert=
local enable_delete=false
# option processing: recognize options for running the registry
# in different modes.
local opt
for opt; do
local value=$(expr "$opt" : '[^=]*=\(.*\)')
case "$opt" in
--port=*) port="$value"; shift;;
--testuser=*) testuser="$value"; shift;;
--testpassword=*) testpassword="$value"; shift;;
--with-cert) create_cert=1; shift;;
--enable-delete=*) enable_delete="$value"; shift;;
-*) die "Invalid option '$opt'" ;;
*) break;;
esac
done
local name=${1?start_registry() invoked without a NAME}
# Temp directory must be defined and must exist
[[ -n $TESTDIR && -d $TESTDIR ]]
AUTHDIR=$TESTDIR/auth
mkdir -p $AUTHDIR
local -a reg_args=(-v $AUTHDIR:/auth:Z -p $port:5000)
if [[ "$enable_delete" == "true" ]]; then
reg_args+=( -e REGISTRY_STORAGE_DELETE_ENABLED=true)
fi
# TODO: This is TEMPORARY (as of 2020-03-30); remove once crun is fixed.
# Skopeo PR #836 claims there's a "regression" in crun with cgroupsv1,
# but offers no details about what it is (crun issue nor PR) nor when/if
# it's fixed. It's simply a workaround, forcing podman to use runc,
# which might work great for skopeo CI but breaks Fedora gating tests.
# Instead of always forcing runc, do so only when under cgroups v1:
local runtime=
cgroup_type=$(stat -f -c %T /sys/fs/cgroup)
if [[ $cgroup_type == "tmpfs" ]]; then
runtime="--runtime runc"
fi
# cgroup option necessary under podman-in-podman (CI tests),
# and doesn't seem to do any harm otherwise.
PODMAN="podman $runtime --cgroup-manager=cgroupfs"
# Called with --testuser? Create an htpasswd file
if [[ -n $testuser ]]; then
if [[ -z $testpassword ]]; then
die "start_registry() invoked with testuser but no testpassword"
fi
if ! egrep -q "^$testuser:" $AUTHDIR/htpasswd; then
htpasswd -Bbn $testuser $testpassword >> $AUTHDIR/htpasswd
fi
reg_args+=(
-e REGISTRY_AUTH=htpasswd
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm"
)
fi
# Called with --with-cert? Create certificates.
if [[ -n $create_cert ]]; then
CERT=$AUTHDIR/domain.crt
if [ ! -e $CERT ]; then
log_and_run openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout $AUTHDIR/domain.key -x509 -days 2 \
-out $CERT \
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \
-addext subjectAltName=DNS:localhost
fi
reg_args+=(
-e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt
-e REGISTRY_HTTP_TLS_KEY=/auth/domain.key
)
# Copy .crt file to a directory *without* the .key one, so we can
# test the client. (If client sees a matching .key file, it fails)
# Thanks to Miloslav Trmac for this hint.
mkdir -p $TESTDIR/client-auth
log_and_run cp $CERT $TESTDIR/client-auth/
fi
log_and_run $PODMAN run -d --name $name "${reg_args[@]}" $REGISTRY_FQIN
# Wait for registry to actually come up
timeout=10
while [[ $timeout -ge 1 ]]; do
if echo -n >/dev/tcp/127.0.0.1/$port; then
return
fi
timeout=$(expr $timeout - 1)
sleep 1
done
die "Timed out waiting for registry container to respond on :$port"
}
# END helpers for starting/stopping registries
###############################################################################
# BEGIN miscellaneous tools
###################
# random_string # Returns a pseudorandom human-readable string
###################
#
# Numeric argument, if present, is desired length of string
#
function random_string() {
local length=${1:-10}
head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
}
# END miscellaneous tools
###############################################################################