Skip to content

Commit

Permalink
EXPERIMENTAL: set up a local registry, cache images
Browse files Browse the repository at this point in the history
On each test VM:
- bring up a local registry
- copy all images used by tests, from quay to this registry
- change the registries.conf used by tests so it points to this reg

Yes, this is stupid as it stands. It gains us nothing. It's just
a proof of concept. If it works, the registry setup and cache
will be moved to automation_images, so each CI VM will come
preloaded with a cache registry. And if it doesn't work, this
PR is a much much faster way to find out than a constant
flurry of spinning up new images.

Signed-off-by: Ed Santiago <santiago@redhat.com>
  • Loading branch information
edsantiago committed May 22, 2024
1 parent c9241c9 commit 03825bc
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 6 deletions.
307 changes: 307 additions & 0 deletions contrib/cirrus/local-cache-registry
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
#! /bin/bash
#
# local-cache-registry - FIXME
#
ME=$(basename $0)

###############################################################################
# BEGIN defaults

PODMAN_REGISTRY_IMAGE=quay.io/libpod/registry:2.8.2

# FIXME: refactor the hardcoded 56789
PODMAN_REGISTRY_PORT=56789

# Podman binary to run
PODMAN=${PODMAN:-/usr/bin/podman}

# END defaults
###############################################################################
# BEGIN help messages

missing=" argument is missing; see $ME -h for details"
usage="Usage: $ME [options] [start|stop|ps|logs]
$ME manages a local instance of a container registry.
When called to start a registry, $ME will pull an image
into a local temporary directory, create an htpasswd, start the
registry, and dump a series of environment variables to stdout:
\$ $ME start
PODMAN_REGISTRY_IMAGE=\"docker.io/library/registry:2.8\"
PODMAN_REGISTRY_PORT=\"5050\"
PODMAN_REGISTRY_USER=\"userZ3RZ\"
PODMAN_REGISTRY_PASS=\"T8JVJzKrcl4p6uT\"
Expected usage, therefore, is something like this in a script
eval \$($ME start)
To stop the registry, you will need to know the port number:
$ME -P \$PODMAN_REGISTRY_PORT stop
Override the default image, port, user, password with:
-i IMAGE registry image to pull (default: $PODMAN_REGISTRY_IMAGE)
-P PORT port to bind to (on 127.0.0.1) (default: random, 5000-5999)
Other options:
-h display usage message
"

die () {
echo "$ME: $*" >&2
exit 1
}

# END help messages
###############################################################################
# BEGIN option processing

while getopts "i:u:p:P:hv" opt; do
case "$opt" in
i) PODMAN_REGISTRY_IMAGE=$OPTARG ;;
P) PODMAN_REGISTRY_PORT=$OPTARG ;;
h) echo "$usage"; exit 0;;
v) verbose=1 ;;
\?) echo "Run '$ME -h' for help" >&2; exit 1;;
esac
done
shift $((OPTIND-1))

# END option processing
###############################################################################
# BEGIN helper functions

function podman() {
if [ -z "${PODMAN_REGISTRY_PORT}" ]; then
die "podman port undefined; please invoke me with -P PORT"
fi

if [ -z "${PODMAN_REGISTRY_WORKDIR}" ]; then
PODMAN_REGISTRY_WORKDIR=${TMPDIR:-/var/tmp}/podman-registry-${PODMAN_REGISTRY_PORT}
if [ ! -d ${PODMAN_REGISTRY_WORKDIR} ]; then
die "$ME: directory does not exist: ${PODMAN_REGISTRY_WORKDIR}"
fi
fi

# Reset $PODMAN, so ps/logs/stop use same args as the initial start
PODMAN="$(<${PODMAN_REGISTRY_WORKDIR}/PODMAN)"

${PODMAN} --root ${PODMAN_REGISTRY_WORKDIR}/root \
--runroot ${PODMAN_REGISTRY_WORKDIR}/runroot \
--tmpdir ${PODMAN_REGISTRY_WORKDIR}/tmp \
"$@"
}

###############
# must_pass # Run a command quietly; abort with error on failure
###############
function must_pass() {
local log=${PODMAN_REGISTRY_WORKDIR}/log

"$@" &> $log
if [ $? -ne 0 ]; then
echo "$ME: Command failed: $*" >&2
cat $log >&2

# If we ever get here, it's a given that the registry is not running.
# Clean up after ourselves.
${PODMAN} unshare rm -rf ${PODMAN_REGISTRY_WORKDIR}
exit 1
fi
}

###################
# wait_for_port # Returns once port is available on localhost
###################
function wait_for_port() {
local port=$1 # Numeric port

local host=127.0.0.1
local _timeout=5

# Wait
while [ $_timeout -gt 0 ]; do
{ exec {unused_fd}<> /dev/tcp/$host/$port; } &>/dev/null && return
sleep 1
_timeout=$(( $_timeout - 1 ))
done

die "Timed out waiting for port $port"
}

