restic-compose-backup/src/restic_compose_backup/restic.py

139 lines
3.8 KiB
Python
Raw Normal View History

"""
Restic commands
"""
2019-11-15 13:23:56 +00:00
import logging
from typing import List, Tuple, Union
2019-11-12 11:39:49 +00:00
from subprocess import Popen, PIPE
2019-12-03 08:40:02 +00:00
from restic_compose_backup import commands
from restic_compose_backup import utils
2019-04-13 17:04:54 +00:00
2019-11-15 13:23:56 +00:00
logger = logging.getLogger(__name__)
2019-04-13 17:04:54 +00:00
2019-12-03 03:23:47 +00:00
def init_repo(repository: str):
2019-11-15 13:23:56 +00:00
"""
Attempt to initialize the repository.
Doing this after the repository is initialized
"""
2019-12-03 04:06:28 +00:00
return commands.run(restic(repository, [
2019-04-13 17:04:54 +00:00
"init",
2019-12-03 04:06:28 +00:00
]))
2019-04-13 17:04:54 +00:00
2019-12-04 21:17:42 +00:00
def backup_files(repository: str, source='/volumes'):
2019-12-03 04:06:28 +00:00
return commands.run(restic(repository, [
2019-04-13 17:04:54 +00:00
"--verbose",
"backup",
2019-11-29 00:25:00 +00:00
source,
2019-12-03 04:06:28 +00:00
]))
2019-04-13 17:04:54 +00:00
def backup_from_stdin(repository: str, filename: str, container_id: str,
source_command: List[str], environment: Union[dict, list] = None):
2019-12-03 03:23:47 +00:00
"""
Backs up from stdin running the source_command passed in within the given container.
2019-12-03 03:23:47 +00:00
It will appear in restic with the filename (including path) passed in.
"""
2019-12-03 04:06:28 +00:00
dest_command = restic(repository, [
'backup',
'--stdin',
'--stdin-filename',
filename,
])
2019-11-29 00:25:00 +00:00
client = utils.docker_client()
logger.debug('docker exec inside %s: %s', container_id, ' '.join(source_command))
# Create and start source command inside the given container
handle = client.api.exec_create(container_id, source_command, environment=environment)
exec_id = handle["Id"]
stream = client.api.exec_start(exec_id, stream=True, demux=True)
source_stderr = ""
# Create the restic process to receive the output of the source command
dest_process = Popen(dest_command, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=65536)
# Send the ouptut of the source command over to restic in the chunks received
for stdout_chunk, stderr_chunk in stream:
if stdout_chunk:
dest_process.stdin.write(stdout_chunk)
if stderr_chunk:
source_stderr += stderr_chunk.decode()
# Wait for restic to finish
stdout, stderr = dest_process.communicate()
# Ensure both processes exited with code 0
source_exit = client.api.exec_inspect(exec_id).get("ExitCode")
dest_exit = dest_process.poll()
exit_code = source_exit or dest_exit
if stdout:
commands.log_std('stdout', stdout, logging.DEBUG if exit_code == 0 else logging.ERROR)
if source_stderr:
commands.log_std(f'stderr ({source_command[0]})', source_stderr, logging.ERROR)
if stderr:
commands.log_std('stderr (restic)', stderr, logging.ERROR)
return exit_code
2019-11-29 00:25:00 +00:00
2019-12-04 21:02:53 +00:00
def snapshots(repository: str, last=True) -> Tuple[str, str]:
"""Returns the stdout and stderr info"""
2019-12-04 21:02:53 +00:00
args = ["snapshots"]
if last:
2019-12-05 10:09:36 +00:00
args.append('--last')
2019-12-04 21:02:53 +00:00
return commands.run_capture_std(restic(repository, args))
2019-04-13 17:04:54 +00:00
def is_initialized(repository: str) -> bool:
"""
Checks if a repository is initialized using snapshots command.
Note that this cannot separate between uninitalized repo
and other errors, but this method is reccomended by the restic
community.
"""
return commands.run(restic(repository, ["snapshots", '--last'])) == 0
2019-12-04 22:24:56 +00:00
def forget(repository: str, daily: str, weekly: str, monthly: str, yearly: str):
return commands.run(restic(repository, [
2019-12-04 22:24:56 +00:00
'forget',
2019-12-09 02:57:16 +00:00
'--group-by',
'paths',
2019-12-04 22:24:56 +00:00
'--keep-daily',
daily,
'--keep-weekly',
weekly,
'--keep-monthly',
monthly,
'--keep-yearly',
yearly,
]))
2019-12-04 22:24:56 +00:00
def prune(repository: str):
return commands.run(restic(repository, [
2019-12-04 22:24:56 +00:00
'prune',
]))
2019-12-04 22:24:56 +00:00
2019-12-03 03:23:47 +00:00
def check(repository: str):
2019-12-03 04:06:28 +00:00
return commands.run(restic(repository, [
2019-04-13 17:04:54 +00:00
"check",
# "--with-cache",
2019-12-03 04:06:28 +00:00
]))
2019-12-03 04:00:47 +00:00
def restic(repository: str, args: List[str]):
"""Generate restic command"""
return [
"restic",
"-r",
repository,
] + args