import json
from pathlib import Path
from django.conf import settings
from django.template.defaultfilters import filesizeformat
from ...utils import hashs
from ..dumper import Dumper
from .base import BaseHandler
[docs]
class DumpCommandHandler(BaseHandler):
"""
Abstraction layer between Dumper and end interfaces, it contains getters
to get and validate options values and provide a shortand to dump.
This relies on ``logger`` attribute that is not provided here. The logger object
should be one of compatible classes from ``diskette.utils.loggers``.
"""
[docs]
def get_archive_destination(self, path=None):
"""
Either get the archive destination path from given argument if given else from
``settings.DISKETTE_DUMP_PATH``.
Arguments:
path (Path): Path object to a directory.
Returns:
Path: Discovered path.
"""
path = path or settings.DISKETTE_DUMP_PATH or Path.cwd()
if not path:
self.logger.critical("Destination path can not be an empty value")
self.logger.debug(
"- Tarball will be written into: {}".format(path)
)
return path
[docs]
def get_archive_filename(self, filename=None):
"""
Either get the archive destination filename from given argument if given else
from ``settings.DISKETTE_DUMP_FILENAME``.
Keyword Arguments:
filename (string): Filename.
Returns:
Path: Discovered filename.
"""
filename = filename or settings.DISKETTE_DUMP_FILENAME
if not filename:
self.logger.critical("Destination filename can not be an empty value")
self.logger.debug(
"- Tarball filename pattern: {}".format(filename)
)
return filename
[docs]
def get_application_configurations(self, appconfs=None, no_data=False):
"""
Either get the application configurations from ``appconfs`` value if not empty,
else from ``settings.DISKETTE_APPS``.
Keyword Arguments:
appconfs (string or list or Path): Either:
* A list which includes application configurations;
* A string which holds valid JSON;
* A Path object to a JSON file to open to get application
configurations;
If None or any empty value, the setting will be used.
no_data (boolean): Value of argument to explicitely disable data dump.
Returns:
tuple: A boolean value that is True if dump is enabled and a list of
application configuration has been discovered.
"""
if no_data is True:
self.logger.debug("- Data dump is disabled")
return False, []
apps = []
if isinstance(appconfs, Path):
apps = json.loads(appconfs.read_text())
elif isinstance(appconfs, str) and appconfs:
apps = json.loads(appconfs)
elif isinstance(appconfs, list):
apps = appconfs
apps = apps or settings.DISKETTE_APPS
if apps:
self.logger.debug("- Data dump enabled for application:")
for i, item in enumerate(apps, start=1):
msg = " ├── {}" if i < len(apps) else " └── {}"
self.logger.debug(msg.format(item[0]))
else:
self.logger.debug("- No application defined, data dump is disabled.")
return False, []
return True, apps
[docs]
def get_storage_paths(self, paths=None, no_storages=False):
"""
Returns given storage paths from arguments if there is any else use
``settings.DISKETTE_STORAGES``.
Keyword Arguments:
paths (list): List of Path object for storage paths to use instead of the
ones from settings. If empty, the paths defined in settings are used.
no_storages (boolean): Value of argument to explicitely disable storage
dump.
Returns:
tuple: A boolean value that is True if dump is enabled and a list of
storage Path objects.
"""
if no_storages is True:
self.logger.debug("- Storage dump is disabled")
return False, []
storages = paths or settings.DISKETTE_STORAGES
if storages:
self.logger.debug("- Storage dump enabled for:")
for i, item in enumerate(storages, start=1):
msg = " ├── {}" if i < len(storages) else " └── {}"
self.logger.debug(msg.format(item))
else:
self.logger.debug("- No storage defined, storage dump is disabled.")
return False, []
return True, storages
[docs]
def get_storage_excludes(self, patterns=None, no_patterns=False):
"""
Get exclude patterns either from args if given, else use the default ones from
``settings.DISKETTE_STORAGES_EXCLUDES``.
Arguments:
patterns (list): List of patterns to use instead of the ones from settings.
If empty, the paths defined in settings are used.
no_patterns (boolean): Value of argument to explicitely disable storage
excluding patterns usage.
Returns:
tuple: A boolean value that is True if pattern usage is enabled and a list
of patterns to use if any.
"""
if no_patterns is True:
self.logger.debug("- Storage exclude patterns are disabled")
return False, []
patterns = patterns or settings.DISKETTE_STORAGES_EXCLUDES
if patterns:
self.logger.debug("- Storage exclude patterns enabled:")
for i, item in enumerate(patterns, start=1):
msg = " ├── {}" if i < len(patterns) else " └── {}"
self.logger.debug(msg.format(item))
else:
self.logger.debug("- No storage exclude patterns defined.")
return False, []
return True, patterns
[docs]
def script(self, archive_destination=None, application_configurations=None,
storages=None, storages_basepath=None, storages_excludes=None,
no_data=False, no_storages=False, no_storages_excludes=False,
indent=None):
"""
Create shellscript command lines to dump data and storages.
.. Note::
This does not involve argument validation methods like
``get_archive_destination`` and others here.
Keyword Arguments:
archive_destination (Path): Path where the archive will be written. If not
given the value from setting ``DISKETTE_DUMP_PATH`` will be used
instead.
filename (string): Custom archive filename to use instead of the default
one. Your custom filename must end with ``.tar.gz``. Default filename
is ``diskette[_data][_storages].tar.gz`` (parts depend from options).
application_configurations (string or list or Path): Either:
* A list which includes application configurations;
* A string which holds valid JSON;
* A Path object to a JSON file to open to get application
configurations;
If None or any empty value, the setting will be used.
storages (list): A list of storage Path objects.
storages_basepath (Path): Basepath for reference in some path resolution.
Currently used by storage dump to make relative path for storage files.
On default this is based on current working directory. If given, the
storage paths must be in the same leaf else this will be an error.
storages_excludes (list): A list of patterns to exclude storage files from
dump.
no_data (boolean): Disable dump of application datas.
no_storages (boolean): Disable dump of media storages.
no_storages_excludes (boolean): Disable usage of excluding patterns when
collecting storages files.
indent (integer): Indentation level in data dumps. If not given, dumps won't
be indented.
Returns:
string: All commands to dump data, each command on its line with a previous
comment line with the dump name.
"""
self.logger.info("=== Starting script ===")
self.log_diskette_version()
archive_destination = self.get_archive_destination(archive_destination)
with_data, application_configurations = self.get_application_configurations(
appconfs=application_configurations,
no_data=no_data
)
with_storages, storages = self.get_storage_paths(storages, no_storages)
with_storages_excludes = None
if with_storages:
with_storages_excludes, storages_excludes = self.get_storage_excludes(
storages_excludes,
no_patterns=no_storages_excludes,
)
else:
storages_excludes = []
if not with_data and not with_storages:
self.logger.critical(
"Data and storages dumps can not be both disabled. At least one dump "
"type must be enabled."
)
dumper = Dumper(
application_configurations,
logger=self.logger,
storages_basepath=storages_basepath,
storages=storages,
storages_excludes=storages_excludes,
indent=indent,
)
# Validate configurations
dumper.validate()
commandlines = dumper.make_script(
archive_destination,
with_data=with_data,
with_storages=with_storages,
with_storages_excludes=with_storages_excludes,
)
return commandlines
[docs]
def dump(self, archive_destination=None, archive_filename=None,
application_configurations=None, storages=None, storages_basepath=None,
storages_excludes=None, no_data=False, no_checksum=False,
no_storages=False, no_storages_excludes=False, indent=None, check=False):
"""
Run configuration validation and proceed to archiving operations for datas and
storages.
Keyword Arguments:
archive_destination (Path): Path where the archive will be written. If not
given the value from setting ``DISKETTE_DUMP_PATH`` will be used
instead.
archive_filename (string): Custom archive filename to use instead of the
default one. Your custom filename must end with ``.tar.gz``. Default
filename is ``diskette[_data][_storages].tar.gz`` (parts depend from
options).
application_configurations (string or list or Path): Either:
* A list which includes application configurations;
* A string which holds valid JSON;
* A Path object to a JSON file to open to get application
configurations;
If None or any empty value, the setting will be used.
storages (list): A list of storage Path objects.
storages_basepath (Path): Basepath for reference in some path resolution.
Currently used by storage dump to make relative path for storage files.
On default this is based on current working directory. If given, the
storage paths must be in the same leaf else this will be an error.
storages_excludes (list): A list of patterns to exclude storage files from
dump.
no_data (boolean): Disable dump of application datas.
no_checksum (boolean): Disable archive checksum.
no_storages (boolean): Disable dump of media storages.
no_storages_excludes (boolean): Disable usage of excluding patterns when
collecting storages files.
indent (integer): Indentation level in data dumps. If not given, dumps won't
be indented.
check (boolean): Only run validations and some additional configurations
checking instead of performing real dump operations and archiving.
Nothing should be queried or created in this mode.
Returns:
Path: Path to the written archive file. With 'check' mode enable the
returned path won't exists since nothing is created.
"""
if not check:
self.logger.info("=== Starting dump ===")
else:
self.logger.info("=== Starting to check ===")
self.log_diskette_version()
archive_destination = self.get_archive_destination(archive_destination)
archive_filename = self.get_archive_filename(archive_filename)
with_data, application_configurations = self.get_application_configurations(
appconfs=application_configurations,
no_data=no_data
)
with_storages, storages = self.get_storage_paths(storages, no_storages)
with_storages_excludes = None
if with_storages:
with_storages_excludes, storages_excludes = self.get_storage_excludes(
storages_excludes,
no_patterns=no_storages_excludes,
)
else:
storages_excludes = []
if not with_data and not with_storages:
self.logger.critical(
"Data and storages dumps can not be both disabled. At least one dump "
"type must be enabled."
)
dumper = Dumper(
application_configurations,
logger=self.logger,
storages_basepath=storages_basepath,
storages=storages,
storages_excludes=storages_excludes,
indent=indent,
)
# Validate configuration
dumper.validate()
if not check:
archive_path = dumper.make_archive(
archive_destination,
archive_filename,
with_data=with_data,
with_storages=with_storages,
with_storages_excludes=with_storages_excludes,
)
self.logger.info(
"Dump archive was created at: {path} ({size})".format(
path=archive_path,
size=filesizeformat(archive_path.stat().st_size),
)
)
if not no_checksum:
self.logger.info(
"Checksum: {}".format(hashs.file_checksum(archive_path))
)
else:
archive_path = dumper.check(
archive_destination,
archive_filename,
with_data=with_data,
with_storages=with_storages,
with_storages_excludes=with_storages_excludes,
)
self.logger.info(
"Dump archive would be created at: {path}".format(path=archive_path)
)
return archive_path