##################
# cache_images # Fetch all remote images
##################
# FIXME: persistent storage
# FIXME: start on VM boot
#
#
# FIXME! how to copy these two images?
# alpine-with-bogus-seccomp:label
# alpine-with-seccomp:label
# skopeo barfs with FATA[0000] writing manifest: uploading manifest label to 127.0.0.1:56789/libpod/alpine-with-seccomp: received unexpected HTTP status: 500 Internal Server Error
#
function cache_images() {
# FIXME! identify buildah image list too
# FIXME! move this list to somewhere more prominent
declare -a from_libpod=(
alpine:3.10.2
alpine:latest
alpine_healthcheck:latest
alpine_nginx:latest
alpine@sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00
alpine@sha256:f270dcd11e64b85919c3bab66886e59d677cf657528ac0e4805d3c71e458e525
alpine@sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f
autoupdatebroken:latest
badhealthcheck:latest
busybox:1.30.1
busybox:glibc
busybox:latest
busybox:musl
cirros:latest
healthcheck:config-only
k8s-pause:3.5
redis:alpine
registry:2.8.2
systemd-image:20240124
testdigest_v2s2
testdigest_v2s2:20200210
testimage:00000000
testimage:00000004
testimage:20240123
testimage:multiimage
testimage@sha256:1385ce282f3a959d0d6baf45636efe686c1e14c3e7240eb31907436f7bc531fa
testdigest_v2s2@sha256:755f4d90b3716e2bf57060d249e2cd61c9ac089b1233465c5c2cb2d7ee550fdb
volume-plugin-test-img:20220623
skopeo/stable:latest
)
for img in "${from_libpod[@]}"; do
# Almost all our images are under libpod; no need to repeat it
if ! expr "$img" : "^\(.*\)/" >/dev/null; then
img="libpod/$img"
fi

echo
echo "...$img"

# FIXME: add a flake retry mechanism, with backoff.
skopeo copy --all --dest-tls-verify=false \
docker://quay.io/$img \
docker://127.0.0.1:56789/$img
done
}

# END helper functions
###############################################################################
# BEGIN action processing

function do_start() {
# If called without a port, assign a random one in the 5xxx range
if [ -z "${PODMAN_REGISTRY_PORT}" ]; then
for port in $(shuf -i 5000-5999);do
if ! { exec {unused_fd}<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
PODMAN_REGISTRY_PORT=$port
break
fi
done
fi

PODMAN_REGISTRY_WORKDIR=${TMPDIR:-/var/tmp}/podman-registry-${PODMAN_REGISTRY_PORT}
if [ -d ${PODMAN_REGISTRY_WORKDIR} ]; then
die "$ME: directory exists: ${PODMAN_REGISTRY_WORKDIR} (another registry might already be running on this port)"
fi

# For the next few commands, die on any error
set -e

mkdir -p ${PODMAN_REGISTRY_WORKDIR}

# Preserve initial podman path & args, so all subsequent invocations
# of this script are consistent with the first one.
echo "$PODMAN" >${PODMAN_REGISTRY_WORKDIR}/PODMAN

local AUTHDIR=${PODMAN_REGISTRY_WORKDIR}/auth
mkdir -p $AUTHDIR

# Pull registry image, but into a separate container storage
mkdir -p ${PODMAN_REGISTRY_WORKDIR}/root
mkdir -p ${PODMAN_REGISTRY_WORKDIR}/runroot

set +e

# Give it three tries, to compensate for flakes
podman pull ${PODMAN_REGISTRY_IMAGE} &>/dev/null ||
podman pull ${PODMAN_REGISTRY_IMAGE} &>/dev/null ||
must_pass podman pull ${PODMAN_REGISTRY_IMAGE}

# Registry image needs a cert. Self-signed is good enough.
local CERT=$AUTHDIR/domain.crt
must_pass openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout ${AUTHDIR}/domain.key -x509 -days 2 \
-out ${AUTHDIR}/domain.crt \
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost"

# Run the registry container.
must_pass podman run --quiet -d \
-p ${PODMAN_REGISTRY_PORT}:5000 \
--name registry \
-v $AUTHDIR:/auth:Z \
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt" \
-e "REGISTRY_HTTP_TLS_KEY=/auth/domain.key" \
${PODMAN_REGISTRY_IMAGE}

# Confirm that registry started and port is active
wait_for_port $PODMAN_REGISTRY_PORT

cache_images
}


function do_stop() {
podman stop registry
podman rm -f registry

# Use straight podman, not our alias function, to avoid 'overlay: EBUSY'
cmd="rm -rf ${PODMAN_REGISTRY_WORKDIR}"
if [[ $(id -u) -eq 0 ]]; then
$cmd
else
${PODMAN} unshare $cmd
fi
}


function do_ps() {
podman ps -a
}


function do_logs() {
podman logs registry
}

# END action processing
###############################################################################
# BEGIN command-line processing

# First command-line arg must be an action
action=${1?ACTION$missing}
shift

case "$action" in
start) do_start ;;
stop) do_stop ;;
ps) do_ps ;;
logs) do_logs ;;
*) die "Unknown action '$action'; must be start / stop / ps / logs" ;;
esac

