From bb64dca6e67a6b758aeb62eb3e2cb4a7b361017d Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 14 Jul 2020 15:39:03 +0200 Subject: [PATCH] Fix symlink snapshot (#1833) * Fix symbolic link issue with snapshot * Add tests * fix symlink * add encrypted test * Modify x --- .devcontainer/Dockerfile | 3 - .devcontainer/devcontainer.json | 16 +++-- supervisor/addons/addon.py | 2 +- supervisor/utils/tar.py | 2 +- tests/fixtures/tar_data/README.md | 3 + tests/fixtures/tar_data/test1/script.sh | 3 + tests/fixtures/tar_data/test_symlink | 1 + tests/utils/test_tarfile.py | 78 ++++++++++++++++++++++++- 8 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 tests/fixtures/tar_data/README.md create mode 100755 tests/fixtures/tar_data/test1/script.sh create mode 120000 tests/fixtures/tar_data/test_symlink diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 636d4efba..ba50711ab 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -46,6 +46,3 @@ COPY requirements.txt requirements_tests.txt ./ RUN pip3 install -r requirements.txt -r requirements_tests.txt \ && pip3 install tox \ && rm -f requirements.txt requirements_tests.txt - -# Set the default shell to bash instead of sh -ENV SHELL /bin/bash diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7b3b69016..7538fde70 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,15 +12,21 @@ "esbenp.prettier-vscode" ], "settings": { - "python.pythonPath": "/usr/local/bin/python", "terminal.integrated.shell.linux": "/bin/bash", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true, + "python.pythonPath": "/usr/local/bin/python3", "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.formatting.provider": "black", "python.formatting.blackArgs": ["--target-version", "py38"], - "editor.formatOnPaste": false, - "editor.formatOnSave": true, - "editor.formatOnType": true, - "files.trimTrailingWhitespace": true + "python.formatting.blackPath": "/usr/local/bin/black", + "python.linting.banditPath": "/usr/local/bin/bandit", + "python.linting.flake8Path": "/usr/local/bin/flake8", + "python.linting.mypyPath": "/usr/local/bin/mypy", + "python.linting.pylintPath": "/usr/local/bin/pylint", + "python.linting.pydocstylePath": "/usr/local/bin/pydocstyle" } } diff --git a/supervisor/addons/addon.py b/supervisor/addons/addon.py index bdfbbce2f..f92cfb7db 100644 --- a/supervisor/addons/addon.py +++ b/supervisor/addons/addon.py @@ -659,7 +659,7 @@ class Addon(AddonModel): # Restore data def _restore_data(): """Restore data.""" - shutil.copytree(Path(temp, "data"), self.path_data) + shutil.copytree(Path(temp, "data"), self.path_data, symlinks=True) _LOGGER.info("Restore data for addon %s", self.slug) if self.path_data.is_dir(): diff --git a/supervisor/utils/tar.py b/supervisor/utils/tar.py index 851c3dae4..8fee51964 100644 --- a/supervisor/utils/tar.py +++ b/supervisor/utils/tar.py @@ -165,7 +165,7 @@ def atomic_contents_add( continue arcpath = PurePath(arcname, directory_item.name).as_posix() - if directory_item.is_dir(): + if directory_item.is_dir() and not directory_item.is_symlink(): atomic_contents_add(tar_file, directory_item, excludes, arcpath) continue diff --git a/tests/fixtures/tar_data/README.md b/tests/fixtures/tar_data/README.md new file mode 100644 index 000000000..77793ab54 --- /dev/null +++ b/tests/fixtures/tar_data/README.md @@ -0,0 +1,3 @@ +# Tar Data + +This is just a test for backup files diff --git a/tests/fixtures/tar_data/test1/script.sh b/tests/fixtures/tar_data/test1/script.sh new file mode 100755 index 000000000..8b3cb8fb3 --- /dev/null +++ b/tests/fixtures/tar_data/test1/script.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Test Script" \ No newline at end of file diff --git a/tests/fixtures/tar_data/test_symlink b/tests/fixtures/tar_data/test_symlink new file mode 120000 index 000000000..f079749c4 --- /dev/null +++ b/tests/fixtures/tar_data/test_symlink @@ -0,0 +1 @@ +test1 \ No newline at end of file diff --git a/tests/utils/test_tarfile.py b/tests/utils/test_tarfile.py index 50838b5ea..24f6116bb 100644 --- a/tests/utils/test_tarfile.py +++ b/tests/utils/test_tarfile.py @@ -1,10 +1,17 @@ """Test Tarfile functions.""" - -from pathlib import PurePath +import os +from pathlib import Path, PurePath +import shutil +from tempfile import TemporaryDirectory import attr -from supervisor.utils.tar import _is_excluded_by_filter, secure_path +from supervisor.utils.tar import ( + SecureTarFile, + _is_excluded_by_filter, + atomic_contents_add, + secure_path, +) @attr.s @@ -61,3 +68,68 @@ def test_is_exclude_by_filter_bad(): for path_object in test_list: assert _is_excluded_by_filter(path_object, filter_list) is True + + +def test_create_pure_tar(): + """Test to create a tar file without encryption.""" + with TemporaryDirectory() as temp_dir: + temp = Path(temp_dir) + + # Prepair test folder + temp_orig = temp.joinpath("orig") + fixture_data = Path(__file__).parents[1].joinpath("fixtures/tar_data") + shutil.copytree(fixture_data, temp_orig, symlinks=True) + + # Create Tarfile + temp_tar = temp.joinpath("backup.tar") + with SecureTarFile(temp_tar, "w") as tar_file: + atomic_contents_add( + tar_file, temp_orig, excludes=[], arcname=".", + ) + + assert temp_tar.exists() + + # Restore + temp_new = temp.joinpath("new") + with SecureTarFile(temp_tar, "r") as tar_file: + tar_file.extractall(path=temp_new, members=tar_file) + + assert temp_new.is_dir() + assert temp_new.joinpath("test_symlink").is_symlink() + assert temp_new.joinpath("test1").is_dir() + assert temp_new.joinpath("test1/script.sh").is_file() + assert temp_new.joinpath("test1/script.sh").stat().st_mode == 33261 + assert temp_new.joinpath("README.md").is_file() + + +def test_create_ecrypted_tar(): + """Test to create a tar file with encryption.""" + with TemporaryDirectory() as temp_dir: + temp = Path(temp_dir) + key = os.urandom(16) + + # Prepair test folder + temp_orig = temp.joinpath("orig") + fixture_data = Path(__file__).parents[1].joinpath("fixtures/tar_data") + shutil.copytree(fixture_data, temp_orig, symlinks=True) + + # Create Tarfile + temp_tar = temp.joinpath("backup.tar") + with SecureTarFile(temp_tar, "w", key=key) as tar_file: + atomic_contents_add( + tar_file, temp_orig, excludes=[], arcname=".", + ) + + assert temp_tar.exists() + + # Restore + temp_new = temp.joinpath("new") + with SecureTarFile(temp_tar, "r", key=key) as tar_file: + tar_file.extractall(path=temp_new, members=tar_file) + + assert temp_new.is_dir() + assert temp_new.joinpath("test_symlink").is_symlink() + assert temp_new.joinpath("test1").is_dir() + assert temp_new.joinpath("test1/script.sh").is_file() + assert temp_new.joinpath("test1/script.sh").stat().st_mode == 33261 + assert temp_new.joinpath("README.md").is_file()