base command line functions

This commit is contained in:
dave 2021-06-03 08:35:19 -07:00
parent 4f14fb406a
commit 1ff5ddf63f
2 changed files with 233 additions and 26 deletions

View File

@ -1,56 +1,113 @@
import logging
import argparse
import logging
import requests
from backupdb2.misc import load_cli_config, tabulate
class BackupdbClient(object):
def __init__(self):
pass
def __init__(self, base_url, passwd=None):
self.session = requests.Session()
if passwd:
self.session.auth = passwd # user, pass tuple
self.base_url = base_url
if not self.base_url.endswith("/"):
self.base_url += "/"
def get(self, url, **params):
return self.do("get", url, **params)
def post(self, url, **params):
return self.do("post", url, **params)
def delete(self, url, **params):
return self.do("delete", url, **params)
def do(self, method, url, **kwargs):
resp = getattr(self.session, method)(self.base_url + "api/v1/" + url, **kwargs)
resp.raise_for_status()
return resp
def list_namespaces(self):
return self.get("namespaces").json()
def list_backups(self, namespace="default"):
return self.get("backups", params=dict(namespace=namespace)).json()
def list_dates(self, backup, namespace="default"):
return self.get("dates", params=dict(namespace=namespace, backup=backup)).json()
# def create_user(self, username, password):
# return self.post("user", data={"username": username,
# "password_hash": pwhash(password)})
# def upload(self, files, metadata):
# return self.post("upload", files=files, data={"meta": json.dumps(metadata)})
def cmd_list_configured(args, parser):
def cmd_list_configured(args, parser, config, client):
"""
If there is a config file, list backups configured in it
"""
pass
if not config["backups"]:
print("No backups configured")
return
tabulate([
[
name,
c["dir"],
c["method"],
] for name, c in config["backups"].items()],
headers=["name", "path", "method"]
)
def cmd_list_namespace(args, parser):
def cmd_list_namespace(args, parser, config, client):
"""
List namespaces
"""
pass
tabulate([
[i] for i in client.list_namespaces()],
headers=["namespaces"]
)
def cmd_list_backup(args, parser):
def cmd_list_backup(args, parser, config, client):
"""
List backups in a namespace
"""
pass
tabulate([
[i] for i in client.list_backups(namespace=args.namespace)],
headers=["backups"]
)
def cmd_list_dates(args, parser):
def cmd_list_dates(args, parser, config, client):
"""
List available dates for a backup
"""
pass
tabulate([
[i] for i in client.list_dates(backup=args.backup, namespace=args.namespace)],
headers=[args.backup]
)
def cmd_backup(args, parser):
"""
Create a new backup - requires it be defined in the config file
"""
pass
def cmd_download(args, parser):
def cmd_download(args, parser, config, client):
"""
Download a backup
"""
pass
def cmd_restore(args, parser):
def cmd_backup(args, parser, config, client):
"""
Create a new backup - requires it be defined in the config file
"""
pass
def cmd_restore(args, parser, config, client):
"""
Restore a backup
"""
@ -62,10 +119,10 @@ def get_args():
parser.add_argument("-c", "--config", help="config file path")
parser.add_argument("-s", "--server", help="server address")
sp_action = parser.add_subparsers(dest="action", help="action to take")
sp_action = parser.add_subparsers(dest="action", required=True, help="action to take")
p_list = sp_action.add_parser("list", help="list action")
sp_list = p_list.add_subparsers(dest="list_action", help="item to list", required=True)
sp_list = p_list.add_subparsers(dest="list_action", required=True, help="item to list")
p_list_configured = sp_list.add_parser("configured", help="list locally configured backups")
p_list_configured.set_defaults(func=cmd_list_configured)
@ -77,7 +134,7 @@ def get_args():
p_list_backup.set_defaults(func=cmd_list_backup)
p_list_backup.add_argument("-n", "--namespace", default="default", help="parent namespace to list in")
p_list_dates = sp_list.add_parser("date", help="list dates")
p_list_dates = sp_list.add_parser("dates", help="list dates")
p_list_dates.set_defaults(func=cmd_list_dates)
p_list_dates.add_argument("-n", "--namespace", default="default", help="parent namespace to list in")
p_list_dates.add_argument("backup", help="backup to list dates for")
@ -106,8 +163,7 @@ def main():
logging.basicConfig(level=logging.INFO)
args, parser = get_args()
#TODO one of `config` or `server` or a default config file is required
config = load_cli_config(args.config)
client = BackupdbClient(args.server or config["options"].get("server"))
client = BackupdbClient()
args.func(args, parser)
args.func(args, parser, config, client)