# END command-line processing
###############################################################################

exit 0
1 change: 1 addition & 0 deletions contrib/cirrus/runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ exec_container() {

# VM Images and Container images are built using (nearly) identical operations.
set -x
env CONTAINERS_REGISTRIES_CONF=/dev/null bin/podman pull -q $CTR_FQIN
# shellcheck disable=SC2154
exec bin/podman run --rm --privileged --net=host --cgroupns=host \
-v `mktemp -d -p /var/tmp`:/var/tmp:Z \
Expand Down
3 changes: 3 additions & 0 deletions contrib/cirrus/setup_environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@ case "$TEST_FLAVOR" in
die "Invalid value for \$TEST_ENVIRON=$TEST_ENVIRON"
fi

if ((CONTAINER==0)); then
contrib/cirrus/local-cache-registry start
fi
install_test_configs
;;
farm)
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ var _ = Describe("Podman load", func() {
Skip("skip on ppc64le")
}
outfile := filepath.Join(podmanTest.TempDir, "alpine.tar")
alpVersion := "quay.io/libpod/alpine:3.2"
alpVersion := "quay.io/libpod/alpine:3.10.2"

pull := podmanTest.Podman([]string{"pull", "-q", alpVersion})
pull.WaitWithDefaultTimeout()
Expand All @@ -169,6 +169,7 @@ var _ = Describe("Podman load", func() {
inspect := podmanTest.Podman([]string{"inspect", ALPINE})
inspect.WaitWithDefaultTimeout()
Expect(result).Should(ExitCleanly())
// FIXME: this should probably check output
inspect = podmanTest.Podman([]string{"inspect", alpVersion})
inspect.WaitWithDefaultTimeout()
Expect(result).Should(ExitCleanly())
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var _ = Describe("Podman pull", func() {

session = podmanTest.Podman([]string{"pull", "busybox:latest", "docker.io/library/ibetthisdoesnotexistfr:random", "alpine"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitWithError(125, "initializing source docker://ibetthisdoesnotexistfr:random: reading manifest random in quay.io/libpod/ibetthisdoesnotexistfr:"))
Expect(session).Should(ExitWithError(125, "initializing source docker://ibetthisdoesnotexistfr:random: reading manifest random in 127.0.0.1:56789/libpod/ibetthisdoesnotexistfr:"))

session = podmanTest.Podman([]string{"rmi", "busybox:musl", "alpine", "quay.io/libpod/cirros", "testdigest_v2s2@sha256:755f4d90b3716e2bf57060d249e2cd61c9ac089b1233465c5c2cb2d7ee550fdb"})
session.WaitWithDefaultTimeout()
Expand All @@ -38,7 +38,8 @@ var _ = Describe("Podman pull", func() {

session := podmanTest.Podman([]string{"pull", "quay.io/libpod/ibetthisdoesntexist:there"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "nitializing source docker://quay.io/libpod/ibetthisdoesntexist:there: reading manifest there in quay.io/libpod/ibetthisdoesntexist: unauthorized: access to the requested resource is not authorized"))
// FIXME: uncomfortable hardcoding of localhost:56789
Expect(session).To(ExitWithError(125, "nitializing source docker://quay.io/libpod/ibetthisdoesntexist:there: reading manifest there in 127.0.0.1:56789/libpod/ibetthisdoesntexist: manifest unknown"))
})

It("podman pull with tag --quiet", func() {
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/run_seccomp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (

var _ = Describe("Podman run", func() {

BeforeEach(func() {
Skip("FIXME-temporary, until Ed can figure out how to local-cache the seccomp images")
})

It("podman run --seccomp-policy default", func() {
session := podmanTest.Podman([]string{"run", "-q", "--seccomp-policy", "default", alpineSeccomp, "ls"})
session.WaitWithDefaultTimeout()
Expand Down
11 changes: 9 additions & 2 deletions test/registries.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ unqualified-search-registries = ['docker.io', 'quay.io', 'registry.fedoraproject
# unqualified `docker.io` images.
# Ref: https://cloud.google.com/container-registry/docs/pulling-cached-images
prefix="docker.io"
location="mirror.gcr.io"
location="127.0.0.1:56789"
insecure=true

[[registry]]
prefix="quay.io"
location="127.0.0.1:56789"
insecure=true

# 2020-10-27 a number of images are not present in gcr.io, and podman
# barfs spectacularly when trying to fetch them. We've hand-copied
# those to quay, using skopeo copy --all ...
[[registry]]
prefix="docker.io/library"
location="quay.io/libpod"
location="127.0.0.1:56789/libpod"
insecure=true

# For testing #11933 to make sure that registries.conf is consulted unless
# --tls-verify is used during container creation.
Expand Down
Loading

0 comments on commit 03825bc

Please sign in to comment.