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 "restic" ./test/blocks/restic.nix)
|
||||||
// (vm_test "ssl" ./test/blocks/ssl.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-databasebackup" ./test/contracts/databasebackup.nix)
|
||||||
// (vm_test "contracts-secret" ./test/contracts/secret.nix)
|
// (vm_test "contracts-secret" ./test/contracts/secret.nix)
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -333,9 +333,17 @@ in
|
||||||
environment.systemPackages = let
|
environment.systemPackages = let
|
||||||
mkResticBinary = name: instance:
|
mkResticBinary = name: instance:
|
||||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
export $(grep -v '^#' "/run/secrets_restic_env/${fullName name instance.settings.repository}" \
|
export $(grep -v '^#' "/run/secrets_restic_env/${fullName name instance.settings.repository}" \
|
||||||
| xargs -d '\n')
|
| 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
|
in
|
||||||
flatten (mapAttrsToList mkResticBinary cfg.instances);
|
flatten (mapAttrsToList mkResticBinary cfg.instances);
|
||||||
|
|
@ -346,15 +354,11 @@ in
|
||||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||||
set -euo pipefail
|
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}" \
|
export $(grep -v '^#' "/run/secrets_restic_env/${fullName name instance.settings.repository}" \
|
||||||
| xargs -d '\n')
|
| xargs -d '\n')
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
if ! [ "$1" = "restore" ]; then
|
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
|
else
|
||||||
shift
|
shift
|
||||||
sudo --preserve-env -u ${instance.request.user} sh -c "${pkgs.restic}/bin/restic dump $@ ${instance.request.backupFile} | ${instance.request.restoreCmd}"
|
sudo --preserve-env -u ${instance.request.user} sh -c "${pkgs.restic}/bin/restic dump $@ ${instance.request.backupFile} | ${instance.request.restoreCmd}"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption;
|
inherit (lib) mkOption;
|
||||||
inherit (lib.types) anything listOf nonEmptyListOf nullOr submodule str;
|
inherit (lib.types) anything listOf nonEmptyListOf submodule str;
|
||||||
in
|
in
|
||||||
submodule {
|
{
|
||||||
|
request = submodule {
|
||||||
freeformType = anything;
|
freeformType = anything;
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -43,4 +44,19 @@ submodule {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
request = submodule {
|
||||||
freeformType = anything;
|
freeformType = anything;
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
description = "Unix user doing the backups.";
|
description = "Unix user doing the backups.";
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@
|
||||||
test = {
|
test = {
|
||||||
secret = import ./secret/test.nix { inherit pkgs lib; };
|
secret = import ./secret/test.nix { inherit pkgs lib; };
|
||||||
databasebackup = import ./databasebackup/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