From 8e156969c98a7d40829eafe39bc0084c696fa1c3 Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Mon, 26 Feb 2024 20:02:35 +0100 Subject: [PATCH 1/3] Swarm mode check fails on non-standard Info responses --- cmd/backup/stop_restart.go | 15 ++++-- test/balena/Dockerfile | 76 ++++++++++++++++++++++++++++ test/balena/NOTICE | 4 ++ test/balena/config/arch/amd64.cfg | 1 + test/balena/config/arch/arm64.cfg | 1 + test/balena/config/arch/armv6.cfg | 2 + test/balena/config/arch/armv7.cfg | 1 + test/balena/config/balena-engine.cfg | 3 ++ test/balena/config/common.cfg | 20 ++++++++ test/balena/config/libc/glibc.cfg | 1 + test/balena/config/libc/musl.cfg | 1 + test/balena/docker-compose.yml | 30 +++++++++++ test/balena/run.sh | 26 ++++++++++ 13 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 test/balena/Dockerfile create mode 100644 test/balena/NOTICE create mode 100644 test/balena/config/arch/amd64.cfg create mode 100644 test/balena/config/arch/arm64.cfg create mode 100644 test/balena/config/arch/armv6.cfg create mode 100644 test/balena/config/arch/armv7.cfg create mode 100644 test/balena/config/balena-engine.cfg create mode 100644 test/balena/config/common.cfg create mode 100644 test/balena/config/libc/glibc.cfg create mode 100644 test/balena/config/libc/musl.cfg create mode 100644 test/balena/docker-compose.yml create mode 100755 test/balena/run.sh diff --git a/cmd/backup/stop_restart.go b/cmd/backup/stop_restart.go index 73af5d2c..0baae201 100644 --- a/cmd/backup/stop_restart.go +++ b/cmd/backup/stop_restart.go @@ -81,6 +81,16 @@ func awaitContainerCountForService(cli *client.Client, serviceID string, count i } } +func isSwarm(c interface { + Info(context.Context) (types.Info, error) +}) (bool, error) { + info, err := c.Info(context.Background()) + if err != nil { + return false, errwrap.Wrap(err, "error getting docker info") + } + return info.Swarm.LocalNodeState != "" && info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive, nil +} + // stopContainersAndServices stops all Docker containers that are marked as to being // stopped during the backup and returns a function that can be called to // restart everything that has been stopped. @@ -89,11 +99,10 @@ func (s *script) stopContainersAndServices() (func() error, error) { return noop, nil } - dockerInfo, err := s.cli.Info(context.Background()) + isDockerSwarm, err := isSwarm(s.cli) if err != nil { - return noop, errwrap.Wrap(err, "error getting docker info") + return noop, errwrap.Wrap(err, "error determining swarm state") } - isDockerSwarm := dockerInfo.Swarm.LocalNodeState != "inactive" labelValue := s.c.BackupStopDuringBackupLabel if s.c.BackupStopContainerLabel != "" { diff --git a/test/balena/Dockerfile b/test/balena/Dockerfile new file mode 100644 index 00000000..cb018a70 --- /dev/null +++ b/test/balena/Dockerfile @@ -0,0 +1,76 @@ +#syntax=docker/dockerfile:1.2 + +# BUILDPLATFORM is set by buildkit (DOCKER_BUILDKIT=1) +# hadolint ignore=DL3029 +FROM --platform=$BUILDPLATFORM debian:bullseye-20211220-slim AS buildroot-base + +# hadolint ignore=DL3008 +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + bc \ + build-essential \ + ca-certificates \ + cmake \ + cpio \ + file \ + git \ + locales \ + python3 \ + rsync \ + unzip \ + wget && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# hadolint ignore=DL3059 +RUN sed -i 's/# \(en_US.UTF-8\)/\1/' /etc/locale.gen && \ + /usr/sbin/locale-gen && \ + useradd -ms /bin/bash br-user && \ + chown -R br-user:br-user /home/br-user + +USER br-user + +WORKDIR /home/br-user + +ENV HOME=/home/br-user + +ENV LC_ALL=en_US.UTF-8 + +ARG BR_VERSION=2021.11 + +RUN git clone --depth 1 --branch ${BR_VERSION} https://git.busybox.net/buildroot + +FROM buildroot-base as rootfs + +WORKDIR /home/br-user/buildroot + +# set by buildkit (DOCKER_BUILDKIT=1) +ARG TARGETARCH +ARG TARGETVARIANT + +# musl or glibc (musl is smaller) +ARG ROOTFS_LIBC=musl + +COPY config ./config + +RUN support/kconfig/merge_config.sh -m \ + config/arch/"${TARGETARCH}${TARGETVARIANT}".cfg \ + config/libc/"${ROOTFS_LIBC}".cfg \ + config/*.cfg + +RUN --mount=type=cache,target=/cache,uid=1000,gid=1000,sharing=private \ + make olddefconfig && make source && make + +# hadolint ignore=DL3002 +USER root + +WORKDIR /rootfs + +RUN tar xpf /home/br-user/buildroot/output/images/rootfs.tar -C /rootfs + +FROM scratch + +COPY --from=rootfs rootfs/ / + +ENTRYPOINT [ "/usr/bin/balena-engine-daemon" ] + +CMD [ "-H", "tcp://0.0.0.0:2375" ] diff --git a/test/balena/NOTICE b/test/balena/NOTICE new file mode 100644 index 00000000..f5aff99d --- /dev/null +++ b/test/balena/NOTICE @@ -0,0 +1,4 @@ +Dockerfile and content in `config` is taken from +https://github.com/balena-io-experimental/balena-engine-docker which is +distributed under the Apache-2.0 license: +https://github.com/balena-io-experimental/balena-engine-docker/blob/532d24e746240a5d26bcd55112e8cf68dd6b8227/LICENSE diff --git a/test/balena/config/arch/amd64.cfg b/test/balena/config/arch/amd64.cfg new file mode 100644 index 00000000..447655bd --- /dev/null +++ b/test/balena/config/arch/amd64.cfg @@ -0,0 +1 @@ +BR2_x86_64=y diff --git a/test/balena/config/arch/arm64.cfg b/test/balena/config/arch/arm64.cfg new file mode 100644 index 00000000..1c771472 --- /dev/null +++ b/test/balena/config/arch/arm64.cfg @@ -0,0 +1 @@ +BR2_aarch64=y diff --git a/test/balena/config/arch/armv6.cfg b/test/balena/config/arch/armv6.cfg new file mode 100644 index 00000000..560b9a9b --- /dev/null +++ b/test/balena/config/arch/armv6.cfg @@ -0,0 +1,2 @@ +BR2_arm=y +BR2_arm1176jzf_s=y diff --git a/test/balena/config/arch/armv7.cfg b/test/balena/config/arch/armv7.cfg new file mode 100644 index 00000000..5d689c0b --- /dev/null +++ b/test/balena/config/arch/armv7.cfg @@ -0,0 +1 @@ +BR2_arm=y diff --git a/test/balena/config/balena-engine.cfg b/test/balena/config/balena-engine.cfg new file mode 100644 index 00000000..d56f2873 --- /dev/null +++ b/test/balena/config/balena-engine.cfg @@ -0,0 +1,3 @@ +BR2_PACKAGE_TINI=y +BR2_PACKAGE_BALENA_ENGINE=y +BR2_PACKAGE_CA_CERTIFICATES=y diff --git a/test/balena/config/common.cfg b/test/balena/config/common.cfg new file mode 100644 index 00000000..d4ac76b7 --- /dev/null +++ b/test/balena/config/common.cfg @@ -0,0 +1,20 @@ +BR2_TOOLCHAIN_BUILDROOT_CXX=y +BR2_KERNEL_HEADERS_4_19=y +BR2_GCC_ENABLE_LTO=y + +# We don't need init inside a container +BR2_INIT_NONE=y + +# Also unselect busybox +BR2_PACKAGE_BUSYBOX=n + +# We don't need a system shell or ifupdown-scripts +BR2_SYSTEM_BIN_SH_NONE=y +BR2_PACKAGE_IFUPDOWN_SCRIPTS=n + +# Setup cache paths +BR2_DL_DIR="/cache/dl" + +BR2_CCACHE=y +BR2_CCACHE_DIR="/cache/ccache" +BR2_CCACHE_USE_BASEDIR=y diff --git a/test/balena/config/libc/glibc.cfg b/test/balena/config/libc/glibc.cfg new file mode 100644 index 00000000..94c0be83 --- /dev/null +++ b/test/balena/config/libc/glibc.cfg @@ -0,0 +1 @@ +BR2_TOOLCHAIN_BUILDROOT_GLIBC=y diff --git a/test/balena/config/libc/musl.cfg b/test/balena/config/libc/musl.cfg new file mode 100644 index 00000000..cb38dfbc --- /dev/null +++ b/test/balena/config/libc/musl.cfg @@ -0,0 +1 @@ +BR2_TOOLCHAIN_BUILDROOT_MUSL=y diff --git a/test/balena/docker-compose.yml b/test/balena/docker-compose.yml new file mode 100644 index 00000000..33832c20 --- /dev/null +++ b/test/balena/docker-compose.yml @@ -0,0 +1,30 @@ +version: '3' + +services: + balena: + build: . + privileged: true + + backup: + image: offen/docker-volume-backup:${TEST_VERSION:-canary} + hostname: hostnametoken + restart: always + environment: + BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ? + BACKUP_FILENAME: test.tar.gz + DOCKER_HOST: tcp://balena:2375 + volumes: + - ${LOCAL_DIR:-./local}:/archive + - app_data:/backup/app_data:ro + + offen: + image: offen/offen:latest + labels: + - docker-volume-backup.stop-during-backup=true + volumes: + - app_data:/var/opt/offen + +volumes: + minio_backup_data: + name: minio_backup_data + app_data: diff --git a/test/balena/run.sh b/test/balena/run.sh new file mode 100755 index 00000000..f52b78f3 --- /dev/null +++ b/test/balena/run.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) +. ../util.sh +current_test=$(basename $(pwd)) + +export LOCAL_DIR=$(mktemp -d) + +docker compose up -d + +sleep 20 + +expect_running_containers "3" + +docker compose exec backup backup + +sleep 5 + +if [ ! -f $LOCAL_DIR/test.tar.gz ]; then + fail "Archive was not created" +fi +pass "Found expected file." + +expect_running_containers "3" From 12f2c17d8e0a6a1431ef587a11d45a4a4c855415 Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Mon, 26 Feb 2024 20:22:34 +0100 Subject: [PATCH 2/3] Add unit test --- cmd/backup/stop_restart_test.go | 85 +++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 cmd/backup/stop_restart_test.go diff --git a/cmd/backup/stop_restart_test.go b/cmd/backup/stop_restart_test.go new file mode 100644 index 00000000..ff866b31 --- /dev/null +++ b/cmd/backup/stop_restart_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "errors" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" +) + +type mockInfoClient struct { + result types.Info + err error +} + +func (m *mockInfoClient) Info(context.Context) (types.Info, error) { + return m.result, m.err +} + +func TestIsSwarm(t *testing.T) { + tests := []struct { + name string + client *mockInfoClient + expected bool + expectError bool + }{ + { + "swarm", + &mockInfoClient{ + result: types.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateActive, + }, + }, + }, + true, + false, + }, + { + "compose", + &mockInfoClient{ + result: types.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateInactive, + }, + }, + }, + false, + false, + }, + { + "balena", + &mockInfoClient{ + result: types.Info{ + Swarm: swarm.Info{ + LocalNodeState: "", + }, + }, + }, + false, + false, + }, + { + "error", + &mockInfoClient{ + err: errors.New("the dinosaurs escaped"), + }, + false, + true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := isSwarm(test.client) + if (err != nil) != test.expectError { + t.Errorf("Unexpected error value %v", err) + } + if test.expected != result { + t.Errorf("Expected %v, got %v", test.expected, result) + } + }) + } +} From f414854e22fa4efee27091512fd5e39230b544dd Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Tue, 27 Feb 2024 21:56:12 +0100 Subject: [PATCH 3/3] Remove balena tests, add note to docs --- docs/index.md | 5 ++ test/balena/Dockerfile | 76 ---------------------------- test/balena/NOTICE | 4 -- test/balena/config/arch/amd64.cfg | 1 - test/balena/config/arch/arm64.cfg | 1 - test/balena/config/arch/armv6.cfg | 2 - test/balena/config/arch/armv7.cfg | 1 - test/balena/config/balena-engine.cfg | 3 -- test/balena/config/common.cfg | 20 -------- test/balena/config/libc/glibc.cfg | 1 - test/balena/config/libc/musl.cfg | 1 - test/balena/docker-compose.yml | 30 ----------- test/balena/run.sh | 26 ---------- 13 files changed, 5 insertions(+), 166 deletions(-) delete mode 100644 test/balena/Dockerfile delete mode 100644 test/balena/NOTICE delete mode 100644 test/balena/config/arch/amd64.cfg delete mode 100644 test/balena/config/arch/arm64.cfg delete mode 100644 test/balena/config/arch/armv6.cfg delete mode 100644 test/balena/config/arch/armv7.cfg delete mode 100644 test/balena/config/balena-engine.cfg delete mode 100644 test/balena/config/common.cfg delete mode 100644 test/balena/config/libc/glibc.cfg delete mode 100644 test/balena/config/libc/musl.cfg delete mode 100644 test/balena/docker-compose.yml delete mode 100755 test/balena/run.sh diff --git a/docs/index.md b/docs/index.md index 90c43c03..83272659 100644 --- a/docs/index.md +++ b/docs/index.md @@ -100,6 +100,11 @@ docker pull ghcr.io/offen/docker-volume-backup:v2 Documentation references Docker Hub, but all examples will work using ghcr.io just as well. +### Supported Engines + +This tool is developed and tested against the Docker CE engine exclusively. +While it may work against different implementations (e.g. Balena Engine), there are no guarantees about support for non-Docker engines. + ## Differences to `jareware/docker-volume-backup` This image is heavily inspired by `jareware/docker-volume-backup`. We decided to publish this image as a simpler and more lightweight alternative because of the following requirements: diff --git a/test/balena/Dockerfile b/test/balena/Dockerfile deleted file mode 100644 index cb018a70..00000000 --- a/test/balena/Dockerfile +++ /dev/null @@ -1,76 +0,0 @@ -#syntax=docker/dockerfile:1.2 - -# BUILDPLATFORM is set by buildkit (DOCKER_BUILDKIT=1) -# hadolint ignore=DL3029 -FROM --platform=$BUILDPLATFORM debian:bullseye-20211220-slim AS buildroot-base - -# hadolint ignore=DL3008 -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - bc \ - build-essential \ - ca-certificates \ - cmake \ - cpio \ - file \ - git \ - locales \ - python3 \ - rsync \ - unzip \ - wget && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -# hadolint ignore=DL3059 -RUN sed -i 's/# \(en_US.UTF-8\)/\1/' /etc/locale.gen && \ - /usr/sbin/locale-gen && \ - useradd -ms /bin/bash br-user && \ - chown -R br-user:br-user /home/br-user - -USER br-user - -WORKDIR /home/br-user - -ENV HOME=/home/br-user - -ENV LC_ALL=en_US.UTF-8 - -ARG BR_VERSION=2021.11 - -RUN git clone --depth 1 --branch ${BR_VERSION} https://git.busybox.net/buildroot - -FROM buildroot-base as rootfs - -WORKDIR /home/br-user/buildroot - -# set by buildkit (DOCKER_BUILDKIT=1) -ARG TARGETARCH -ARG TARGETVARIANT - -# musl or glibc (musl is smaller) -ARG ROOTFS_LIBC=musl - -COPY config ./config - -RUN support/kconfig/merge_config.sh -m \ - config/arch/"${TARGETARCH}${TARGETVARIANT}".cfg \ - config/libc/"${ROOTFS_LIBC}".cfg \ - config/*.cfg - -RUN --mount=type=cache,target=/cache,uid=1000,gid=1000,sharing=private \ - make olddefconfig && make source && make - -# hadolint ignore=DL3002 -USER root - -WORKDIR /rootfs - -RUN tar xpf /home/br-user/buildroot/output/images/rootfs.tar -C /rootfs - -FROM scratch - -COPY --from=rootfs rootfs/ / - -ENTRYPOINT [ "/usr/bin/balena-engine-daemon" ] - -CMD [ "-H", "tcp://0.0.0.0:2375" ] diff --git a/test/balena/NOTICE b/test/balena/NOTICE deleted file mode 100644 index f5aff99d..00000000 --- a/test/balena/NOTICE +++ /dev/null @@ -1,4 +0,0 @@ -Dockerfile and content in `config` is taken from -https://github.com/balena-io-experimental/balena-engine-docker which is -distributed under the Apache-2.0 license: -https://github.com/balena-io-experimental/balena-engine-docker/blob/532d24e746240a5d26bcd55112e8cf68dd6b8227/LICENSE diff --git a/test/balena/config/arch/amd64.cfg b/test/balena/config/arch/amd64.cfg deleted file mode 100644 index 447655bd..00000000 --- a/test/balena/config/arch/amd64.cfg +++ /dev/null @@ -1 +0,0 @@ -BR2_x86_64=y diff --git a/test/balena/config/arch/arm64.cfg b/test/balena/config/arch/arm64.cfg deleted file mode 100644 index 1c771472..00000000 --- a/test/balena/config/arch/arm64.cfg +++ /dev/null @@ -1 +0,0 @@ -BR2_aarch64=y diff --git a/test/balena/config/arch/armv6.cfg b/test/balena/config/arch/armv6.cfg deleted file mode 100644 index 560b9a9b..00000000 --- a/test/balena/config/arch/armv6.cfg +++ /dev/null @@ -1,2 +0,0 @@ -BR2_arm=y -BR2_arm1176jzf_s=y diff --git a/test/balena/config/arch/armv7.cfg b/test/balena/config/arch/armv7.cfg deleted file mode 100644 index 5d689c0b..00000000 --- a/test/balena/config/arch/armv7.cfg +++ /dev/null @@ -1 +0,0 @@ -BR2_arm=y diff --git a/test/balena/config/balena-engine.cfg b/test/balena/config/balena-engine.cfg deleted file mode 100644 index d56f2873..00000000 --- a/test/balena/config/balena-engine.cfg +++ /dev/null @@ -1,3 +0,0 @@ -BR2_PACKAGE_TINI=y -BR2_PACKAGE_BALENA_ENGINE=y -BR2_PACKAGE_CA_CERTIFICATES=y diff --git a/test/balena/config/common.cfg b/test/balena/config/common.cfg deleted file mode 100644 index d4ac76b7..00000000 --- a/test/balena/config/common.cfg +++ /dev/null @@ -1,20 +0,0 @@ -BR2_TOOLCHAIN_BUILDROOT_CXX=y -BR2_KERNEL_HEADERS_4_19=y -BR2_GCC_ENABLE_LTO=y - -# We don't need init inside a container -BR2_INIT_NONE=y - -# Also unselect busybox -BR2_PACKAGE_BUSYBOX=n - -# We don't need a system shell or ifupdown-scripts -BR2_SYSTEM_BIN_SH_NONE=y -BR2_PACKAGE_IFUPDOWN_SCRIPTS=n - -# Setup cache paths -BR2_DL_DIR="/cache/dl" - -BR2_CCACHE=y -BR2_CCACHE_DIR="/cache/ccache" -BR2_CCACHE_USE_BASEDIR=y diff --git a/test/balena/config/libc/glibc.cfg b/test/balena/config/libc/glibc.cfg deleted file mode 100644 index 94c0be83..00000000 --- a/test/balena/config/libc/glibc.cfg +++ /dev/null @@ -1 +0,0 @@ -BR2_TOOLCHAIN_BUILDROOT_GLIBC=y diff --git a/test/balena/config/libc/musl.cfg b/test/balena/config/libc/musl.cfg deleted file mode 100644 index cb38dfbc..00000000 --- a/test/balena/config/libc/musl.cfg +++ /dev/null @@ -1 +0,0 @@ -BR2_TOOLCHAIN_BUILDROOT_MUSL=y diff --git a/test/balena/docker-compose.yml b/test/balena/docker-compose.yml deleted file mode 100644 index 33832c20..00000000 --- a/test/balena/docker-compose.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: '3' - -services: - balena: - build: . - privileged: true - - backup: - image: offen/docker-volume-backup:${TEST_VERSION:-canary} - hostname: hostnametoken - restart: always - environment: - BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ? - BACKUP_FILENAME: test.tar.gz - DOCKER_HOST: tcp://balena:2375 - volumes: - - ${LOCAL_DIR:-./local}:/archive - - app_data:/backup/app_data:ro - - offen: - image: offen/offen:latest - labels: - - docker-volume-backup.stop-during-backup=true - volumes: - - app_data:/var/opt/offen - -volumes: - minio_backup_data: - name: minio_backup_data - app_data: diff --git a/test/balena/run.sh b/test/balena/run.sh deleted file mode 100755 index f52b78f3..00000000 --- a/test/balena/run.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -e - -cd $(dirname $0) -. ../util.sh -current_test=$(basename $(pwd)) - -export LOCAL_DIR=$(mktemp -d) - -docker compose up -d - -sleep 20 - -expect_running_containers "3" - -docker compose exec backup backup - -sleep 5 - -if [ ! -f $LOCAL_DIR/test.tar.gz ]; then - fail "Archive was not created" -fi -pass "Found expected file." - -expect_running_containers "3"