use contract test for backup
This commit is contained in:
parent
bcc219a111
commit
fb60451820
7 changed files with 224 additions and 37 deletions
|
|
@ -136,6 +136,7 @@
|
|||
// (vm_test "restic" ./test/blocks/restic.nix)
|
||||
// (vm_test "ssl" ./test/blocks/ssl.nix)
|
||||
|
||||
// (vm_test "contracts-backup" ./test/contracts/backup.nix)
|
||||
// (vm_test "contracts-databasebackup" ./test/contracts/databasebackup.nix)
|
||||
// (vm_test "contracts-secret" ./test/contracts/secret.nix)
|
||||
));
|
||||
|
|
|
|||
|
|
@ -333,9 +333,17 @@ in
|
|||
environment.systemPackages = let
|
||||
mkResticBinary = name: instance:
|
||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||
set -euo pipefail
|
||||
|
||||
export $(grep -v '^#' "/run/secrets_restic_env/${fullName name instance.settings.repository}" \
|
||||
| xargs -d '\n')
|
||||
${pkgs.restic}/bin/restic $@
|
||||
|
||||
if ! [ "$1" = "restore" ]; then
|
||||
sudo --preserve-env -u ${instance.request.user} ${pkgs.restic}/bin/restic $@
|
||||
else
|
||||
shift
|
||||
sudo --preserve-env -u ${instance.request.user} sh -c "${pkgs.restic}/bin/restic restore $@ --target /"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
flatten (mapAttrsToList mkResticBinary cfg.instances);
|
||||
|
|
@ -346,15 +354,11 @@ in
|
|||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||
set -euo pipefail
|
||||
|
||||
ls /run/secrets_restic_env/${fullName name instance.settings.repository}
|
||||
|
||||
export $(grep -v '^#' "/run/secrets_restic_env/${fullName name instance.settings.repository}" \
|
||||
| xargs -d '\n')
|
||||
|
||||
set -x
|
||||
|
||||
if ! [ "$1" = "restore" ]; then
|
||||
sudo -u ${instance.request.user} ${pkgs.restic}/bin/restic $@
|
||||
sudo --preserve-env -u ${instance.request.user} ${pkgs.restic}/bin/restic $@
|
||||
else
|
||||
shift
|
||||
sudo --preserve-env -u ${instance.request.user} sh -c "${pkgs.restic}/bin/restic dump $@ ${instance.request.backupFile} | ${instance.request.restoreCmd}"
|
||||
|
|
|
|||
|
|
@ -1,46 +1,62 @@
|
|||
{ lib, ... }:
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) anything listOf nonEmptyListOf nullOr submodule str;
|
||||
inherit (lib.types) anything listOf nonEmptyListOf submodule str;
|
||||
in
|
||||
submodule {
|
||||
freeformType = anything;
|
||||
{
|
||||
request = submodule {
|
||||
freeformType = anything;
|
||||
|
||||
options = {
|
||||
user = mkOption {
|
||||
description = "Unix user doing the backups.";
|
||||
type = str;
|
||||
};
|
||||
options = {
|
||||
user = mkOption {
|
||||
description = "Unix user doing the backups.";
|
||||
type = str;
|
||||
};
|
||||
|
||||
sourceDirectories = mkOption {
|
||||
description = "Directories to backup.";
|
||||
type = nonEmptyListOf str;
|
||||
};
|
||||
sourceDirectories = mkOption {
|
||||
description = "Directories to backup.";
|
||||
type = nonEmptyListOf str;
|
||||
};
|
||||
|
||||
excludePatterns = mkOption {
|
||||
description = "Patterns to exclude.";
|
||||
type = listOf str;
|
||||
default = [];
|
||||
};
|
||||
excludePatterns = mkOption {
|
||||
description = "Patterns to exclude.";
|
||||
type = listOf str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
hooks = mkOption {
|
||||
description = "Hooks to run around the backup.";
|
||||
default = {};
|
||||
type = submodule {
|
||||
options = {
|
||||
before_backup = mkOption {
|
||||
description = "Hooks to run before backup";
|
||||
type = listOf str;
|
||||
default = [];
|
||||
};
|
||||
hooks = mkOption {
|
||||
description = "Hooks to run around the backup.";
|
||||
default = {};
|
||||
type = submodule {
|
||||
options = {
|
||||
before_backup = mkOption {
|
||||
description = "Hooks to run before backup";
|
||||
type = listOf str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
after_backup = mkOption {
|
||||
description = "Hooks to run after backup";
|
||||
type = listOf str;
|
||||
default = [];
|
||||
after_backup = mkOption {
|
||||
description = "Hooks to run after backup";
|
||||
type = listOf str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
result = submodule {
|
||||
options = {
|
||||
restoreScript = mkOption {
|
||||
description = "Name of script that can restore the database.";
|
||||
type = str;
|
||||
};
|
||||
|
||||
backupService = mkOption {
|
||||
description = "Name of service backing up the database.";
|
||||
type = str;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
121
modules/contracts/backup/test.nix
Normal file
121
modules/contracts/backup/test.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
pkgs' = pkgs;
|
||||
|
||||
testLib = pkgs.callPackage ../../../test/common.nix {};
|
||||
|
||||
inherit (lib) concatStringsSep concatMapStringsSep getAttrFromPath mkIf optionalAttrs setAttrByPath;
|
||||
inherit (testLib) indent;
|
||||
in
|
||||
{ name,
|
||||
providerRoot,
|
||||
modules ? [],
|
||||
username ? "me",
|
||||
sourceDirectories ? [
|
||||
"/opt/files/A"
|
||||
"/opt/files/B"
|
||||
],
|
||||
settings, # repository -> attrset
|
||||
}: pkgs.testers.runNixOSTest {
|
||||
inherit name;
|
||||
|
||||
nodes.machine = { config, ... }: {
|
||||
imports = ( testLib.baseImports pkgs' ) ++ modules;
|
||||
|
||||
config = lib.mkMerge [
|
||||
(setAttrByPath providerRoot {
|
||||
request = {
|
||||
inherit sourceDirectories;
|
||||
user = username;
|
||||
};
|
||||
settings = settings "/opt/repos/${name}";
|
||||
})
|
||||
(mkIf (username != "root") {
|
||||
users.users.${username} = {
|
||||
isSystemUser = true;
|
||||
extraGroups = [ "sudoers" ];
|
||||
group = "root";
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
extraPythonPackages = p: [ p.dictdiffer ];
|
||||
skipTypeCheck = true;
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
provider = getAttrFromPath providerRoot nodes.machine;
|
||||
backupService = provider.result.backupService;
|
||||
restoreScript = provider.result.restoreScript;
|
||||
onAllSourceDirectories = f: concatMapStringsSep "\n" (path: indent 4 (f path)) sourceDirectories;
|
||||
in ''
|
||||
from dictdiffer import diff
|
||||
|
||||
username = "${username}"
|
||||
sourceDirectories = [ ${concatMapStringsSep ", " (x: ''"${x}"'') sourceDirectories} ]
|
||||
|
||||
def list_files(dir):
|
||||
files_and_content = {}
|
||||
|
||||
files = machine.succeed(f"""find {dir} -type f""").split("\n")[:-1]
|
||||
|
||||
for f in files:
|
||||
content = machine.succeed(f"""cat {f}""").strip()
|
||||
files_and_content[f] = content
|
||||
|
||||
return files_and_content
|
||||
|
||||
def assert_files(dir, files):
|
||||
result = list(diff(list_files(dir), files))
|
||||
if len(result) > 0:
|
||||
raise Exception("Unexpected files:", result)
|
||||
|
||||
with subtest("Create initial content"):
|
||||
for path in sourceDirectories:
|
||||
machine.succeed(f"""
|
||||
mkdir -p {path}
|
||||
echo repo_fileA_1 > {path}/fileA
|
||||
echo repo_fileB_1 > {path}/fileB
|
||||
|
||||
chown {username}: -R {path}
|
||||
chmod go-rwx -R {path}
|
||||
""")
|
||||
|
||||
for path in sourceDirectories:
|
||||
assert_files(path, {
|
||||
f'{path}/fileA': 'repo_fileA_1',
|
||||
f'{path}/fileB': 'repo_fileB_1',
|
||||
})
|
||||
|
||||
with subtest("First backup in repo"):
|
||||
print(machine.succeed("systemctl cat ${backupService}"))
|
||||
machine.succeed("systemctl start ${backupService}")
|
||||
|
||||
with subtest("New content"):
|
||||
for path in sourceDirectories:
|
||||
machine.succeed(f"""
|
||||
echo repo_fileA_2 > {path}/fileA
|
||||
echo repo_fileB_2 > {path}/fileB
|
||||
""")
|
||||
|
||||
assert_files(path, {
|
||||
f'{path}/fileA': 'repo_fileA_2',
|
||||
f'{path}/fileB': 'repo_fileB_2',
|
||||
})
|
||||
|
||||
with subtest("Delete content"):
|
||||
for path in sourceDirectories:
|
||||
machine.succeed(f"""rm -r {path}/*""")
|
||||
|
||||
assert_files(path, {})
|
||||
|
||||
with subtest("Restore initial content from repo"):
|
||||
machine.succeed("""${restoreScript} restore latest""")
|
||||
|
||||
for path in sourceDirectories:
|
||||
assert_files(path, {
|
||||
f'{path}/fileA': 'repo_fileA_1',
|
||||
f'{path}/fileB': 'repo_fileB_1',
|
||||
})
|
||||
'';
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ in
|
|||
{
|
||||
request = submodule {
|
||||
freeformType = anything;
|
||||
|
||||
options = {
|
||||
user = mkOption {
|
||||
description = "Unix user doing the backups.";
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@
|
|||
test = {
|
||||
secret = import ./secret/test.nix { inherit pkgs lib; };
|
||||
databasebackup = import ./databasebackup/test.nix { inherit pkgs lib; };
|
||||
backup = import ./backup/test.nix { inherit pkgs lib; };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
43
test/contracts/backup.nix
Normal file
43
test/contracts/backup.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../../modules/contracts {};
|
||||
in
|
||||
{
|
||||
restic_root = contracts.test.backup {
|
||||
name = "restic_root";
|
||||
username = "root";
|
||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
||||
modules = [
|
||||
../../modules/blocks/restic.nix
|
||||
];
|
||||
settings = repository: {
|
||||
enable = true;
|
||||
passphraseFile = toString (pkgs.writeText "passphrase" "PassPhrase");
|
||||
repository = {
|
||||
path = repository;
|
||||
timerConfig = {
|
||||
OnCalendar = "00:00:00";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
restic_me = contracts.test.backup {
|
||||
name = "restic_me";
|
||||
username = "me";
|
||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
||||
modules = [
|
||||
../../modules/blocks/restic.nix
|
||||
];
|
||||
settings = repository: {
|
||||
enable = true;
|
||||
passphraseFile = toString (pkgs.writeText "passphrase" "PassPhrase");
|
||||
repository = {
|
||||
path = repository;
|
||||
timerConfig = {
|
||||
OnCalendar = "00:00:00";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue