diff --git a/test/docker-vackup/LICENSE.txt b/test/docker-vackup/LICENSE.txt new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/test/docker-vackup/LICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/test/docker-vackup/README.md b/test/docker-vackup/README.md new file mode 100644 index 0000000..035fa00 --- /dev/null +++ b/test/docker-vackup/README.md @@ -0,0 +1,62 @@ +# Vackup: Backup and Restore Docker Volumes + +[![Lint Code Base](https://github.com/BretFisher/docker-vackup/actions/workflows/linter.yml/badge.svg)](https://github.com/BretFisher/docker-vackup/actions/workflows/linter.yml) + +**This is now an [Official Docker Desktop Extension called "Volumes Backup & Share"](https://hub.docker.com/extensions/docker/volumes-backup-extension) which has more features, but I'll keep this repo around for historial purposes.** + +Vackup: (contraction of "volume backup") + +Easily backup and restore Docker volumes using either tarballs or container images. +It's designed for running from any host/container where you have the docker CLI. + +Note that for open files like databases, +it's usually better to use their preferred backup tool to create a backup file, +but if you stored that file on a Docker volume, +this could still be a way you get the Docker volume into a image or tarball +for moving to remote storage for safe keeping. + +`export`/`import` commands copy files between a local tarball and a volume. +For making volume backups and restores. + +`save`/`load` commands copy files between an image and a volume. +For when you want to use image registries as a way to push/pull volume data. + +Usage: + +`vackup export VOLUME FILE` + Creates a gzip'ed tarball in current directory from a volume + +`vackup import FILE VOLUME` + Extracts a gzip'ed tarball into a volume + +`vackup save VOLUME IMAGE` + Copies the volume contents to a busybox image in the /volume-data directory + +`vackup load IMAGE VOLUME` + Copies /volume-data contents from an image to a volume + +## Install + +Download the `vackup` file in this repository to your local machine in your shell path and make it executable. + +```shell +sudo curl -sSL https://raw.githubusercontent.com/BretFisher/docker-vackup/main/vackup -o /usr/local/bin/vackup && sudo chmod +x /usr/local/bin/vackup +``` + +## Error conditions + +If any of the commands fail, the script will check to see if a `VACKUP_FAILURE_SCRIPT` +environment variable is set. If so it will run it and pass the line number the error +happened on and the exit code from the failed command. Eg, + +```shell +# /opt/bin/vackup-failed.sh +LINE_NUMBER=$1 +EXIT_CODE=$2 +send_slack_webhook "Vackup failed on line number ${LINE_NUMBER} with exit code ${EXIT_CODE}!" +``` + +```shell +export VACKUP_FAILURE_SCRIPT=/opt/bin/vackup-failed.sh +./vackup export ...... +``` diff --git a/test/docker-vackup/vackup b/test/docker-vackup/vackup new file mode 100755 index 0000000..168a963 --- /dev/null +++ b/test/docker-vackup/vackup @@ -0,0 +1,189 @@ +#!/bin/bash +# Docker Volume File Backup and Restore Tool +# Easily tar up a volume on a local (or remote) engine +# Inspired by CLIP from Lukasz Lach + +set -Eeo pipefail + +handle_error() { + exit_code=$? + if [ -n "${VACKUP_FAILURE_SCRIPT}" ]; then + /bin/bash "${VACKUP_FAILURE_SCRIPT}" "$1" $exit_code + fi + exit $exit_code +} + +trap 'handle_error $LINENO' ERR + +usage() { +cat <= 2: env_var = parameter[0] value = parameter[1] - list_doc['services']['openwrt']['environment'][env_var] = value + if env_var != "": + list_doc['services']['openwrt']['environment'][env_var] = value + + # Load volume data + if len(parameter) == 3: + # We don't care about the first two parameters, see above + # Let's use only the third one + data_volume_backup = parameter[2] + vackup_dir = os.path.join(str(pytestconfig.rootdir), "docker-vackup") + vackup = os.path.join(vackup_dir, "vackup") # Funny name ;-) + + os.system(f"cd {vackup_dir}/../test_data/ && {vackup} import {data_volume_backup} {docker_compose_project_name}_data >/dev/null 2>/dev/null") # Save docker-compose file with open(compose_file, "w") as f: @@ -183,6 +194,16 @@ def is_openwrt_booted(): else: return False +def get_logs(): + process = subprocess.run(['docker','logs','openwrt'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + + return process.stdout + process.stderr + +def is_specific_log(log_text): + return log_text in get_logs() # ************************ Tests ************************ @@ -292,7 +313,7 @@ def test_openwrt_wan(docker_services, parameter): return case _: # Usage of real Ethernet interface e.g. 'eth0' - # This test is most likely only working in a github action enviroment because multiple VM are necessary to test it. See the action file, please + # This test is most likely only working in a github action environment because multiple VM are necessary to test it. See the action file, please # Try to get IP address from WAN-VM response = run_openwrt_shell_command("udhcpc", "-i", "eth1") assert ('udhcpc: setting default routers: 192.168.22.1' in response['out-data']) == True @@ -333,6 +354,7 @@ def test_cpu_num(docker_services, parameter): assert parameter[1] == cpu_num + def test_additional_installed_packages(docker_services): # Get list of packages to be installed in OpenWrt @@ -363,3 +385,44 @@ def test_additional_installed_packages(docker_services): # Check if all additional OpenWrt packages in Docker image are installed in OpenWrt for package in package_list_docker: assert any(package in s for s in package_list_openwrt_full) == True + + +@pytest.mark.parametrize("parameter", + [('','','20240922_test_volume_openwrt_23.05.4.tar.gz')], indirect=True, + ids=['20240922_test_volume_openwrt_23.05.4']) +def test_openwrt_migrate_existing_volume(docker_services): + docker_services.wait_until_responsive( + timeout=90.0, pause=1, check=lambda: is_specific_log('Booting image using QEMU emulator') + ) + + assert ('upgrade: Saving config files' in get_logs()) == True + assert ('upgrade: Restoring config files' in get_logs()) == True + + +@pytest.mark.parametrize("parameter", + [('','','20240922_test_volume_openwrt_23.05.4.tar.gz')], indirect=True, + ids=['20240922_test_volume_openwrt_23.05.4']) +def test_openwrt_migrate_settings(docker_services): + docker_services.wait_until_responsive( + timeout=90.0, pause=1, check=lambda: is_openwrt_booted() + ) + + # Here we test a custom configuration that is stored in the existing volume. + # Currently hostname and an additional interface are configured. + + # Check for host name 'TestRouter' + response = run_openwrt_shell_command("uci", "get", "system.@system[0].hostname") + assert ('TestRouter' in response['out-data']) == True + + # Check for test LAN + response = run_openwrt_shell_command("uci", "show", "network.TestLan") + assert ("network.TestLan.ipaddr='192.168.40.1'" in response['out-data']) == True + + +def test_kvm(docker_services): + docker_services.wait_until_responsive( + timeout=90.0, pause=1, check=lambda: is_specific_log('Checking for KVM') + ) + + time.sleep(1) + assert ('KVM detected' in get_logs()) == True \ No newline at end of file