151
backupdb2/misc.py Normal file
View File

@ -0,0 +1,151 @@
import os
import logging
from configparser import ConfigParser
def load_cli_config(config_path=None):
"""
Load the CLI config file. The config is delivered as a dict structured like:
{
"options": {...},
"backups": {
"name": {
"uri": null,
"dir": null,
"keep": null,
"auth": null,
"restore_preexec": null,
"restore_postexec": null,
"export_preexec": null,
"export_postexec": null,
"exclude": null
}
}
}
"""
config = {
"options": {},
"backups": {}
}
# local the config file
if not config_path:
config_format, config_path = locate_cli_config()
else:
config_format = get_config_format(config_path)
if not config_path or not os.path.exists(config_path):
raise Exception(f"config file not found: {config_path}")
# TODO other formats and loaders
if config_format == "ini":
load_cli_config_ini(config, config_path)
# logging.info("no config file available")
return config
def locate_cli_config():
if os.path.exists("/etc/datadb.ini"):
return "ini", "/etc/datadb.ini"
return None, None
def get_config_format(fpath):
return "ini"#TODO
def load_cli_config_ini(config, config_path):
"""
The ini config file format is structured like:
[_backupdb_] ; global section
server=https://foo.bar/ ; server http address
username=foo ; http basic auth used for server (optional)
password=bar ; http basic auth used for server (optional)
namespace=default ; backup namespace
[backup_name] ; name of one backup config, must be unique
method=archive ; backup method, only 'archive' is supported now
dir=/foo ; dir to backup
keep=6 ; how many copies to instruct the server to keep
exclude= ; exclude pattern (passed directly to underlying implementation)
restore_preexec= ; commands to run before/after restoring/backing up
restore_postexec= ;
export_preexec= ;
export_postexec= ;
"""
# defaults per backup section
config_defaults = {
"server": None,
"username": None,
"password": None,
"namespace": "default"
}
backup_defaults = {
"method": "archive",
"dir": None,
"keep": 7,
"exclude": None,
"restore_preexec": None,
"restore_postexec": None,
"export_preexec": None,
"export_postexec": None
}
backup_required_params = set(["dir"])
global_required_params = set(["server"])
int_keys = set(["keep"])
# Load profiles
parser = ConfigParser(inline_comment_prefixes=("#", ";", ))
parser.read(config_path)
for section_name in parser.sections():
section = parser[section_name]
for name, options in section.items():
if section_name == "_backupdb_": # parse main config
for k, v in config_defaults.items():
config["options"][k] = section.get(k) or v
validate_section(section_name, config["options"], global_required_params)
else: # parse a backup section
config["backups"][section_name] = {}
for k, v in backup_defaults.items():
value = section.get(k) or v
if k in int_keys:
value = int(value)
config["backups"][section_name][k] = value
validate_section(section_name, config["backups"][section_name], backup_required_params)
return config
def validate_section(section_name, d, required_keys):
for k in required_keys:
if not d.get(k):
raise Exception(f"config section '{section_name}' missing key '{k}'")
def tabulate(rows, headers):
lengths = [0] * len(headers)
for row in rows + [headers]:
for i, c in enumerate(row):
row[i] = str(c)
lengths[i] = max(lengths[i], len(row[i]))
for i, h in enumerate(headers):
print(h + " " * (lengths[i] - len(h)) + " ", end="")
print()
for i, h in enumerate(headers):
print("-" * lengths[i] + " ", end="")
print()
for row in rows:
for i, c in enumerate(row):
print(c + " " * (lengths[i] - len(c)) + " ", end="")
print()
print()