Skip to content
Snippets Groups Projects
Commit a5b469f6 authored by nimrod's avatar nimrod
Browse files

Recreate the backup script with shell, AWK, reg and skopeo.

A few reasons. First of all, it doesn't require a running Docker daemon
instead outputting directly to a file (faster and saves space). Also,
the restore script will probably use skopeo so this the codebase is more
uniform. Without the Docker daemon it can run with lower privileges.
Lastly, it should work without setting the really high timeout that bugs
me a little.
parent 7c0fdbeb
No related branches found
No related tags found
No related merge requests found
Pipeline #2394 passed
...@@ -43,11 +43,10 @@ backup: ...@@ -43,11 +43,10 @@ backup:
- >- - >-
docker run docker run
--volume /var/backups/registry:/var/backups/registry --volume /var/backups/registry:/var/backups/registry
--volume /run/docker.sock:/run/docker.sock --user nobody
--rm
registry.shore.co.il/registry-backup registry.shore.co.il/registry-backup
backup registry.shore.co.il /var/backups/registry backup registry.shore.co.il /var/backups/registry
after_script:
- docker image prune -f
retry: retry:
max: 2 max: 2
timeout: 3h timeout: 3h
* *
!backup !backup
!restore
FROM docker.io/library/alpine:3.14 FROM docker.io/library/alpine:3.14
# hadolint ignore=DL3018 # hadolint ignore=DL3018
RUN apk add --update --no-cache docker-py RUN echo 'https://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories && \
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \
apk add --update --no-cache \
skopeo \
reg \
;
COPY --chown=root:root backup /usr/local/bin/backup COPY --chown=root:root backup /usr/local/bin/backup
COPY --chown=root:root restore /usr/local/bin/restore
#!/usr/bin/env python3 #!/bin/sh
"""Backup a container image registry.""" set -eu
usage() {
import argparse echo "$0: REGISTRY_DOMAIN BACKUP_DEST"
import pathlib }
import sys
import docker if [ "${1:-}" = -h ] || [ "${1:-}" = --help ]
import requests then
usage
exit 0
def get_images(registry): fi
"""Return a list of images from the registry."""
return requests.get(f"https://{registry}/v2/_catalog").json()[ if [ "$#" -ne 2 ]
"repositories" then
] usage
exit 1
fi
def get_image_tags(registry, image):
"""Return a list of tags for an image in the registry.""" registry="$1"
return requests.get(f"https://{registry}/v2/{image}/tags/list").json()[ dest="$2"
"tags"
] mkdir -p "$dest"
reg ls "$registry" | \
def backup(docker_client, registry, image, tag, dest): sed 's/,//g' | \
"""Backup an image tag from the registry to a file in the destination awk -v "registry=$registry" -v "dest=$dest" '
directory.""" BEGIN {
full_name = f"{registry}/{image}:{tag}" exitcode = 0
print(f"Backing up {full_name}.", file=sys.stderr) }
try: NR>2 {
docker_client.images.get(full_name) system("mkdir -p " dest "/" $1)
image_existed = True for (i=2; i<=NF; i++) {
except docker.errors.ImageNotFound: image_url = registry "/" $1 ":" $(i)
image_existed = False image_file = dest "/" $1 "/" $(i) ".tar"
docker_image = docker_client.images.pull(full_name) printf "Saving %s to %s.\n", image_url, image_file
with open(dest / f"{tag}.tar", "wb") as tarball: system("rm " image_file)
for chunk in docker_image.save(): if (system("skopeo copy docker://" image_url " docker-archive://" image_file) == 0)
tarball.write(chunk) printf "Backup of %s was successful.\n", image_url
if not image_existed: else {
docker_client.images.remove(full_name) exitcode = 1
printf "Backup of %s failed, continuing with other images.\n", image_url
}
def backup_registry(registry, dest): }
"""Backup the images in the registry to the destination.""" }
docker_client = docker.from_env(timeout=600) END {
docker_client.ping() exit retruncode
for image in get_images(registry): }
(dest / image).mkdir(exist_ok=True) '
for tag in get_image_tags(registry, image):
backup(docker_client, registry, image, tag, dest / image)
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser(description=__doc__)
arg_parser.add_argument("registry", help="FQDN of the registry to backup.")
arg_parser.add_argument(
"destination", help="Location to store the images.", type=pathlib.Path
)
args = arg_parser.parse_args()
destination = args.destination.expanduser()
if not destination.exists() or not destination.is_dir():
arg_parser.error(
"Backup destination doesn't exists or isn't a directory."
)
try:
destination.touch()
except IsADirectoryError:
arg_parser.error("Can't write to the backup destination.")
try:
backup_registry(args.registry, destination)
except Exception as exception:
arg_parser.error(str(exception))
sys.exit()
#!/bin/sh
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment