diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39feb6b5..5d16b43a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-20.04] # For python3.6 python-version: - '3.6' - '3.7' @@ -37,3 +37,32 @@ jobs: pip install -e . - name: Test and compare api calls run: ci/run_testsuite_and_record.sh + + tox: + name: tox + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tox tox-gh-actions + python -m pip install -r requirements.txt + python -m pip install -r requirements-dev.txt + - name: Test with tox + run: tox r + \ No newline at end of file diff --git a/ci/diff.py b/ci/diff.py index 6be14df9..2f2ef056 100644 --- a/ci/diff.py +++ b/ci/diff.py @@ -2,8 +2,9 @@ import json import sys + def group_objects(json_file_path): - with open(json_file_path, 'r') as f: + with open(json_file_path, "r") as f: data = json.load(f) grouped_objects = [] @@ -23,53 +24,57 @@ def group_objects(json_file_path): def main(): + if len(sys.argv) != 3: + print("Usage: diff.py ") + sys.exit(1) + + expected = group_objects(sys.argv[1]) + result = group_objects(sys.argv[2]) - if len(sys.argv) != 3: - print("Usage: diff.py ") - sys.exit(1) + # Verify that the list of commands is the same + cmdlist1 = [] + cmdlist2 = [] + for a in expected: + cmdlist1.append(a[0]["command"].rstrip()) + for a in result: + cmdlist2.append(a[0]["command"].rstrip()) + differ = difflib.Differ() + diff = differ.compare(cmdlist1, cmdlist2) + differences = [ + line for line in diff if line.startswith("-") or line.startswith("+") + ] + if differences: + print( + "Diff between what commands were run in the recorded result and the current testsuite:" + ) + for line in differences: + print(line) + sys.exit(1) - expected = group_objects(sys.argv[1]) - result = group_objects(sys.argv[2]) + # For each command, verify that the http calls and output is the same + has_diff = False + for i in range(len(expected)): + cmd = expected[i][0]["command"].rstrip() + cmd2 = result[i][0]["command"].rstrip() + if cmd != cmd2: + # This should never happen here, because it would get caught above + print(f"Expected command: {cmd}\nActual command: {cmd2}") + sys.exit(1) - # Verify that the list of commands is the same - cmdlist1 = [] - cmdlist2 = [] - for a in expected: - cmdlist1.append(a[0]['command'].rstrip()) - for a in result: - cmdlist2.append(a[0]['command'].rstrip()) - differ = difflib.Differ() - diff = differ.compare(cmdlist1, cmdlist2) - differences = [line for line in diff if line.startswith('-') or line.startswith('+')] - if differences: - print("Diff between what commands were run in the recorded result and the current testsuite:") - for line in differences: - print(line) - sys.exit(1) + s1 = json.dumps(expected[i], indent=4).splitlines(keepends=True) + s2 = json.dumps(result[i], indent=4).splitlines(keepends=True) + if s1 != s2: + has_diff = True + print("=" * 72) + print("Command:", cmd, " -expected, +tested") + print("=" * 72) + gen = difflib.ndiff(s1, s2) + sys.stdout.writelines(gen) + print("\n") # 2 newlines - # For each command, verify that the http calls and output is the same - has_diff = False - for i in range(len(expected)): - cmd = expected[i][0]['command'].rstrip() - cmd2 = result[i][0]['command'].rstrip() - if cmd != cmd2: - # This should never happen here, because it would get caught above - print(f"Expected command: {cmd}\nActual command: {cmd2}") - sys.exit(1) - - s1 = json.dumps(expected[i], indent=4).splitlines(keepends=True) - s2 = json.dumps(result[i], indent=4).splitlines(keepends=True) - if s1 != s2: - has_diff = True - print("=" * 72) - print("Command:",cmd," -expected, +tested") - print("=" * 72) - gen = difflib.ndiff(s1,s2) - sys.stdout.writelines(gen) - print("\n") # 2 newlines + if has_diff: + sys.exit(1) - if has_diff: - sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/mreg_cli/__init__.pyi b/mreg_cli/__init__.pyi index 98fed2c6..00cf78fb 100644 --- a/mreg_cli/__init__.pyi +++ b/mreg_cli/__init__.pyi @@ -1 +1 @@ -from .log import * \ No newline at end of file +from .log import * diff --git a/mreg_cli/__main__.py b/mreg_cli/__main__.py index 633d5dab..0880d0c7 100644 --- a/mreg_cli/__main__.py +++ b/mreg_cli/__main__.py @@ -1,4 +1,4 @@ from . import main -if __name__ == '__main__': +if __name__ == "__main__": main.main() diff --git a/mreg_cli/bacnet.py b/mreg_cli/bacnet.py index 6c830577..73343e36 100644 --- a/mreg_cli/bacnet.py +++ b/mreg_cli/bacnet.py @@ -1,71 +1,69 @@ -from .cli import Flag, cli -from .exceptions import HostNotFoundWarning +from .cli import Flag from .history import history from .host import host -from .log import cli_error, cli_info, cli_warning -from .util import ( - host_info_by_name, - print_table, - get, - get_list, - post, - delete, -) +from .log import cli_error, cli_info +from .util import delete, get, get_list, host_info_by_name, post, print_table + def bacnetid_add(args): info = host_info_by_name(args.name) - if 'bacnetid' in info and info['bacnetid'] is not None: - cli_error("{} already has BACnet ID {}.".format(info['name'],info['bacnetid']['id'])) - postdata = {'hostname': info['name']} - path = '/api/v1/bacnet/ids/' - bacnetid = getattr(args, 'id') + if "bacnetid" in info and info["bacnetid"] is not None: + cli_error( + "{} already has BACnet ID {}.".format(info["name"], info["bacnetid"]["id"]) + ) + postdata = {"hostname": info["name"]} + path = "/api/v1/bacnet/ids/" + bacnetid = args.id if bacnetid: - response = get(path+bacnetid, ok404=True) + response = get(path + bacnetid, ok404=True) if response: j = response.json() - cli_error('BACnet ID {} is already in use by {}'.format(j['id'], j['hostname'])) - postdata['id'] = bacnetid - history.record_post(path, '', postdata) + cli_error( + "BACnet ID {} is already in use by {}".format(j["id"], j["hostname"]) + ) + postdata["id"] = bacnetid + history.record_post(path, "", postdata) post(path, **postdata) info = host_info_by_name(args.name) - if 'bacnetid' in info and info['bacnetid'] is not None: - b = info['bacnetid'] - cli_info("Assigned BACnet ID {} to {}".format(b['id'], info['name']), print_msg=True) + if "bacnetid" in info and info["bacnetid"] is not None: + b = info["bacnetid"] + cli_info( + "Assigned BACnet ID {} to {}".format(b["id"], info["name"]), print_msg=True + ) host.add_command( - prog='bacnetid_add', - description='Assign a BACnet ID to the host.', - short_desc='Add BACnet ID', + prog="bacnetid_add", + description="Assign a BACnet ID to the host.", + short_desc="Add BACnet ID", callback=bacnetid_add, flags=[ - Flag('name', - description='Name of host.', - metavar='NAME'), - Flag('-id', - description='ID value (0-4194302)', - metavar='ID'), + Flag("name", description="Name of host.", metavar="NAME"), + Flag("-id", description="ID value (0-4194302)", metavar="ID"), ], ) + def bacnetid_remove(args): info = host_info_by_name(args.name) - if 'bacnetid' not in info or info["bacnetid"] is None: - cli_error("{} does not have a BACnet ID assigned.".format(info['name'])) - path = '/api/v1/bacnet/ids/{}'.format(info['bacnetid']['id']) - history.record_delete(path, info['bacnetid']) + if "bacnetid" not in info or info["bacnetid"] is None: + cli_error("{} does not have a BACnet ID assigned.".format(info["name"])) + path = "/api/v1/bacnet/ids/{}".format(info["bacnetid"]["id"]) + history.record_delete(path, info["bacnetid"]) delete(path) - cli_info("Unassigned BACnet ID {} from {}".format(info['bacnetid']['id'], info['name']), print_msg=True) + cli_info( + "Unassigned BACnet ID {} from {}".format(info["bacnetid"]["id"], info["name"]), + print_msg=True, + ) + host.add_command( - prog='bacnetid_remove', - description='Unassign the BACnet ID from the host.', - short_desc='Remove BACnet ID', + prog="bacnetid_remove", + description="Unassign the BACnet ID from the host.", + short_desc="Remove BACnet ID", callback=bacnetid_remove, flags=[ - Flag('name', - description='Name of host.', - metavar='NAME'), + Flag("name", description="Name of host.", metavar="NAME"), ], ) @@ -81,22 +79,27 @@ def bacnetid_list(args): maxval = args.max if maxval > 4194302: cli_error("The maximum ID value is 4194302.") - r = get_list("/api/v1/bacnet/ids/",{'id__range':'{},{}'.format(minval,maxval)}) - print_table(('ID','Hostname'), ('id','hostname'), r) + r = get_list("/api/v1/bacnet/ids/", {"id__range": "{},{}".format(minval, maxval)}) + print_table(("ID", "Hostname"), ("id", "hostname"), r) + host.add_command( - prog='bacnetid_list', - description='Find/list BACnet IDs and hostnames', - short_desc='List used BACnet IDs', + prog="bacnetid_list", + description="Find/list BACnet IDs and hostnames", + short_desc="List used BACnet IDs", callback=bacnetid_list, flags=[ - Flag('-min', - description='Minimum ID value (0-4194302)', - type=int, - metavar='MIN'), - Flag('-max', - description='Maximum ID value (0-4194302)', - type=int, - metavar='MAX'), + Flag( + "-min", + description="Minimum ID value (0-4194302)", + flag_type=int, + metavar="MIN", + ), + Flag( + "-max", + description="Maximum ID value (0-4194302)", + flag_type=int, + metavar="MAX", + ), ], ) diff --git a/mreg_cli/cli.py b/mreg_cli/cli.py index f6d2a39f..50fd0086 100644 --- a/mreg_cli/cli.py +++ b/mreg_cli/cli.py @@ -1,11 +1,10 @@ import argparse import os -from prompt_toolkit import HTML -from prompt_toolkit import print_formatted_text as print +from prompt_toolkit import HTML, print_formatted_text from prompt_toolkit.completion import Completer, Completion -from . import util, recorder +from . import recorder, util from .exceptions import CliError, CliWarning @@ -14,15 +13,25 @@ class CliExit(Exception): class Flag: - def __init__(self, name, description='', short_desc='', nargs=None, - default=None, type=None, choices=None, required=False, - metavar=None, action=None): + def __init__( + self, + name, + description="", + short_desc="", + nargs=None, + default=None, + flag_type=None, + choices=None, + required=False, + metavar=None, + action=None, + ): self.name = name self.short_desc = short_desc self.description = description self.nargs = nargs self.default = default - self.type = type + self.type = flag_type self.choices = choices self.required = required self.metavar = metavar @@ -33,13 +42,14 @@ def _create_command_group(parent): parent_name = parent.prog.strip() if parent_name: - title = 'subcommands' + title = "subcommands" else: - title = 'commands' + title = "commands" - metavar = '' + metavar = "" description = "Run '{}' for more details".format( - ' '.join(word for word in (parent_name, metavar, '-h') if word)) + " ".join(word for word in (parent_name, metavar, "-h") if word) + ) return parent.add_subparsers( title=title, @@ -66,48 +76,46 @@ def __init__(self, parser, flags, short_desc): self.children = {} self.flags = {} for flag in flags: - if flag.name.startswith('-'): - self.flags[flag.name.lstrip('-')] = flag + if flag.name.startswith("-"): + self.flags[flag.name.lstrip("-")] = flag - def add_command(self, prog, description, short_desc='', epilog=None, - callback=None, flags=[]): - """ - :param flags: a list of Flag objects. NB: must be handled as read-only, + def add_command( + self, prog, description, short_desc="", epilog=None, callback=None, flags=None + ): + """:param flags: a list of Flag objects. NB: must be handled as read-only, since the default value is []. :return: the Command object of the new command. """ + if flags is None: + flags = [] if not self.sub: self.sub = _create_command_group(self.parser) - parser = self.sub.add_parser(prog, - description=description, - epilog=epilog, - help=short_desc) + parser = self.sub.add_parser( + prog, description=description, epilog=epilog, help=short_desc + ) for f in flags: # Need to create a dict with the parameters so only used # parameters are sent, or else exceptions are raised. Ex: if # required is passed with an argument which doesn't accept the # required option. args = { - 'help': f.description, + "help": f.description, } if f.type: - args['type'] = f.type + args["type"] = f.type if f.nargs: - args['nargs'] = f.nargs + args["nargs"] = f.nargs if f.default: - args['default'] = f.default + args["default"] = f.default if f.choices: - args['choices'] = f.choices + args["choices"] = f.choices if f.required: - args['required'] = f.required + args["required"] = f.required if f.metavar: - args['metavar'] = f.metavar + args["metavar"] = f.metavar if f.action: - args['action'] = f.action - parser.add_argument( - f.name, - **args - ) + args["action"] = f.action + parser.add_argument(f.name, **args) parser.set_defaults(func=callback) new_cmd = Command(parser, flags, short_desc) @@ -118,7 +126,7 @@ def parse(self, args): try: args = self.parser.parse_args(args) # If the command has a callback function, call it. - if 'func' in vars(args) and args.func: + if "func" in vars(args) and args.func: args.func(args) except SystemExit as e: @@ -128,13 +136,14 @@ def parse(self, args): self.last_errno = e.code except CliWarning as e: - print(HTML(f'{e}')) + print_formatted_text(HTML(f"{e}")) except CliError as e: - print(HTML(f'{e}')) + print_formatted_text(HTML(f"{e}")) except CliExit: from sys import exit + exit(0) else: @@ -144,17 +153,14 @@ def parse(self, args): def get_completions(self, document, complete_event): cur = document.get_word_before_cursor() - words = document.text.strip().split(' ') + words = document.text.strip().split(" ") yield from self.complete(cur, words) def complete(self, cur, words): # if line is empty suggest all sub commands if not words: for name in self.children: - yield Completion( - name, - display_meta=self.children[name].short_desc - ) + yield Completion(name, display_meta=self.children[name].short_desc) return # only suggest sub commands if there's only one word on the line. @@ -170,7 +176,7 @@ def complete(self, cur, words): yield Completion( name, display_meta=self.children[name].short_desc, - start_position=-len(cur) + start_position=-len(cur), ) # if the line starts with one of the sub commands, pass it along @@ -184,26 +190,26 @@ def complete(self, cur, words): if not cur: return # If the current word is - then it is the beginning of a flag - if cur == '-': - cur = '' + if cur == "-": + cur = "" # If current word doesn't start with - then it isn't a flag being typed - elif ('-' + cur) not in words: + elif ("-" + cur) not in words: return # complete flags which aren't already used for name in self.flags: - if ('-' + name) not in words: + if ("-" + name) not in words: if name.startswith(cur): yield Completion( name, display_meta=self.flags[name].short_desc, - start_position=-len(cur) + start_position=-len(cur), ) # Top parser is the root of all the command parsers -_top_parser = argparse.ArgumentParser('') -cli = Command(_top_parser, list(), '') +_top_parser = argparse.ArgumentParser("") +cli = Command(_top_parser, list(), "") def _quit(args): @@ -212,100 +218,118 @@ def _quit(args): # Always need a quit command cli.add_command( - prog='quit', - description='Exit application.', - short_desc='Quit', + prog="quit", + description="Exit application.", + short_desc="Quit", callback=_quit, ) cli.add_command( - prog='exit', - description='Exit application.', - short_desc='Quit', + prog="exit", + description="Exit application.", + short_desc="Quit", callback=_quit, ) + def logout(args): util.logout() raise CliExit + cli.add_command( - prog='logout', - description='Log out from mreg and exit. Will delete token', - short_desc='Log out from mreg', + prog="logout", + description="Log out from mreg and exit. Will delete token", + short_desc="Log out from mreg", callback=logout, ) def source(files, ignore_errors, verbose): - """source reads commands from one or more source files. + """Source reads commands from one or more source files. Each command must be on one line and the commands must be separated with newlines. - The files may contain comments. The comment symbol is # + The files may contain comments. The comment symbol is #. """ - import shlex import html + import shlex rec = recorder.Recorder() for filename in files: - if filename.startswith('~'): + if filename.startswith("~"): filename = os.path.expanduser(filename) try: with open(filename) as f: - for i, l in enumerate(f): + for i, line in enumerate(f): # Shell commands can be called from scripts. They start with '!' - if l.startswith('!'): - os.system(l[1:]) + if line.startswith("!"): + os.system(line[1:]) continue # If recording commands, submit the command line. # Don't record the "source" command itself. - if rec.is_recording() and not l.lstrip().startswith('source'): - rec.record_command(l) + if rec.is_recording() and not line.lstrip().startswith("source"): + rec.record_command(line) # With comments=True shlex will remove comments from the line # when splitting. Comment symbol is # - s = shlex.split(l, comments=True) + s = shlex.split(line, comments=True) # In verbose mode all commands are printed before execution. if verbose and s: - print(HTML(f'> {html.escape(l.strip())}')) + print_formatted_text( + HTML(f"> {html.escape(line.strip())}") + ) cli.parse(s) if cli.last_errno != 0: - print(HTML(f'{filename}: ' - f'Error on line {i + 1}')) + print_formatted_text( + HTML( + ( + f"{filename}: " + f"Error on line {i + 1}" + ) + ) + ) if not ignore_errors: return except FileNotFoundError: - print(f"No such file: '{filename}'") + print_formatted_text(f"No such file: '{filename}'") except PermissionError: - print(f"Permission denied: '{filename}'") + print_formatted_text(f"Permission denied: '{filename}'") + def _source(args): source(args.files, args.ignore_errors, args.verbose) + # Always need the source command. cli.add_command( - prog='source', - description='Read and run commands from the given source files.', - short_desc='Run commands from file(s)', + prog="source", + description="Read and run commands from the given source files.", + short_desc="Run commands from file(s)", callback=_source, flags=[ - Flag('files', - description='Source files to read commands from. Commands are ' - 'separated with new lines and comments are started ' - 'with "#"', - short_desc='File names', - nargs='+', - metavar='SOURCE'), - Flag('-ignore-errors', - description='Continue command execution on error. Default is to ' - 'stop execution on error.', - short_desc='Stop on error.', - action='store_true'), - Flag('-verbose', - description='Verbose output.', - action='store_true'), - ] + Flag( + "files", + description=( + "Source files to read commands from. Commands are " + "separated with new lines and comments are started " + 'with "#"' + ), + short_desc="File names", + nargs="+", + metavar="SOURCE", + ), + Flag( + "-ignore-errors", + description=( + "Continue command execution on error. Default is to " + "stop execution on error." + ), + short_desc="Stop on error.", + action="store_true", + ), + Flag("-verbose", description="Verbose output.", action="store_true"), + ], ) diff --git a/mreg_cli/config.py b/mreg_cli/config.py index dc4a5682..6c2df71c 100644 --- a/mreg_cli/config.py +++ b/mreg_cli/config.py @@ -1,5 +1,4 @@ -""" -Logging +"""Logging ------- This module can be used to configure basic logging to stderr, with an optional filter level. @@ -21,14 +20,16 @@ logger = logging.getLogger(__name__) # Config file locations -DEFAULT_CONFIG_PATH = tuple(( - os.path.expanduser('~/.config/mreg-cli.conf'), - '/etc/mreg-cli.conf', - os.path.join(sys.prefix, 'local', 'share', 'mreg-cli.conf'), - os.path.join(sys.prefix, 'share', 'mreg-cli.conf'), - # At last, look in ../data/ in case we're developing - os.path.join(os.path.dirname(__file__), '..', 'data', 'mreg-cli.conf'), -)) +DEFAULT_CONFIG_PATH = tuple( + ( + os.path.expanduser("~/.config/mreg-cli.conf"), + "/etc/mreg-cli.conf", + os.path.join(sys.prefix, "local", "share", "mreg-cli.conf"), + os.path.join(sys.prefix, "share", "mreg-cli.conf"), + # At last, look in ../data/ in case we're developing + os.path.join(os.path.dirname(__file__), "..", "data", "mreg-cli.conf"), + ) +) DEFAULT_URL = None DEFAULT_DOMAIN = None @@ -47,8 +48,7 @@ def get_verbosity(verbosity): - """ - Translate verbosity to logging level. + """Translate verbosity to logging level. Levels are traslated according to :py:const:`LOGGING_VERBOSITY`. @@ -61,8 +61,7 @@ def get_verbosity(verbosity): def configure_logging(level): - """ - Enable and configure logging. + """Enable and configure logging. :param int level: logging level """ @@ -70,17 +69,16 @@ def configure_logging(level): def get_config_file(): - """ - :return: - returns the best (first) match from DEFAULT_CONFIG_PATH, or - None if no file was found. + """:return: + returns the best (first) match from DEFAULT_CONFIG_PATH, or + None if no file was found. """ for path in DEFAULT_CONFIG_PATH: - logger.debug('looking for config in %r', os.path.abspath(path)) + logger.debug("looking for config in %r", os.path.abspath(path)) if os.path.isfile(path): - logger.info('found config in %r', path) + logger.info("found config in %r", path) return path - logger.debug('no config file found in config paths') + logger.debug("no config file found in config paths") return None @@ -89,7 +87,7 @@ def get_default_domain(): def get_default_url(): - for url in (os.environ.get('MREGCLI_DEFAULT_URL'), DEFAULT_URL): + for url in (os.environ.get("MREGCLI_DEFAULT_URL"), DEFAULT_URL): if url is not None: return url return None diff --git a/mreg_cli/dhcp.py b/mreg_cli/dhcp.py index 4a2fd661..8de91b79 100644 --- a/mreg_cli/dhcp.py +++ b/mreg_cli/dhcp.py @@ -1,20 +1,28 @@ from .cli import Flag, cli from .history import history from .log import cli_error, cli_info, cli_warning -from .util import format_mac, get_list, host_info_by_name, is_valid_ip, is_valid_mac, patch +from .util import ( + format_mac, + get_list, + host_info_by_name, + is_valid_ip, + is_valid_mac, + patch, +) ################################# # Add the main command 'dhcp' # ################################# dhcp = cli.add_command( - prog='dhcp', - description='Manage DHCP associations.', - short_desc='Manage DHCP', + prog="dhcp", + description="Manage DHCP associations.", + short_desc="Manage DHCP", ) + def _dhcp_get_ip_by_arg(arg): - """Get A/AAAA record by either ip address or host name""" + """Get A/AAAA record by either ip address or host name.""" if is_valid_ip(arg): path = "/api/v1/ipaddresses/" params = { @@ -25,8 +33,7 @@ def _dhcp_get_ip_by_arg(arg): if not len(ip): cli_warning(f"ip {arg} doesn't exist.") elif len(ip) > 1: - cli_warning( - "ip {} is in use by {} hosts".format(arg, len(ip))) + cli_warning("ip {} is in use by {} hosts".format(arg, len(ip))) ip = ip[0] else: info = host_info_by_name(arg) @@ -35,12 +42,14 @@ def _dhcp_get_ip_by_arg(arg): "{} has {} ip addresses, please enter one of the addresses instead.".format( info["name"], len(info["ipaddresses"]), - )) + ) + ) if len(info["ipaddresses"]) == 0: cli_error("{} doesn't have any ip addresses.".format(arg)) ip = info["ipaddresses"][0] return ip + def assoc_mac_to_ip(mac, ip, force=False): # MAC addr sanity check if is_valid_mac(mac): @@ -52,22 +61,27 @@ def assoc_mac_to_ip(mac, ip, force=False): } history.record_get(path) macs = get_list(path, params=params) - ips = ", ".join([i['ipaddress'] for i in macs]) + ips = ", ".join([i["ipaddress"] for i in macs]) if len(macs) and not force: cli_warning( "mac {} already in use by: {}. " "Use force to add {} -> {} as well.".format( - mac, ips, ip['ipaddress'], mac)) + mac, ips, ip["ipaddress"], mac + ) + ) else: cli_warning("invalid MAC address: {}".format(mac)) - old_mac = ip.get('macaddress') + old_mac = ip.get("macaddress") if old_mac == new_mac: cli_info("new and old mac are identical. Ignoring.", print_msg=True) return elif old_mac and not force: - cli_warning("ip {} has existing mac {}. Use force to replace.".format( - ip['ipaddress'], old_mac)) + cli_warning( + "ip {} has existing mac {}. Use force to replace.".format( + ip["ipaddress"], old_mac + ) + ) # Update Ipaddress with a mac path = f"/api/v1/ipaddresses/{ip['id']}" @@ -80,37 +94,33 @@ def assoc_mac_to_ip(mac, ip, force=False): # Implementation of sub command 'assoc' # ######################################### + def assoc(args): # .name .mac .force """Associate MAC address with host. If host got multiple A/AAAA records an IP must be given instead of name. """ - ip = _dhcp_get_ip_by_arg(args.name) new_mac = assoc_mac_to_ip(args.mac, ip, force=args.force) if new_mac is not None: - cli_info("associated mac address {} with ip {}" - .format(new_mac, ip["ipaddress"]), print_msg=True) + cli_info( + "associated mac address {} with ip {}".format(new_mac, ip["ipaddress"]), + print_msg=True, + ) dhcp.add_command( - prog='assoc', - description='Associate MAC address with host. If host got multiple A/AAAA ' - 'records an IP must be given instead of name.', - short_desc='Add MAC address to host.', + prog="assoc", + description="Associate MAC address with host. If host got multiple A/AAAA " + "records an IP must be given instead of name.", + short_desc="Add MAC address to host.", callback=assoc, flags=[ - Flag('name', - description='Name or IP of target host.', - metavar='NAME/IP'), - Flag('mac', - description='Mac address.', - metavar='MACADDRESS'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag("name", description="Name or IP of target host.", metavar="NAME/IP"), + Flag("mac", description="Mac address.", metavar="MACADDRESS"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -118,36 +128,38 @@ def assoc(args): # Implementation of sub command 'disassoc' # ############################################ + def disassoc(args): """Disassociate MAC address with host/ip. If host got multiple A/AAAA - records an IP must be given instead of name + records an IP must be given instead of name. """ - ip = _dhcp_get_ip_by_arg(args.name) - if ip.get('macaddress'): + if ip.get("macaddress"): # Update ipaddress path = f"/api/v1/ipaddresses/{ip['id']}" history.record_patch(path, new_data={"macaddress": ""}, old_data=ip) patch(path, macaddress="") - cli_info("disassociated mac address {} from ip {}".format( - ip["macaddress"], - ip["ipaddress"] - ), print_msg=True) + cli_info( + "disassociated mac address {} from ip {}".format( + ip["macaddress"], ip["ipaddress"] + ), + print_msg=True, + ) else: - cli_info("ipaddress {} has no associated mac address" - .format(ip["ipaddress"]), print_msg=True) + cli_info( + "ipaddress {} has no associated mac address".format(ip["ipaddress"]), + print_msg=True, + ) dhcp.add_command( - prog='disassoc', - description='Disassociate MAC address with host/ip. If host got multiple ' - 'A/AAAA records an IP must be given instead of name.', - short_desc='Disassociate MAC address.', + prog="disassoc", + description="Disassociate MAC address with host/ip. If host got multiple " + "A/AAAA records an IP must be given instead of name.", + short_desc="Disassociate MAC address.", callback=disassoc, flags=[ - Flag('name', - description='Name or IP of host.', - metavar='NAME/IP'), - ] + Flag("name", description="Name or IP of host.", metavar="NAME/IP"), + ], ) diff --git a/mreg_cli/group.py b/mreg_cli/group.py index 35818c65..071d6d84 100644 --- a/mreg_cli/group.py +++ b/mreg_cli/group.py @@ -4,16 +4,16 @@ from .history import history from .history_log import get_history_items, print_history_items from .log import cli_error, cli_info, cli_warning -from .util import delete, get, get_list, host_info_by_name, patch, post +from .util import delete, get_list, host_info_by_name, patch, post ################################## # Add the main command 'group' # ################################## group = cli.add_command( - prog='group', - description='Manage hostgroups.', - short_desc='Manage hostgroups', + prog="group", + description="Manage hostgroups.", + short_desc="Manage hostgroups", ) # Utils @@ -33,38 +33,28 @@ def get_hostgroup(name): def create(args): # .name .description - """ - Create a new host group - """ - + """Create a new host group.""" ret = get_list("/api/v1/hostgroups/", params={"name": args.name}) if ret: cli_error(f'Groupname "{args.name}" already in use') - data = { - 'name': args.name, - 'description': args.description - } + data = {"name": args.name, "description": args.description} - path = '/api/v1/hostgroups/' + path = "/api/v1/hostgroups/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Created new group {args.name!r}", print_msg=True) group.add_command( - prog='create', - description='Create a new host group', - short_desc='Create a new host group', + prog="create", + description="Create a new host group", + short_desc="Create a new host group", callback=create, flags=[ - Flag('name', - description='Group name', - metavar='NAME'), - Flag('description', - description='Description', - metavar='DESCRIPTION'), - ] + Flag("name", description="Group name", metavar="NAME"), + Flag("description", description="Description", metavar="DESCRIPTION"), + ], ) @@ -72,10 +62,9 @@ def create(args): # Implementation of sub command 'info' # ######################################## + def info(args): - """ - Show host group info - """ + """Show host group info.""" def _print(key, value, padding=14): print("{1:<{0}} {2}".format(padding, key, value)) @@ -83,32 +72,29 @@ def _print(key, value, padding=14): for name in args.name: info = get_hostgroup(name) - _print('Name:', info['name']) - _print('Description:', info['description']) + _print("Name:", info["name"]) + _print("Description:", info["description"]) members = [] - count = len(info['hosts']) + count = len(info["hosts"]) if count: - members.append('{} host{}'.format(count, 's' if count > 1 else '')) - count = len(info['groups']) + members.append("{} host{}".format(count, "s" if count > 1 else "")) + count = len(info["groups"]) if count: - members.append('{} group{}'.format(count, 's' if count > 1 else '')) - _print('Members:', ', '.join(members)) - if len(info['owners']): - owners = ', '.join([i['name'] for i in info['owners']]) - _print('Owners:', owners) + members.append("{} group{}".format(count, "s" if count > 1 else "")) + _print("Members:", ", ".join(members)) + if len(info["owners"]): + owners = ", ".join([i["name"] for i in info["owners"]]) + _print("Owners:", owners) group.add_command( - prog='info', - description='Shows group info with description, member count and owner(s)', - short_desc='Group info', + prog="info", + description="Shows group info with description, member count and owner(s)", + short_desc="Group info", callback=info, flags=[ - Flag('name', - description='Group name', - nargs='+', - metavar='NAME'), - ] + Flag("name", description="Group name", nargs="+", metavar="NAME"), + ], ) @@ -118,26 +104,21 @@ def _print(key, value, padding=14): def rename(args): - """Rename group - """ + """Rename group.""" get_hostgroup(args.oldname) patch(f"/api/v1/hostgroups/{args.oldname}", name=args.newname) cli_info(f"Renamed group {args.oldname!r} to {args.newname!r}", True) group.add_command( - prog='rename', - description='Rename a group', - short_desc='Rename a group', + prog="rename", + description="Rename a group", + short_desc="Rename a group", callback=rename, flags=[ - Flag('oldname', - description='Existing name', - metavar='OLDNAME'), - Flag('newname', - description='New name', - metavar='NEWNAME'), - ] + Flag("oldname", description="Existing name", metavar="OLDNAME"), + Flag("newname", description="New name", metavar="NEWNAME"), + ], ) @@ -145,103 +126,92 @@ def rename(args): # Implementation of sub command 'list' # ######################################## + def _list(args): - """ - List group members - """ + """List group members.""" - def _print(key, value, source='', padding=14): + def _print(key, value, source="", padding=14): print("{1:<{0}} {2:<{0}} {3}".format(padding, key, value, source)) - def _print_hosts(hosts, source=''): + def _print_hosts(hosts, source=""): for host in hosts: - _print('host', host['name'], source=source) + _print("host", host["name"], source=source) def _expand_group(groupname): info = get_hostgroup(groupname) - _print_hosts(info['hosts'], source=groupname) - for group in info['groups']: - _expand_group(group['name']) + _print_hosts(info["hosts"], source=groupname) + for group in info["groups"]: + _expand_group(group["name"]) info = get_hostgroup(args.name) if args.expand: - _print('Type', 'Name', 'Source') - _print_hosts(info['hosts'], source=args.name) + _print("Type", "Name", "Source") + _print_hosts(info["hosts"], source=args.name) else: - _print('Type', 'Name') - _print_hosts(info['hosts']) + _print("Type", "Name") + _print_hosts(info["hosts"]) - for group in info['groups']: + for group in info["groups"]: if args.expand: - _expand_group(group['name']) + _expand_group(group["name"]) else: - _print('group', group['name']) + _print("group", group["name"]) group.add_command( - prog='list', - description='List group members', - short_desc='List group members', + prog="list", + description="List group members", + short_desc="List group members", callback=_list, flags=[ - Flag('name', - description='Group name', - metavar='NAME'), - Flag('-expand', - description='Expand group members', - action='store_true'), - ] + Flag("name", description="Group name", metavar="NAME"), + Flag("-expand", description="Expand group members", action="store_true"), + ], ) def _delete(args): # .name .force - """ - Delete a host group - """ - + """Delete a host group.""" info = get_hostgroup(args.name) - if (len(info['hosts']) or len(info['groups'])) and not args.force: - cli_error('Group contains %d host(s) and %d group(s), must force' - % (len(info['hosts']), len(info['groups']))) + if (len(info["hosts"]) or len(info["groups"])) and not args.force: + cli_error( + "Group contains %d host(s) and %d group(s), must force" + % (len(info["hosts"]), len(info["groups"])) + ) - path = f'/api/v1/hostgroups/{args.name}' + path = f"/api/v1/hostgroups/{args.name}" history.record_delete(path, dict()) delete(path) cli_info(f"Deleted group {args.name!r}", print_msg=True) group.add_command( - prog='delete', - description='Delete host group', - short_desc='Delete host group', + prog="delete", + description="Delete host group", + short_desc="Delete host group", callback=_delete, flags=[ - Flag('name', - description='Group name', - metavar='NAME'), - Flag('-force', - action='store_true', - description='Enable force'), - ] + Flag("name", description="Group name", metavar="NAME"), + Flag("-force", action="store_true", description="Enable force"), + ], ) def _history(args): - """Show host history for name""" - items = get_history_items(args.name, 'group', data_relation='groups') + """Show host history for name.""" + items = get_history_items(args.name, "group", data_relation="groups") print_history_items(args.name, items) + group.add_command( - prog='history', - description='Show history for group name', - short_desc='Show history for group name', + prog="history", + description="Show history for group name", + short_desc="Show history for group name", callback=_history, flags=[ - Flag('name', - description='Group name', - metavar='NAME'), + Flag("name", description="Group name", metavar="NAME"), ], ) @@ -252,38 +222,30 @@ def _history(args): def group_add(args): - """ - Add group(s) to group - """ - + """Add group(s) to group.""" for name in chain([args.dstgroup], args.srcgroup): get_hostgroup(name) for src in args.srcgroup: data = { - 'name': src, + "name": src, } - path = f'/api/v1/hostgroups/{args.dstgroup}/groups/' + path = f"/api/v1/hostgroups/{args.dstgroup}/groups/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Added group {src!r} to {args.dstgroup!r}", print_msg=True) group.add_command( - prog='group_add', - description='Add source group(s) to destination group', - short_desc='Add group(s) to group', + prog="group_add", + description="Add source group(s) to destination group", + short_desc="Add group(s) to group", callback=group_add, flags=[ - Flag('dstgroup', - description='destination group', - metavar='DSTGROUP'), - Flag('srcgroup', - description='source group', - nargs='+', - metavar='SRCGROUP'), - ] + Flag("dstgroup", description="destination group", metavar="DSTGROUP"), + Flag("srcgroup", description="source group", nargs="+", metavar="SRCGROUP"), + ], ) ################################################ @@ -292,37 +254,29 @@ def group_add(args): def group_remove(args): - """ - Remove group(s) from group - """ - + """Remove group(s) from group.""" info = get_hostgroup(args.dstgroup) - group_names = set(i['name'] for i in info['groups']) + group_names = set(i["name"] for i in info["groups"]) for name in args.srcgroup: if name not in group_names: cli_warning(f"{name!r} not a group member in {args.dstgroup!r}") for src in args.srcgroup: - path = f'/api/v1/hostgroups/{args.dstgroup}/groups/{src}' + path = f"/api/v1/hostgroups/{args.dstgroup}/groups/{src}" history.record_delete(path, dict()) delete(path) cli_info(f"Removed group {src!r} from {args.dstgroup!r}", print_msg=True) group.add_command( - prog='group_remove', - description='Remove source group(s) from destination group', - short_desc='Remove group(s) from group', + prog="group_remove", + description="Remove source group(s) from destination group", + short_desc="Remove group(s) from group", callback=group_remove, flags=[ - Flag('dstgroup', - description='destination group', - metavar='DSTGROUP'), - Flag('srcgroup', - description='source group', - nargs='+', - metavar='SRCGROUP'), - ] + Flag("dstgroup", description="destination group", metavar="DSTGROUP"), + Flag("srcgroup", description="source group", nargs="+", metavar="SRCGROUP"), + ], ) ############################################ @@ -331,40 +285,32 @@ def group_remove(args): def host_add(args): - """ - Add host(s) to group - """ - + """Add host(s) to group.""" get_hostgroup(args.group) info = [] for name in args.hosts: info.append(host_info_by_name(name, follow_cname=False)) for i in info: - name = i['name'] + name = i["name"] data = { - 'name': name, + "name": name, } - path = f'/api/v1/hostgroups/{args.group}/hosts/' + path = f"/api/v1/hostgroups/{args.group}/hosts/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Added host {name!r} to {args.group!r}", print_msg=True) group.add_command( - prog='host_add', - description='Add host(s) to group', - short_desc='Add host(s) to group', + prog="host_add", + description="Add host(s) to group", + short_desc="Add host(s) to group", callback=host_add, flags=[ - Flag('group', - description='group', - metavar='GROUP'), - Flag('hosts', - description='hosts', - nargs='+', - metavar='HOST'), - ] + Flag("group", description="group", metavar="GROUP"), + Flag("hosts", description="hosts", nargs="+", metavar="HOST"), + ], ) ############################################### @@ -373,44 +319,35 @@ def host_add(args): def host_remove(args): - """ - Remove host(s) from group - """ - + """Remove host(s) from group.""" get_hostgroup(args.group) info = [] for name in args.hosts: info.append(host_info_by_name(name, follow_cname=False)) for i in info: - name = i['name'] - path = f'/api/v1/hostgroups/{args.group}/hosts/{name}' + name = i["name"] + path = f"/api/v1/hostgroups/{args.group}/hosts/{name}" history.record_delete(path, dict()) delete(path) cli_info(f"Removed host {name!r} from {args.group!r}", print_msg=True) group.add_command( - prog='host_remove', - description='Remove host(s) from group', - short_desc='Remove host(s) from group', + prog="host_remove", + description="Remove host(s) from group", + short_desc="Remove host(s) from group", callback=host_remove, flags=[ - Flag('group', - description='group', - metavar='GROUP'), - Flag('hosts', - description='host', - nargs='+', - metavar='HOST'), - ] + Flag("group", description="group", metavar="GROUP"), + Flag("hosts", description="host", nargs="+", metavar="HOST"), + ], ) + def host_list(args): - """ - List group memberships for host - """ - hostname = host_info_by_name(args.host, follow_cname=False)['name'] + """List group memberships for host.""" + hostname = host_info_by_name(args.host, follow_cname=False)["name"] group_list = get_list("/api/v1/hostgroups/", params={"hosts__name": hostname}) if len(group_list) == 0: cli_info(f"Host {hostname!r} is not a member in any hostgroup", True) @@ -421,17 +358,14 @@ def host_list(args): print(" ", group["name"]) - group.add_command( - prog='host_list', + prog="host_list", description="List host's group memberships", short_desc="List host's group memberships", callback=host_list, flags=[ - Flag('host', - description='hostname', - metavar='HOST'), - ] + Flag("host", description="hostname", metavar="HOST"), + ], ) ############################################ @@ -440,36 +374,28 @@ def host_list(args): def owner_add(args): - """ - Add owner(s) to group - """ - + """Add owner(s) to group.""" get_hostgroup(args.group) for name in args.owners: data = { - 'name': name, + "name": name, } - path = f'/api/v1/hostgroups/{args.group}/owners/' + path = f"/api/v1/hostgroups/{args.group}/owners/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Added {name!r} as owner of {args.group!r}", print_msg=True) group.add_command( - prog='owner_add', - description='Add owner(s) to group', - short_desc='Add owner(s) to group', + prog="owner_add", + description="Add owner(s) to group", + short_desc="Add owner(s) to group", callback=owner_add, flags=[ - Flag('group', - description='group', - metavar='GROUP'), - Flag('owners', - description='owners', - nargs='+', - metavar='OWNER'), - ] + Flag("group", description="group", metavar="GROUP"), + Flag("owners", description="owners", nargs="+", metavar="OWNER"), + ], ) ################################################ @@ -478,37 +404,29 @@ def owner_add(args): def owner_remove(args): - """ - Remove owner(s) from group - """ - + """Remove owner(s) from group.""" info = get_hostgroup(args.group) - names = set(i['name'] for i in info['owners']) + names = set(i["name"] for i in info["owners"]) for i in args.owners: if i not in names: cli_warning(f"{i!r} not a owner of {args.group}") for i in args.owners: - path = f'/api/v1/hostgroups/{args.group}/owners/{i}' + path = f"/api/v1/hostgroups/{args.group}/owners/{i}" history.record_delete(path, dict()) delete(path) cli_info(f"Removed {i!r} as owner of {args.group!r}", print_msg=True) group.add_command( - prog='owner_remove', - description='Remove owner(s) from group', - short_desc='Remove owner(s) from group', + prog="owner_remove", + description="Remove owner(s) from group", + short_desc="Remove owner(s) from group", callback=owner_remove, flags=[ - Flag('group', - description='group', - metavar='GROUP'), - Flag('owners', - description='owner', - nargs='+', - metavar='OWNER'), - ] + Flag("group", description="group", metavar="GROUP"), + Flag("owners", description="owner", nargs="+", metavar="OWNER"), + ], ) ################################################### @@ -517,24 +435,19 @@ def owner_remove(args): def set_description(args): - """Set description for group - """ + """Set description for group.""" get_hostgroup(args.name) patch(f"/api/v1/hostgroups/{args.name}", description=args.description) cli_info(f"updated description to {args.description!r} for {args.name!r}", True) group.add_command( - prog='set_description', - description='Set description for group', - short_desc='Set description for group', + prog="set_description", + description="Set description for group", + short_desc="Set description for group", callback=set_description, flags=[ - Flag('name', - description='Group', - metavar='GROUP'), - Flag('description', - description='Group description.', - metavar='DESC'), - ] + Flag("name", description="Group", metavar="GROUP"), + Flag("description", description="Group description.", metavar="DESC"), + ], ) diff --git a/mreg_cli/history.py b/mreg_cli/history.py index f321d167..a8dd2cf4 100644 --- a/mreg_cli/history.py +++ b/mreg_cli/history.py @@ -16,7 +16,8 @@ # which a foreign key is involved (directly or indirectly) is problematic. # Undo/redo is not RESTfull... -# QUESTION HISTORY: kanskje redo/undo kan løses ved å generere CLI kommandoer som utfører redo/undo operasjonene? +# QUESTION HISTORY: kanskje redo/undo kan løses ved å generere CLI kommandoer som utfører +# redo/undo operasjonene? class HistoryEvent: @@ -44,23 +45,33 @@ def __str__(self): def __repr__(self): return "<{} event with {} requests>".format(self.name, len(self.requests)) - def add_request(self, name: str, url: str, resource_name: str, old_data: dict, new_data: dict, - redoable: bool, undoable: bool) -> None: + def add_request( + self, + name: str, + url: str, + resource_name: str, + old_data: dict, + new_data: dict, + redoable: bool, + undoable: bool, + ) -> None: """Add a recording of a request which happened during this event.""" - self.requests.append({ - "name": name, - "url": url, - "resource_name": resource_name, - "old_data": old_data, - "new_data": new_data, - }) + self.requests.append( + { + "name": name, + "url": url, + "resource_name": resource_name, + "old_data": old_data, + "new_data": new_data, + } + ) if not redoable: self.redoable = False if not undoable: self.undoable = False def undo(self): - """Undo this event""" + """Undo this event.""" if not self.undoable: return for request in reversed(self.requests): @@ -72,18 +83,15 @@ def undo(self): res = requests.patch(url=request["url"], data=request["old_data"]) msg = "patched {}".format(request["url"]) elif request["name"] == "DELETE": - url = request["url"].rsplit(sep='/', maxsplit=1)[0] + "/" + url = request["url"].rsplit(sep="/", maxsplit=1)[0] + "/" res = requests.post(url, data=request["old_data"]) msg = "posted {}".format(url) else: continue if not res.ok: # QUESTION HISTORY: hvordan egentlig håndtere feil under undo av event? - message = "{} \"{}\": {}: {}".format( - request["name"], - request["url"], - res.status_code, - res.reason + message = '{} "{}": {}: {}'.format( + request["name"], request["url"], res.status_code, res.reason ) try: body = res.json() @@ -96,7 +104,7 @@ def undo(self): cli_info("{}".format(msg), print_msg=True) def redo(self): - """Redo this event""" + """Redo this event.""" if not self.redoable: return for request in self.requests: @@ -113,11 +121,8 @@ def redo(self): continue if not res.ok: # QUESTION HISTORY: hvordan egentlig håndtere feil under redo av event? - message = "{} \"{}\": {}: {}".format( - request["name"], - request["url"], - res.status_code, - res.reason + message = '{} "{}": {}: {}'.format( + request["name"], request["url"], res.status_code, res.reason ) try: body = res.json() @@ -138,13 +143,13 @@ def __init__(self): self.in_event = False def start_event(self, name: str) -> None: - """Start a new event which will record requests until it is ended""" + """Start a new event which will record requests until it is ended.""" if not self.in_event: self.in_event = True self.current = HistoryEvent(name, index=self.count) def end_event(self) -> None: - """End the current event""" + """End the current event.""" if self.in_event: self.in_event = False if len(self.current.requests) > 0: @@ -153,9 +158,15 @@ def end_event(self) -> None: # Setting current to a dummy event which will be lost when a new event is started. self.current = HistoryEvent() - def record_post(self, url: str, resource_name: str, new_data: dict, redoable: bool = True, - undoable: bool = True) -> None: - """Record a POST request in the current event""" + def record_post( + self, + url: str, + resource_name: str, + new_data: dict, + redoable: bool = True, + undoable: bool = True, + ) -> None: + """Record a POST request in the current event.""" self.current.add_request( name="POST", url=url, @@ -166,9 +177,15 @@ def record_post(self, url: str, resource_name: str, new_data: dict, redoable: bo undoable=undoable, ) - def record_patch(self, url: str, new_data: dict, old_data: dict, - redoable: bool = True, undoable: bool = True) -> None: - """Record a PATCH request in the current event""" + def record_patch( + self, + url: str, + new_data: dict, + old_data: dict, + redoable: bool = True, + undoable: bool = True, + ) -> None: + """Record a PATCH request in the current event.""" self.current.add_request( name="PATCH", url=url, @@ -179,9 +196,10 @@ def record_patch(self, url: str, new_data: dict, old_data: dict, undoable=undoable, ) - def record_delete(self, url: str, old_data: dict, redoable: bool = True, - undoable: bool = True) -> None: - """Record a DELETE request in the current event""" + def record_delete( + self, url: str, old_data: dict, redoable: bool = True, undoable: bool = True + ) -> None: + """Record a DELETE request in the current event.""" self.current.add_request( name="DELETE", url=url, @@ -192,8 +210,10 @@ def record_delete(self, url: str, old_data: dict, redoable: bool = True, undoable=undoable, ) - def record_get(self, url: str, redoable: bool = True, undoable: bool = True) -> None: - """Record a GET request in the current event""" + def record_get( + self, url: str, redoable: bool = True, undoable: bool = True + ) -> None: + """Record a GET request in the current event.""" self.current.add_request( name="GET", url=url, @@ -204,7 +224,7 @@ def record_get(self, url: str, redoable: bool = True, undoable: bool = True) -> undoable=undoable, ) - def print(self): + def print(self): # noqa: A003 (Shadowing builting), needs fixing for e in self.events: print(e) @@ -244,9 +264,9 @@ def undo(self, event_num: int): #################################### history_ = cli.add_command( - prog='history', - description='Undo, redo or print history for this program session.', - short_desc='Session history' + prog="history", + description="Undo, redo or print history for this program session.", + short_desc="Session history", ) @@ -254,14 +274,15 @@ def undo(self, event_num: int): # Implementation of sub command 'print' # ######################################### + def print_(args): - print('printing history.') + print("printing history.") history_.add_command( - prog='print', - description='Print the history', - short_desc='Print the history', + prog="print", + description="Print the history", + short_desc="Print the history", callback=print_, ) @@ -270,21 +291,19 @@ def print_(args): # Implementation of sub command 'redo' # ######################################## + def redo(args): - print('redo:', args.num) + print("redo:", args.num) history_.add_command( - prog='redo', - description='Redo some history event given by NUM (GET ' - 'requests are not redone)', - short_desc='Redo history.', + prog="redo", + description="Redo some history event given by NUM (GET requests are not redone)", + short_desc="Redo history.", callback=redo, flags=[ - Flag('num', - description='History number of the event to redo.', - metavar='NUM'), - ] + Flag("num", description="History number of the event to redo.", metavar="NUM"), + ], ) @@ -292,19 +311,18 @@ def redo(args): # Implementation of sub command 'undo' # ######################################## + def undo(args): - print('undo:', args.num) + print("undo:", args.num) history_.add_command( - prog='undo', - description='Undo some history event given by (GET ' - 'requests are not redone)', - short_desc='Undo history.', + prog="undo", + description="Undo some history event given by (GET " + "requests are not redone)", + short_desc="Undo history.", callback=undo, flags=[ - Flag('num', - description='History number of the event to undo.', - metavar='NUM'), - ] + Flag("num", description="History number of the event to undo.", metavar="NUM"), + ], ) diff --git a/mreg_cli/history_log.py b/mreg_cli/history_log.py index 779da7fe..97b13998 100644 --- a/mreg_cli/history_log.py +++ b/mreg_cli/history_log.py @@ -17,7 +17,7 @@ def get_history_items(name, resource, data_relation=None): if len(ret) == 0: cli_warning(f"No history found for {name}") # Get all model ids, a group gets a new one when deleted and created again - model_ids = { str(i["model_id"]) for i in ret } + model_ids = {str(i["model_id"]) for i in ret} model_ids = ",".join(model_ids) params = { "resource": resource, @@ -34,47 +34,50 @@ def get_history_items(name, resource, data_relation=None): def print_history_items(ownname, items): - def _remove_unneded_keys(data): - for key in ('id', 'created_at', 'updated_at',): + for key in ( + "id", + "created_at", + "updated_at", + ): data.pop(key, None) - for i in sorted(items, key=lambda i: parse(i['timestamp'])): - timestamp = parse(i['timestamp']).strftime('%Y-%m-%d %H:%M:%S') - msg = '' - if isinstance(i['data'], dict): - data = i['data'] + for i in sorted(items, key=lambda i: parse(i["timestamp"])): + timestamp = parse(i["timestamp"]).strftime("%Y-%m-%d %H:%M:%S") + msg = "" + if isinstance(i["data"], dict): + data = i["data"] else: - data = json.loads(i['data']) - action = i['action'] - model = i['model'] - if action in ('add', 'remove'): - if i['name'] == ownname: - msg = data['name'] + data = json.loads(i["data"]) + action = i["action"] + model = i["model"] + if action in ("add", "remove"): + if i["name"] == ownname: + msg = data["name"] else: - msg = i['resource'] + ' ' + i['name'] - if action == 'add': - action = 'add to' - elif action == 'remove': - action = 'remove from' - elif action == 'create': - msg = ', '.join(f"{k} = '{v}'" for k,v in data.items()) - elif action == 'update': - if model in ('Ipaddress', ): - msg = data['current_data']['ipaddress'] + ', ' + msg = i["resource"] + " " + i["name"] + if action == "add": + action = "add to" + elif action == "remove": + action = "remove from" + elif action == "create": + msg = ", ".join(f"{k} = '{v}'" for k, v in data.items()) + elif action == "update": + if model in ("Ipaddress",): + msg = data["current_data"]["ipaddress"] + ", " changes = [] - for key, newval in data['update'].items(): - oldval = data["current_data"][key] or 'not set' - newval = newval or 'not set' + for key, newval in data["update"].items(): + oldval = data["current_data"][key] or "not set" + newval = newval or "not set" changes.append(f"{key}: {oldval} -> {newval}") - msg += ','.join(changes) - elif action == 'destroy': + msg += ",".join(changes) + elif action == "destroy": _remove_unneded_keys(data) - if model == 'Host': + if model == "Host": msg = "deleted " + i["name"] else: - msg = ', '.join(f"{k} = '{v}'" for k,v in data.items()) + msg = ", ".join(f"{k} = '{v}'" for k, v in data.items()) else: - cli_warning(f'Unhandled history entry: {i}') + cli_warning(f"Unhandled history entry: {i}") print(f"{timestamp} [{i['user']}]: {model} {action}: {msg}") diff --git a/mreg_cli/host.py b/mreg_cli/host.py index 791aa30f..78a33d9c 100644 --- a/mreg_cli/host.py +++ b/mreg_cli/host.py @@ -1,7 +1,6 @@ import ipaddress from typing import Iterable, Optional - from .cli import Flag, cli from .dhcp import assoc_mac_to_ip from .exceptions import HostNotFoundWarning @@ -41,21 +40,21 @@ ################################# host = cli.add_command( - prog='host', - description='Manage hosts.', - short_desc='Manage hosts', + prog="host", + description="Manage hosts.", + short_desc="Manage hosts", ) def print_hinfo(hinfo: dict, padding: int = 14) -> None: - """Pretty given hinfo id""" + """Pretty given hinfo id.""" if hinfo is None: return - print("{1:<{0}}cpu={2} os={3}".format(padding, "Hinfo:", hinfo['cpu'], hinfo['os'])) + print("{1:<{0}}cpu={2} os={3}".format(padding, "Hinfo:", hinfo["cpu"], hinfo["os"])) def zoneinfo_for_hostname(host: str) -> Optional[dict]: - """Return zoneinfo for a hostname, or None if not found or invalid""" + """Return zoneinfo for a hostname, or None if not found or invalid.""" if "." not in host: return None @@ -73,13 +72,12 @@ def check_zone_for_hostname(name: str, force: bool, require_zone: bool = False): cli_warning(f"{name} isn't in a zone controlled by MREG.") if not force: cli_warning(f"{name} isn't in a zone controlled by MREG, must force") - elif 'delegation' in zoneinfo and not force: - delegation = zoneinfo['delegation']['name'] + elif "delegation" in zoneinfo and not force: + delegation = zoneinfo["delegation"]["name"] cli_warning(f"{name} is in zone delegation {delegation}, must force") def _get_ip_from_args(ip, force, ipversion=None): - # Try to fail fast for valid IP if ipversion is not None and is_valid_ip(ip): if ipversion == 4: @@ -107,8 +105,8 @@ def _get_ip_from_args(ip, force, ipversion=None): path = "/api/v1/hosts/" hosts = get_list(path, params={"ipaddresses__ipaddress": ip}) if hosts and not force: - hostnames = ','.join([i['name'] for i in hosts]) - cli_warning(f'{ip} already in use by: {hostnames}. Must force') + hostnames = ",".join([i["name"] for i in hosts]) + cli_warning(f"{ip} already in use by: {hostnames}. Must force") network = get_network_by_ip(ip) if not network: if force: @@ -117,7 +115,7 @@ def _get_ip_from_args(ip, force, ipversion=None): else: cli_warning(f"Could not determine network for {ip}") - network_object = ipaddress.ip_network(network['network']) + network_object = ipaddress.ip_network(network["network"]) if ipversion: if network_object.version != ipversion: if ipversion == 4: @@ -126,10 +124,9 @@ def _get_ip_from_args(ip, force, ipversion=None): cli_warning("Attemptet to get an ipv6 address, but input yielded ipv4") if network["frozen"] and not force: - cli_warning("network {} is frozen, must force" - .format(network["network"])) + cli_warning("network {} is frozen, must force".format(network["network"])) # Chat the address given isn't reserved - reserved_addresses = get_network_reserved_ips(network['network']) + reserved_addresses = get_network_reserved_ips(network["network"]) if ip in reserved_addresses and not force: cli_warning("Address is reserved. Requires force") if network_object.num_addresses > 2: @@ -145,12 +142,12 @@ def _check_ipversion(ip, ipversion): # Ip sanity check if ipversion == 4: if not is_valid_ipv4(ip): - cli_warning(f'not a valid ipv4: {ip}') + cli_warning(f"not a valid ipv4: {ip}") elif ipversion == 6: if not is_valid_ipv6(ip): - cli_warning(f'not a valid ipv6: {ip}') + cli_warning(f"not a valid ipv6: {ip}") else: - cli_warning(f'Unknown ipversion: {ipversion}') + cli_warning(f"Unknown ipversion: {ipversion}") ################################################################################ @@ -164,11 +161,11 @@ def _check_ipversion(ip, ipversion): # Implementation of sub command 'add' # ######################################### + def add(args): """Add a new host with the given name. - ip/network, comment and contact are optional. + ip/network, comment and contact are optional. """ - # Fail if given host exists name = clean_hostname(args.name) try: @@ -196,8 +193,9 @@ def add(args): if args.contact and not is_valid_email(args.contact): cli_warning( "invalid mail address ({}) when trying to add {}".format( - args.contact, - args.name)) + args.contact, args.name + ) + ) # Create the new host with an ip address path = "/api/v1/hosts/" @@ -207,14 +205,14 @@ def add(args): "comment": args.comment or None, } if args.ip: - data['ipaddress'] = ip + data["ipaddress"] = ip history.record_post(path, resource_name=name, new_data=data) post(path, **data) if args.macaddress is not None: - # It can only be one, as it was just created. - ipdata = get(f"{path}{name}").json()['ipaddresses'][0] - assoc_mac_to_ip(args.macaddress, ipdata, force=args.force) + # It can only be one, as it was just created. + ipdata = get(f"{path}{name}").json()["ipaddresses"][0] + assoc_mac_to_ip(args.macaddress, ipdata, force=args.force) msg = f"created host {name}" if args.ip: msg += f" with IP {ip}" @@ -223,33 +221,33 @@ def add(args): # Add 'add' as a sub command to the 'host' command host.add_command( - prog='add', - description='Add a new host with the given name, ip or network and contact. ' - 'comment is optional.', - short_desc='Add a new host', + prog="add", + description="Add a new host with the given name, ip or network and contact. " + "comment is optional.", + short_desc="Add a new host", callback=add, flags=[ - Flag('name', - short_desc='Name of new host (req)', - description='Name of new host (req)'), - Flag('-ip', - short_desc='An ip or net', - description="The hosts ip or a network. If it's a network the first free IP is " - "selected from the network", - metavar='IP/NET'), - Flag('-contact', - short_desc='Contact mail for the host', - description='Contact mail for the host'), - Flag('-comment', - short_desc='A comment.', - description='A comment.'), - Flag('-macaddress', - description='Mac address', - metavar='MACADDRESS'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag( + "name", + short_desc="Name of new host (req)", + description="Name of new host (req)", + ), + Flag( + "-ip", + short_desc="An ip or net", + description="The hosts ip or a network. If it's a network the first free IP is " + "selected from the network", + metavar="IP/NET", + ), + Flag( + "-contact", + short_desc="Contact mail for the host", + description="Contact mail for the host", + ), + Flag("-comment", short_desc="A comment.", description="A comment."), + Flag("-macaddress", description="Mac address", metavar="MACADDRESS"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -257,10 +255,10 @@ def add(args): # Implementation of sub command 'remove' # ############################################ + def remove(args): # args.name, args.force """Remove host.""" - # Get host info or raise exception info = host_info_by_name_or_ip(args.name) @@ -279,30 +277,34 @@ def remove(args): # force path = "/api/v1/naptrs/" history.record_get(path) - naptrs = get_list(path, params={"host": info['id']}) + naptrs = get_list(path, params={"host": info["id"]}) if len(naptrs) > 0: if not args.force: warn_msg += "{} NAPTR records. ".format(len(naptrs)) else: for naptr in naptrs: - cli_info("deleted NAPTR record {} when removing {}".format( - naptr["replacement"], - info["name"], - )) + cli_info( + "deleted NAPTR record {} when removing {}".format( + naptr["replacement"], + info["name"], + ) + ) # Require force if host has any SRV records. Delete the SRV records if force path = "/api/v1/srvs/" history.record_get(path) - srvs = get_list(path, params={"host__name": info['name']}) + srvs = get_list(path, params={"host__name": info["name"]}) if len(srvs) > 0: if not args.force: warn_msg += "{} SRV records. ".format(len(srvs)) else: for srv in srvs: - cli_info("deleted SRV record {} when removing {}".format( - srv["name"], - info["name"], - )) + cli_info( + "deleted SRV record {} when removing {}".format( + srv["name"], + info["name"], + ) + ) # Require force if host has any PTR records. Delete the PTR records if force if len(info["ptr_overrides"]) > 0: @@ -310,10 +312,12 @@ def remove(args): warn_msg += "{} PTR records. ".format(len(info["ptr_overrides"])) else: for ptr in info["ptr_overrides"]: - cli_info("deleted PTR record {} when removing {}".format( - ptr["ipaddress"], - info["name"], - )) + cli_info( + "deleted PTR record {} when removing {}".format( + ptr["ipaddress"], + info["name"], + ) + ) # To be able to undo the delete the ipaddress field of the 'old_data' has to # be an ipaddress string @@ -333,18 +337,18 @@ def remove(args): # Add 'remove' as a sub command to the 'host' command host.add_command( - prog='remove', - description='Remove the given host.', + prog="remove", + description="Remove the given host.", callback=remove, flags=[ - Flag('name', - short_desc='Name or ip.', - description='Name of host or an ip belonging to the host.', - metavar='NAME/IP'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag( + "name", + short_desc="Name or ip.", + description="Name of host or an ip belonging to the host.", + metavar="NAME/IP", + ), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -354,6 +358,7 @@ def remove(args): # first some print helpers + def print_host_name(name: str, padding: int = 14) -> None: """Pretty print given name.""" if name is None: @@ -381,24 +386,26 @@ def print_comment(comment: str, padding: int = 14) -> None: def print_ipaddresses( ipaddresses: Iterable[dict], names: bool = False, padding: int = 14 ) -> None: - """Pretty print given ip addresses""" + """Pretty print given ip addresses.""" + def _find_padding(lst, attr): - return max(padding, max([len(i[attr]) for i in lst])+1) + return max(padding, max([len(i[attr]) for i in lst]) + 1) + if not ipaddresses: return a_records = [] aaaa_records = [] - len_ip = _find_padding(ipaddresses, 'ipaddress') + len_ip = _find_padding(ipaddresses, "ipaddress") for record in ipaddresses: if is_valid_ipv4(record["ipaddress"]): a_records.append(record) elif is_valid_ipv6(record["ipaddress"]): aaaa_records.append(record) if names: - len_names = _find_padding(ipaddresses, 'name') + len_names = _find_padding(ipaddresses, "name") else: len_names = padding - for records, text in ((a_records, 'A_Records'), (aaaa_records, 'AAAA_Records')): + for records, text in ((a_records, "A_Records"), (aaaa_records, "AAAA_Records")): if records: print("{1:<{0}}{2:<{3}} {4}".format(len_names, text, "IP", len_ip, "MAC")) for record in records: @@ -408,56 +415,65 @@ def _find_padding(lst, attr): name = record["name"] else: name = "" - print("{1:<{0}}{2:<{3}} {4}".format( - len_names, name, ip if ip else "", len_ip, - mac if mac else "")) + print( + "{1:<{0}}{2:<{3}} {4}".format( + len_names, + name, + ip if ip else "", + len_ip, + mac if mac else "", + ) + ) def print_ttl(ttl: int, padding: int = 14) -> None: - """Pretty print given ttl""" + """Pretty print given ttl.""" assert isinstance(ttl, int) or ttl is None print("{1:<{0}}{2}".format(padding, "TTL:", ttl or "(Default)")) - def print_loc(loc: dict, padding: int = 14) -> None: - """Pretty print given loc""" + """Pretty print given loc.""" if loc is None: return assert isinstance(loc, dict) - print("{1:<{0}}{2}".format(padding, "Loc:", loc['loc'])) + print("{1:<{0}}{2}".format(padding, "Loc:", loc["loc"])) def print_cname(cname: str, host: str, padding: int = 14) -> None: - """Pretty print given cname""" + """Pretty print given cname.""" print("{1:<{0}}{2} -> {3}".format(padding, "Cname:", cname, host)) def print_mx(mxs: dict, padding: int = 14) -> None: - """Pretty print all MXs""" + """Pretty print all MXs.""" if not mxs: return len_pri = len("Priority") print("{1:<{0}}{2} {3}".format(padding, "MX:", "Priority", "Server")) - for mx in sorted(mxs, key=lambda i: i['priority']): - print("{1:<{0}}{2:>{3}} {4}".format(padding, "", mx['priority'], len_pri, mx['mx'])) + for mx in sorted(mxs, key=lambda i: i["priority"]): + print( + "{1:<{0}}{2:>{3}} {4}".format( + padding, "", mx["priority"], len_pri, mx["mx"] + ) + ) def print_naptr(naptr: dict, host_name: str, padding: int = 14) -> None: - """Pretty print given txt""" + """Pretty print given txt.""" assert isinstance(naptr, dict) assert isinstance(host_name, str) def print_ptr(ip: str, host_name: str, padding: int = 14) -> None: - """Pretty print given txt""" + """Pretty print given txt.""" assert isinstance(ip, str) assert isinstance(host_name, str) - print("{1:<{0}}{2} -> {3}".format(padding, 'PTR override:', ip, host_name)) + print("{1:<{0}}{2} -> {3}".format(padding, "PTR override:", ip, host_name)) def print_txt(txt: str, padding: int = 14) -> None: - """Pretty print given txt""" + """Pretty print given txt.""" if txt is None: return assert isinstance(txt, str) @@ -465,11 +481,11 @@ def print_txt(txt: str, padding: int = 14) -> None: def print_bacnetid(bacnetid: dict, padding: int = 14) -> None: - """Pretty print given txt""" + """Pretty print given txt.""" if bacnetid is None: return assert isinstance(bacnetid, dict) - print("{1:<{0}}{2}".format(padding, "BACnet ID:", bacnetid['id'])) + print("{1:<{0}}{2}".format(padding, "BACnet ID:", bacnetid["id"])) def _print_host_info(info): @@ -482,15 +498,15 @@ def _print_host_info(info): for ptr in info["ptr_overrides"]: print_ptr(ptr["ipaddress"], info["name"]) print_ttl(info["ttl"]) - print_mx(info['mxs']) - print_hinfo(info['hinfo']) + print_mx(info["mxs"]) + print_hinfo(info["hinfo"]) if info["loc"]: print_loc(info["loc"]) for cname in info["cnames"]: print_cname(cname["name"], info["name"]) for txt in info["txts"]: print_txt(txt["txt"]) - _srv_show(host_id=info['id']) + _srv_show(host_id=info["id"]) _naptr_show(info) _sshfp_show(info) if "bacnetid" in info: @@ -511,17 +527,17 @@ def _print_ip_info(ip): ipaddresses = [] ptrhost = None for info in hosts: - for i in info['ipaddresses']: - if i['ipaddress'] == ip: - i['name'] = info['name'] + for i in info["ipaddresses"]: + if i["ipaddress"] == ip: + i["name"] = info["name"] ipaddresses.append(i) - for i in info['ptr_overrides']: - if i['ipaddress'] == ip: - ptrhost = info['name'] + for i in info["ptr_overrides"]: + if i["ipaddress"] == ip: + ptrhost = info["name"] print_ipaddresses(ipaddresses, names=True) if len(ipaddresses) > 1 and ptrhost is None: - cli_warning(f'IP {ip} used by {len(ipaddresses)} hosts, but no PTR override') + cli_warning(f"IP {ip} used by {len(ipaddresses)} hosts, but no PTR override") if ptrhost is None: path = "/api/v1/hosts/" params = { @@ -530,11 +546,11 @@ def _print_ip_info(ip): history.record_get(path) hosts = get_list(path, params=params) if hosts: - ptrhost = hosts[0]['name'] + ptrhost = hosts[0]["name"] elif ipaddresses: - ptrhost = 'default' + ptrhost = "default" if not ipaddresses and ptrhost is None: - cli_warning(f'Found no hosts or ptr override matching IP {ip}') + cli_warning(f"Found no hosts or ptr override matching IP {ip}") print_ptr(ip, ptrhost) @@ -552,48 +568,49 @@ def info_(args): if ret: _print_host_info(ret[0]) else: - cli_warning(f'Found no host with macaddress: {mac}') + cli_warning(f"Found no host with macaddress: {mac}") else: info = host_info_by_name(name_or_ip) name = clean_hostname(name_or_ip) - if any(cname['name'] == name for cname in info['cnames']): + if any(cname["name"] == name for cname in info["cnames"]): print(f'{name} is a CNAME for {info["name"]}') _print_host_info(info) # Add 'info' as a sub command to the 'host' command host.add_command( - prog='info', - description='Print info about one or more hosts.', - short_desc='Print info about one or more hosts.', + prog="info", + description="Print info about one or more hosts.", + short_desc="Print info about one or more hosts.", callback=info_, flags=[ - Flag('hosts', - description='One or more hosts given by their name, ip or mac.', - short_desc='One or more names, ips or macs.', - nargs='+', - metavar='NAME/IP/MAC') - ] + Flag( + "hosts", + description="One or more hosts given by their name, ip or mac.", + short_desc="One or more names, ips or macs.", + nargs="+", + metavar="NAME/IP/MAC", + ) + ], ) def find(args): - """List hosts maching search criteria - """ + """List hosts maching search criteria.""" def _add_param(param, value): param, value = convert_wildcard_to_regex(param, value, True) params[param] = value if not any([args.name, args.comment, args.contact]): - cli_warning('Need at least one search critera') + cli_warning("Need at least one search critera") params = { "ordering": "name", "page_size": 1, } - for param in ('contact', 'comment', 'name'): + for param in ("contact", "comment", "name"): value = getattr(args, param) if value: _add_param(param, value) @@ -601,44 +618,57 @@ def _add_param(param, value): path = "/api/v1/hosts/" ret = get(path, params=params).json() - if ret['count'] == 0: - cli_warning('No hosts found.') - elif ret['count'] > 500: - cli_warning(f'Too many hits, {ret["count"]}, more than limit of 500. Refine search.') + if ret["count"] == 0: + cli_warning("No hosts found.") + elif ret["count"] > 500: + cli_warning( + f'Too many hits, {ret["count"]}, more than limit of 500. Refine search.' + ) - del(params["page_size"]) + del params["page_size"] ret = get_list(path, params=params) max_name = max_contact = 20 for i in ret: - max_name = max(max_name, len(i['name'])) - max_contact = max(max_contact, len(i['contact'])) + max_name = max(max_name, len(i["name"])) + max_contact = max(max_contact, len(i["contact"])) def _print(name, contact, comment): - print("{0:<{1}} {2:<{3}} {4}".format(name, max_name, contact, max_contact, comment)) + print( + "{0:<{1}} {2:<{3}} {4}".format( + name, max_name, contact, max_contact, comment + ) + ) - _print('Name', 'Contact', 'Comment') + _print("Name", "Contact", "Comment") for i in ret: - _print(i['name'], i['contact'], i['comment']) + _print(i["name"], i["contact"], i["comment"]) + host.add_command( - prog='find', - description='Lists hosts matching search criteria', - short_desc='Lists hosts matching search criteria', + prog="find", + description="Lists hosts matching search criteria", + short_desc="Lists hosts matching search criteria", callback=find, flags=[ - Flag('-name', - description='Name or part of name', - short_desc='Name or part of name', - metavar='NAME'), - Flag('-comment', - description='Comment or part of comment', - short_desc='Comment or part of comment', - metavar='CONTACT'), - Flag('-contact', - description='Contact or part of contact', - short_desc='Contact or part of contact', - metavar='CONTACT') - ] + Flag( + "-name", + description="Name or part of name", + short_desc="Name or part of name", + metavar="NAME", + ), + Flag( + "-comment", + description="Comment or part of comment", + short_desc="Comment or part of comment", + metavar="CONTACT", + ), + Flag( + "-contact", + description="Contact or part of contact", + short_desc="Contact or part of contact", + metavar="CONTACT", + ), + ], ) @@ -646,10 +676,9 @@ def _print(name, contact, comment): # Implementation of sub command 'rename' # ############################################ -def rename(args): - """Rename host. If is an alias then the alias is renamed. - """ +def rename(args): + """Rename host. If is an alias then the alias is renamed.""" # Find old host old_name = resolve_input_name(args.old_name) @@ -678,8 +707,7 @@ def rename(args): # Rename host path = f"/api/v1/hosts/{old_name}" # Cannot redo/undo now since it changes name - history.record_patch(path, new_data, old_data, redoable=False, - undoable=False) + history.record_patch(path, new_data, old_data, redoable=False, undoable=False) patch(path, name=new_name) cli_info("renamed {} to {}".format(old_name, new_name), print_msg=True) @@ -687,24 +715,26 @@ def rename(args): # Add 'rename' as a sub command to the 'host' command host.add_command( - prog='rename', - description='Rename host. If the old name is an alias then the alias is ' - 'renamed.', - short_desc='Rename a host', + prog="rename", + description="Rename host. If the old name is an alias then the alias is " + "renamed.", + short_desc="Rename a host", callback=rename, flags=[ - Flag('old_name', - description='Host name of the host to rename. May be an alias. ' - 'If it is an alias then the alias is renamed.', - short_desc='Existing host name.', - metavar='OLD'), - Flag('new_name', - description='New name for the host, or alias.', - short_desc='New name', - metavar='NEW'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag( + "old_name", + description="Host name of the host to rename. May be an alias. " + "If it is an alias then the alias is renamed.", + short_desc="Existing host name.", + metavar="OLD", + ), + Flag( + "new_name", + description="New name for the host, or alias.", + short_desc="New name", + metavar="NEW", + ), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -713,9 +743,9 @@ def rename(args): # Implementation of sub command 'set_comment' # ################################################# + def set_comment(args): - """Set comment for host. If is an alias the cname host is updated. - """ + """Set comment for host. If is an alias the cname host is updated.""" # Get host info or raise exception info = host_info_by_name(args.name) old_data = {"comment": info["comment"] or ""} @@ -725,25 +755,27 @@ def set_comment(args): path = f"/api/v1/hosts/{info['name']}" history.record_patch(path, new_data, old_data) patch(path, comment=args.comment) - cli_info("Updated comment of {} to \"{}\"" - .format(info["name"], args.comment), print_msg=True) + cli_info( + 'Updated comment of {} to "{}"'.format(info["name"], args.comment), + print_msg=True, + ) # Add 'set_comment' as a sub command to the 'host' command host.add_command( - prog='set_comment', - description='Set comment for host. If NAME is an alias the cname host is ' - 'updated.', - short_desc='Set comment.', + prog="set_comment", + description="Set comment for host. If NAME is an alias the cname host is " + "updated.", + short_desc="Set comment.", callback=set_comment, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('comment', - description='The new comment. If it contains spaces then it must ' - 'be enclosed in quotes.', - metavar='COMMENT') + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag( + "comment", + description="The new comment. If it contains spaces then it must " + "be enclosed in quotes.", + metavar="COMMENT", + ), ], ) @@ -752,13 +784,14 @@ def set_comment(args): # Implementation of sub command 'set_contact' # ################################################# + def set_contact(args): - """Set contact for host. If is an alias the cname host is updated. - """ + """Set contact for host. If is an alias the cname host is updated.""" # Contact sanity check if not is_valid_email(args.contact): - cli_warning("invalid mail address {} (target host: {})".format( - args.contact, args.name)) + cli_warning( + "invalid mail address {} (target host: {})".format(args.contact, args.name) + ) # Get host info or raise exception info = host_info_by_name(args.name) @@ -769,24 +802,21 @@ def set_contact(args): path = f"/api/v1/hosts/{info['name']}" history.record_patch(path, new_data, old_data) patch(path, contact=args.contact) - cli_info("Updated contact of {} to {}".format(info["name"], args.contact), - print_msg=True) + cli_info( + "Updated contact of {} to {}".format(info["name"], args.contact), print_msg=True + ) # Add 'set_contact' as a sub command to the 'host' command host.add_command( - prog='set_contact', - description='Set contact for host. If NAME is an alias the cname host is ' - 'updated.', - short_desc='Set contact.', + prog="set_contact", + description="Set contact for host. If NAME is an alias the cname host is " + "updated.", + short_desc="Set contact.", callback=set_contact, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('contact', - description='Mail address of the contact.', - metavar='CONTACT') + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("contact", description="Mail address of the contact.", metavar="CONTACT"), ], ) @@ -797,6 +827,7 @@ def set_contact(args): # # ################################################################################ + def _ip_add(args, ipversion, macaddress=None): info = None @@ -819,8 +850,7 @@ def _ip_add(args, ipversion, macaddress=None): if info is None: hostname = clean_hostname(args.name) - data = {'name': hostname, - 'ipaddress': ip} + data = {"name": hostname, "ipaddress": ip} # Create new host with IP path = "/api/v1/hosts/" history.record_post(path, ip, data) @@ -828,14 +858,15 @@ def _ip_add(args, ipversion, macaddress=None): cli_info(f"Created host {hostname} with ip {ip}", print_msg=True) if macaddress is not None: # It can only be one, as it was just created. - ip = get(f"{path}{hostname}").json()['ipaddresses'][0] + ip = get(f"{path}{hostname}").json()["ipaddresses"][0] assoc_mac_to_ip(macaddress, ip, force=args.force) else: # Require force if host has multiple A/AAAA records if len(info["ipaddresses"]) and not args.force: - cli_warning("{} already has A/AAAA record(s), must force" - .format(info["name"])) + cli_warning( + "{} already has A/AAAA record(s), must force".format(info["name"]) + ) if any(args.ip == i["ipaddress"] for i in info["ipaddresses"]): cli_warning(f"Host already has IP {args.ip}") @@ -845,7 +876,7 @@ def _ip_add(args, ipversion, macaddress=None): "ipaddress": ip, } if macaddress is not None: - data['macaddress'] = macaddress + data["macaddress"] = macaddress # Add IP path = "/api/v1/ipaddresses/" @@ -858,34 +889,30 @@ def _ip_add(args, ipversion, macaddress=None): # Implementation of sub command 'a_add' # ########################################### + def a_add(args): - """Add an A record to host. If is an alias the cname host is used. - """ + """Add an A record to host. If is an alias the cname host is used.""" _ip_add(args, 4, macaddress=args.macaddress) # Add 'a_add' as a sub command to the 'host' command host.add_command( - prog='a_add', - description='Add an A record to host. If NAME is an alias the cname host ' - 'is used.', - short_desc='Add A record.', + prog="a_add", + description="Add an A record to host. If NAME is an alias the cname host " + "is used.", + short_desc="Add A record.", callback=a_add, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('ip', - description='The IP of new A record. May also be a network, ' - 'in which case a random IP address from that network ' - 'is chosen.', - metavar='IP/network'), - Flag('-macaddress', - description='Mac address', - metavar='MACADDRESS'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag( + "ip", + description="The IP of new A record. May also be a network, " + "in which case a random IP address from that network " + "is chosen.", + metavar="IP/network", + ), + Flag("-macaddress", description="Mac address", metavar="MACADDRESS"), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -894,6 +921,7 @@ def a_add(args): # Implementation of sub command 'a_change' # ############################################## + def _ip_change(args, ipversion): if args.old == args.new: cli_warning("New and old IP are equal") @@ -908,7 +936,7 @@ def _ip_change(args, ipversion): ip_id = i["id"] break else: - cli_warning("\"{}\" is not owned by {}".format(args.old, info["name"])) + cli_warning('"{}" is not owned by {}'.format(args.old, info["name"])) new_ip = _get_ip_from_args(args.new, args.force, ipversion=ipversion) @@ -918,47 +946,49 @@ def _ip_change(args, ipversion): # Update A/AAAA records ip address path = f"/api/v1/ipaddresses/{ip_id}" # Cannot redo/undo since recourse name changes - history.record_patch(path, new_data, old_data, redoable=False, - undoable=False) + history.record_patch(path, new_data, old_data, redoable=False, undoable=False) patch(path, ipaddress=new_ip) cli_info( "changed ip {} to {} for {}".format(args.old, new_ip, info["name"]), - print_msg=True) + print_msg=True, + ) def a_change(args): - """Change A record. If is an alias the cname host is used. - """ - + """Change A record. If is an alias the cname host is used.""" _ip_change(args, 4) # Add 'a_change' as a sub command to the 'host' command host.add_command( - prog='a_change', - description='Change an A record for the target host. If NAME is an alias ' - 'the cname host is used.', - short_desc='Change A record.', + prog="a_change", + description="Change an A record for the target host. If NAME is an alias " + "the cname host is used.", + short_desc="Change A record.", callback=a_change, flags=[ - Flag('name', - description='Name of the target host.', - short_desc='Host name.', - metavar='NAME'), - Flag('-old', - description='The existing IP that should be changed.', - short_desc='IP to change.', - required=True, - metavar='IP'), - Flag('-new', - description='The new IP address. May also be a network, in which ' - 'case a random IP from that network is chosen.', - short_desc='New IP.', - required=True, - metavar='IP/network'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag( + "name", + description="Name of the target host.", + short_desc="Host name.", + metavar="NAME", + ), + Flag( + "-old", + description="The existing IP that should be changed.", + short_desc="IP to change.", + required=True, + metavar="IP", + ), + Flag( + "-new", + description="The new IP address. May also be a network, in which " + "case a random IP from that network is chosen.", + short_desc="New IP.", + required=True, + metavar="IP/network", + ), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -972,55 +1002,54 @@ def _ip_move(args, ipversion): frominfo = host_info_by_name(args.fromhost) toinfo = host_info_by_name(args.tohost) ip_id = None - for ip in frominfo['ipaddresses']: - if ip['ipaddress'] == args.ip: - ip_id = ip['id'] + for ip in frominfo["ipaddresses"]: + if ip["ipaddress"] == args.ip: + ip_id = ip["id"] ptr_id = None - for ptr in frominfo['ptr_overrides']: - if ptr['ipaddress'] == args.ip: - ptr_id = ptr['id'] + for ptr in frominfo["ptr_overrides"]: + if ptr["ipaddress"] == args.ip: + ptr_id = ptr["id"] if ip_id is None and ptr_id is None: cli_warning(f'Host {frominfo["name"]} have no IP or PTR with address {args.ip}') msg = "" if ip_id: - path = f'/api/v1/ipaddresses/{ip_id}' - patch(path, host=toinfo['id']) - msg = f'Moved ipaddress {args.ip}' + path = f"/api/v1/ipaddresses/{ip_id}" + patch(path, host=toinfo["id"]) + msg = f"Moved ipaddress {args.ip}" else: - msg += f'No ipaddresses matched. ' + msg += "No ipaddresses matched. " if ptr_id: - path = f'/api/v1/ptroverrides/{ptr_id}' - patch(path, host=toinfo['id']) - msg += 'Moved PTR override.' + path = f"/api/v1/ptroverrides/{ptr_id}" + patch(path, host=toinfo["id"]) + msg += "Moved PTR override." cli_info(msg, print_msg=True) def a_move(args): - """Move an IP from a host to another host. Will move also move the PTR, if any. - """ - + """Move an IP from a host to another host. Will move also move the PTR, if any.""" _ip_move(args, 4) # Add 'a_move' as a sub command to the 'host' command host.add_command( - prog='a_move', - description='Move A record from a host to another host', - short_desc='Move A record', + prog="a_move", + description="Move A record from a host to another host", + short_desc="Move A record", callback=a_move, flags=[ - Flag('-ip', - description='IP to move', - required=True, - metavar='IP'), - Flag('-fromhost', - description='Name of source host', - required=True, - metavar='NAME'), - Flag('-tohost', - description='Name of destination host', - required=True, - metavar='NAME'), + Flag("-ip", description="IP to move", required=True, metavar="IP"), + Flag( + "-fromhost", + description="Name of source host", + required=True, + metavar="NAME", + ), + Flag( + "-tohost", + description="Name of destination host", + required=True, + metavar="NAME", + ), ], ) @@ -1029,6 +1058,7 @@ def a_move(args): # Implementation of sub command 'a_remove' # ############################################## + def _ip_remove(args, ipversion): ip_id = None @@ -1052,30 +1082,23 @@ def _ip_remove(args, ipversion): path = f"/api/v1/ipaddresses/{ip_id}" history.record_delete(path, old_data) delete(path) - cli_info("removed ip {} from {}".format(args.ip, info["name"]), - print_msg=True) + cli_info("removed ip {} from {}".format(args.ip, info["name"]), print_msg=True) def a_remove(args): - """Remove A record from host. If is an alias the cname host is used. - """ - + """Remove A record from host. If is an alias the cname host is used.""" _ip_remove(args, 4) # Add 'a_remove' as a sub command to the 'host' command host.add_command( - prog='a_remove', - description='Remove an A record from the target host.', - short_desc='Remove A record.', + prog="a_remove", + description="Remove an A record from the target host.", + short_desc="Remove A record.", callback=a_remove, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('ip', - description='IP to remove.', - metavar='IP'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("ip", description="IP to remove.", metavar="IP"), ], ) @@ -1084,9 +1107,9 @@ def a_remove(args): # Implementation of sub command 'a_show' # ############################################ + def a_show(args): - """Show hosts ipaddresses. If is an alias the cname host is used. - """ + """Show hosts ipaddresses. If is an alias the cname host is used.""" info = host_info_by_name(args.name) print_ipaddresses(info["ipaddresses"]) cli_info("showed ip addresses for {}".format(info["name"])) @@ -1094,15 +1117,13 @@ def a_show(args): # Add 'a_show' as a sub command to the 'host' command host.add_command( - prog='a_show', - description='Show hosts ipaddresses. If NAME is an alias the cname host ' - 'is used.', - short_desc='Show ipaddresses.', + prog="a_show", + description="Show hosts ipaddresses. If NAME is an alias the cname host " + "is used.", + short_desc="Show ipaddresses.", callback=a_show, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) @@ -1118,32 +1139,24 @@ def a_show(args): # Implementation of sub command 'aaaa_add' # ############################################## + def aaaa_add(args): - """Add an AAAA record to host. If is an alias the cname host is used. - """ + """Add an AAAA record to host. If is an alias the cname host is used.""" _ip_add(args, 6, macaddress=args.macaddress) # Add 'aaaa_add' as a sub command to the 'host' command host.add_command( - prog='aaaa_add', - description=' Add an AAAA record to host. If NAME is an alias the cname ' - 'host is used.', - short_desc='Add AAAA record.', + prog="aaaa_add", + description=" Add an AAAA record to host. If NAME is an alias the cname " + "host is used.", + short_desc="Add AAAA record.", callback=aaaa_add, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('ip', - description='The IPv6 to add to target host.', - metavar='IPv6'), - Flag('-macaddress', - description='Mac address', - metavar='MACADDRESS'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("ip", description="The IPv6 to add to target host.", metavar="IPv6"), + Flag("-macaddress", description="Mac address", metavar="MACADDRESS"), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -1152,38 +1165,40 @@ def aaaa_add(args): # Implementation of sub command 'aaaa_change' # ################################################# -def aaaa_change(args): - """Change AAAA record. If is an alias the cname host is used. - """ +def aaaa_change(args): + """Change AAAA record. If is an alias the cname host is used.""" _ip_change(args, 6) # Add 'aaaa_change' as a sub command to the 'host' command host.add_command( - prog='aaaa_change', - description='Change AAAA record. If NAME is an alias the cname host is ' - 'used.', - short_desc='Change AAAA record.', + prog="aaaa_change", + description="Change AAAA record. If NAME is an alias the cname host is used.", + short_desc="Change AAAA record.", callback=aaaa_change, flags=[ - Flag('name', - description='Name of the target host.', - short_desc='Host name.', - metavar='NAME'), - Flag('-old', - description='The existing IPv6 that should be changed.', - short_desc='IPv6 to change.', - required=True, - metavar='IPv6'), - Flag('-new', - description='The new IPv6 address.', - short_desc='New IPv6.', - required=True, - metavar='IPv6'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag( + "name", + description="Name of the target host.", + short_desc="Host name.", + metavar="NAME", + ), + Flag( + "-old", + description="The existing IPv6 that should be changed.", + short_desc="IPv6 to change.", + required=True, + metavar="IPv6", + ), + Flag( + "-new", + description="The new IPv6 address.", + short_desc="New IPv6.", + required=True, + metavar="IPv6", + ), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -1193,31 +1208,30 @@ def aaaa_change(args): def aaaa_move(args): - """Move an IP from a host to another host. Will move also move the PTR, if any. - """ - + """Move an IP from a host to another host. Will move also move the PTR, if any.""" _ip_move(args, 6) # Add 'aaaa_move' as a sub command to the 'host' command host.add_command( - prog='aaaa_move', - description='Move AAAA record from a host to another host', - short_desc='Move AAAA record', + prog="aaaa_move", + description="Move AAAA record from a host to another host", + short_desc="Move AAAA record", callback=aaaa_move, flags=[ - Flag('-ip', - description='IP to move', - required=True, - metavar='IP'), - Flag('-fromhost', - description='Name of source host', - required=True, - metavar='NAME'), - Flag('-tohost', - description='Name of destination host', - required=True, - metavar='NAME'), + Flag("-ip", description="IP to move", required=True, metavar="IP"), + Flag( + "-fromhost", + description="Name of source host", + required=True, + metavar="NAME", + ), + Flag( + "-tohost", + description="Name of destination host", + required=True, + metavar="NAME", + ), ], ) @@ -1226,6 +1240,7 @@ def aaaa_move(args): # Implementation of sub command 'aaaa_remove' # ################################################# + def aaaa_remove(args): """Remove AAAA record from host. If is an alias the cname host is used. @@ -1235,18 +1250,14 @@ def aaaa_remove(args): # Add 'aaaa_remove' as a sub command to the 'host' command host.add_command( - prog='aaaa_remove', - description='Remove AAAA record from host. If NAME is an alias the cname ' - 'host is used.', - short_desc='Remove AAAA record.', + prog="aaaa_remove", + description="Remove AAAA record from host. If NAME is an alias the cname " + "host is used.", + short_desc="Remove AAAA record.", callback=aaaa_remove, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('ip', - description='IPv6 to remove.', - metavar='IPv6'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("ip", description="IPv6 to remove.", metavar="IPv6"), ], ) @@ -1255,9 +1266,9 @@ def aaaa_remove(args): # Implementation of sub command 'aaaa_show' # ############################################### + def aaaa_show(args): - """Show hosts ipaddresses. If is an alias the cname host is used. - """ + """Show hosts ipaddresses. If is an alias the cname host is used.""" info = host_info_by_name(args.name) print_ipaddresses(info["ipaddresses"]) cli_info("showed aaaa records for {}".format(info["name"])) @@ -1265,15 +1276,13 @@ def aaaa_show(args): # Add 'aaaa_show' as a sub command to the 'host' command host.add_command( - prog='aaaa_show', - description='Show hosts AAAA records. If NAME is an alias the cname host ' - 'is used.', - short_desc='Show AAAA records.', + prog="aaaa_show", + description="Show hosts AAAA records. If NAME is an alias the cname host " + "is used.", + short_desc="Show AAAA records.", callback=aaaa_show, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) @@ -1289,9 +1298,9 @@ def aaaa_show(args): # Implementation of sub command 'cname_add' # ############################################### + def cname_add(args): - """Add a CNAME record to host. - """ + """Add a CNAME record to host.""" # Get host info or raise exception info = host_info_by_name(args.name) alias = clean_hostname(args.alias) @@ -1310,33 +1319,25 @@ def cname_add(args): # Check if the cname is in a zone controlled by mreg check_zone_for_hostname(alias, args.force) - data = {'host': info['id'], - 'name': alias} + data = {"host": info["id"], "name": alias} # Create CNAME record path = "/api/v1/cnames/" history.record_post(path, "", data, undoable=False) post(path, **data) - cli_info("Added cname alias {} for {}".format(alias, info["name"]), - print_msg=True) + cli_info("Added cname alias {} for {}".format(alias, info["name"]), print_msg=True) # Add 'cname_add' as a sub command to the 'host' command host.add_command( - prog='cname_add', - description='Add a CNAME record to host. If NAME is an alias ' - 'the cname host is used as target for ALIAS.', - short_desc='Add CNAME.', + prog="cname_add", + description="Add a CNAME record to host. If NAME is an alias " + "the cname host is used as target for ALIAS.", + short_desc="Add CNAME.", callback=cname_add, flags=[ - Flag('name', - description='Name of target host.', - metavar='NAME'), - Flag('alias', - description='Name of CNAME host.', - metavar='ALIAS'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag("name", description="Name of target host.", metavar="NAME"), + Flag("alias", description="Name of CNAME host.", metavar="ALIAS"), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -1345,42 +1346,37 @@ def cname_add(args): # Implementation of sub command 'cname_remove' # ################################################## + def cname_remove(args): - """Remove CNAME record. - """ + """Remove CNAME record.""" info = host_info_by_name(args.name) - hostname = info['name'] + hostname = info["name"] alias = clean_hostname(args.alias) - if not info['cnames']: - cli_warning("\"{}\" doesn't have any CNAME records.".format(hostname)) + if not info["cnames"]: + cli_warning('"{}" doesn\'t have any CNAME records.'.format(hostname)) - for cname in info['cnames']: - if cname['name'] == alias: + for cname in info["cnames"]: + if cname["name"] == alias: break else: - cli_warning("\"{}\" is not an alias for \"{}\"".format(alias, hostname)) + cli_warning('"{}" is not an alias for "{}"'.format(alias, hostname)) # Delete CNAME host path = f"/api/v1/cnames/{alias}" history.record_delete(path, dict(), undoable=False) delete(path) - cli_info("Removed cname alias {} for {}".format(alias, hostname), - print_msg=True) + cli_info("Removed cname alias {} for {}".format(alias, hostname), print_msg=True) # Add 'cname_remove' as a sub command to the 'host' command host.add_command( - prog='cname_remove', - description='Remove CNAME record.', + prog="cname_remove", + description="Remove CNAME record.", callback=cname_remove, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('alias', - description='Name of CNAME to remove.', - metavar='CNAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("alias", description="Name of CNAME to remove.", metavar="CNAME"), ], ) @@ -1388,39 +1384,36 @@ def cname_remove(args): # Implementation of sub command 'cname_replace' # ################################################### -def cname_replace(args): - """Move a CNAME entry from one host to another. - """ +def cname_replace(args): + """Move a CNAME entry from one host to another.""" cname = clean_hostname(args.cname) host = clean_hostname(args.host) cname_info = host_info_by_name(cname) host_info = host_info_by_name(host) - if cname_info['id'] == host_info['id']: + if cname_info["id"] == host_info["id"]: cli_error(f"The CNAME {cname} already points to {host}.") # Update CNAME record. - data = {'host': host_info['id'], 'name': cname } + data = {"host": host_info["id"], "name": cname} path = f"/api/v1/cnames/{cname}" history.record_patch(path, "", data, undoable=False) patch(path, **data) - cli_info(f"Moved CNAME alias {cname}: {cname_info['name']} -> {host}", - print_msg=True) + cli_info( + f"Moved CNAME alias {cname}: {cname_info['name']} -> {host}", print_msg=True + ) + host.add_command( - prog='cname_replace', - description='Move a CNAME record from one host to another.', - short_desc='Replace a CNAME record.', + prog="cname_replace", + description="Move a CNAME record from one host to another.", + short_desc="Replace a CNAME record.", callback=cname_replace, flags=[ - Flag('cname', - description='The CNAME to modify.', - metavar='CNAME'), - Flag('host', - description='The new host for the CNAME.', - metavar='HOST'), + Flag("cname", description="The CNAME to modify.", metavar="CNAME"), + Flag("host", description="The new host for the CNAME.", metavar="HOST"), ], ) @@ -1429,6 +1422,7 @@ def cname_replace(args): # Implementation of sub command 'cname_show' # ################################################ + def cname_show(args): """Show CNAME records for host. If is an alias the cname hosts aliases are shown. @@ -1445,15 +1439,13 @@ def cname_show(args): # Add 'cname_show' as a sub command to the 'host' command host.add_command( - prog='cname_show', - description='Show CNAME records for host. If NAME is an alias the cname ' - 'hosts aliases are shown.', - short_desc='Show CNAME records.', + prog="cname_show", + description="Show CNAME records for host. If NAME is an alias the cname " + "hosts aliases are shown.", + short_desc="Show CNAME records.", callback=cname_show, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) @@ -1464,10 +1456,9 @@ def cname_show(args): # # ################################################################################ + def _hinfo_remove(host_) -> None: - """ - Helper method to remove hinfo from a host. - """ + """Helper method to remove hinfo from a host.""" old_data = {"hinfo": host_["hinfo"]} new_data = {"hinfo": ""} @@ -1477,25 +1468,20 @@ def _hinfo_remove(host_) -> None: patch(path, hinfo="") - ############################################### # Implementation of sub command 'hinfo_add' # ############################################### + def hinfo_add(args): - """Add hinfo for host. If is an alias the cname host is updated. - """ + """Add hinfo for host. If is an alias the cname host is updated.""" # Get host info or raise exception info = host_info_by_name(args.name) - if info['hinfo']: + if info["hinfo"]: cli_warning(f"{info['name']} already has hinfo set.") - data = { - "host": info["id"], - "cpu": args.cpu, - "os": args.os - } + data = {"host": info["id"], "cpu": args.cpu, "os": args.os} # Add HINFO record to host path = "/api/v1/hinfos/" history.record_post(path, "", data, undoable=False) @@ -1504,21 +1490,14 @@ def hinfo_add(args): host.add_command( - prog='hinfo_add', - description='Add HINFO for host. If NAME is an alias the cname host is ' - 'updated.', - short_desc='Set HINFO.', + prog="hinfo_add", + description="Add HINFO for host. If NAME is an alias the cname host is updated.", + short_desc="Set HINFO.", callback=hinfo_add, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('cpu', - description='CPU/hardware', - metavar='CPU'), - Flag('os', - description='Operating system', - metavar='OS'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("cpu", description="CPU/hardware", metavar="CPU"), + Flag("os", description="Operating system", metavar="OS"), ], ) @@ -1527,43 +1506,41 @@ def hinfo_add(args): # Implementation of sub command 'hinfo_remove' # ################################################## + def hinfo_remove(args): - """ - hinfo_remove - Remove hinfo for host. If is an alias the cname host is updated. + """hinfo_remove + Remove hinfo for host. If is an alias the cname host is updated. """ # Get host info or raise exception info = host_info_by_name(args.name) - if not info['hinfo']: + if not info["hinfo"]: cli_warning(f"{info['name']} already has no hinfo set.") - host_id = info['id'] + host_id = info["id"] path = f"/api/v1/hinfos/{host_id}" history.record_delete(path, host_id) delete(path) - cli_info("deleted HINFO from {}".format(info['name']), True) + cli_info("deleted HINFO from {}".format(info["name"]), True) # Add 'hinfo_remove' as a sub command to the 'host' command host.add_command( - prog='hinfo_remove', - description='Remove hinfo for host. If NAME is an alias the cname host is ' - 'updated.', - short_desc='Remove HINFO.', + prog="hinfo_remove", + description="Remove hinfo for host. If NAME is an alias the cname host is " + "updated.", + short_desc="Remove HINFO.", callback=hinfo_remove, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) - ################################################ # Implementation of sub command 'hinfo_show' # ################################################ + def hinfo_show(args): """Show hinfo for host. If is an alias the cname hosts hinfo is shown. @@ -1578,38 +1555,35 @@ def hinfo_show(args): # Add 'hinfo_show' as a sub command to the 'host' command host.add_command( - prog='hinfo_show', - description='Show hinfo for host. If NAME is an alias the cname hosts ' - 'hinfo is shown.', - short_desc='Show HINFO.', + prog="hinfo_show", + description="Show hinfo for host. If NAME is an alias the cname hosts " + "hinfo is shown.", + short_desc="Show HINFO.", callback=hinfo_show, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) + def _history(args): - """Show host history for name""" + """Show host history for name.""" hostname = clean_hostname(args.name) - items = get_history_items(hostname, 'host', data_relation='hosts') + items = get_history_items(hostname, "host", data_relation="hosts") print_history_items(hostname, items) + host.add_command( - prog='history', - description='Show history for host name', - short_desc='Show history for host name', + prog="history", + description="Show history for host name", + short_desc="Show history for host name", callback=_history, flags=[ - Flag('name', - description='Host name', - metavar='NAME'), + Flag("name", description="Host name", metavar="NAME"), ], ) - ################################################################################ # # # LOC records # @@ -1621,16 +1595,16 @@ def _history(args): # Implementation of sub command 'loc_remove' # ################################################ + def loc_remove(args): """Remove location from host. If is an alias the cname host is updated. """ - # Get host info or raise exception info = host_info_by_name(args.name) - if not info['loc']: + if not info["loc"]: cli_warning(f"{info['name']} already has no loc set.") - host_id = info['id'] + host_id = info["id"] path = f"/api/v1/locs/{host_id}" history.record_delete(path, host_id) delete(path) @@ -1640,15 +1614,13 @@ def loc_remove(args): # Add 'loc_remove' as a sub command to the 'host' command host.add_command( - prog='loc_remove', - description='Remove location from host. If NAME is an alias the cname host ' - 'is updated.', - short_desc='Remove LOC record.', + prog="loc_remove", + description="Remove location from host. If NAME is an alias the cname host " + "is updated.", + short_desc="Remove LOC record.", callback=loc_remove, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) @@ -1657,40 +1629,31 @@ def loc_remove(args): # Implementation of sub command 'loc_add' # ############################################# -def loc_add(args): - """Set location of host. If is an alias the cname host is updated. - """ +def loc_add(args): + """Set location of host. If is an alias the cname host is updated.""" # Get host info or raise exception info = host_info_by_name(args.name) - if info['loc']: + if info["loc"]: cli_warning(f"{info['name']} already has loc set.") - data = { - "host": info["id"], - "loc": args.loc - } + data = {"host": info["id"], "loc": args.loc} path = "/api/v1/locs/" history.record_post(path, "", data, undoable=False) post(path, **data) - cli_info("added LOC '{}' for {}".format(args.loc, info["name"]), - print_msg=True) + cli_info("added LOC '{}' for {}".format(args.loc, info["name"]), print_msg=True) host.add_command( - prog='loc_add', - description='Set location of host. If NAME is an alias the cname host is ' - 'updated.', - short_desc='Set LOC record.', + prog="loc_add", + description="Set location of host. If NAME is an alias the cname host is " + "updated.", + short_desc="Set LOC record.", callback=loc_add, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), - Flag('loc', - description='New LOC.', - metavar='LOC'), + Flag("name", description="Name of the target host.", metavar="NAME"), + Flag("loc", description="New LOC.", metavar="LOC"), ], ) @@ -1699,6 +1662,7 @@ def loc_add(args): # Implementation of sub command 'loc_show' # ############################################## + def loc_show(args): """Show location of host. If is an alias the cname hosts LOC is shown. @@ -1713,15 +1677,13 @@ def loc_show(args): # Add 'loc_show' as a sub command to the 'host' command host.add_command( - prog='loc_show', - description='Show location of host. If NAME is an alias the cname hosts ' - 'LOC is shown.', - short_desc='Show LOC record.', + prog="loc_show", + description="Show location of host. If NAME is an alias the cname hosts " + "LOC is shown.", + short_desc="Show LOC record.", callback=loc_show, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) @@ -1732,10 +1694,11 @@ def loc_show(args): # # ############################################################################### + def _mx_in_mxs(mxs, priority, mx): for info in mxs: - if info['priority'] == priority and info['mx'] == mx: - return info['id'] + if info["priority"] == priority and info["mx"] == mx: + return info["id"] return None @@ -1743,20 +1706,17 @@ def _mx_in_mxs(mxs, priority, mx): # Implementation of sub command 'mx_add' # ############################################# + def mx_add(args): """Add a mx record to host. must be enclosed in double quotes if it contains more than one word. """ # Get host info or raise exception info = host_info_by_name(args.name) - if _mx_in_mxs(info['mxs'], args.priority, args.mx): - cli_warning("{} already has that MX defined".format(info['name'])) + if _mx_in_mxs(info["mxs"], args.priority, args.mx): + cli_warning("{} already has that MX defined".format(info["name"])) - data = { - "host": info["id"], - "priority": args.priority, - "mx": args.mx - } + data = {"host": info["id"], "priority": args.priority, "mx": args.mx} # Add MX record to host path = "/api/v1/mxs/" history.record_post(path, "", data, undoable=False) @@ -1766,21 +1726,14 @@ def mx_add(args): # Add 'mx_add' as a sub command to the 'host' command host.add_command( - prog='mx_add', - description='Add a MX record to host.', - short_desc='Add MX record.', + prog="mx_add", + description="Add a MX record to host.", + short_desc="Add MX record.", callback=mx_add, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('priority', - description='Priority', - type=int, - metavar='PRIORITY'), - Flag('mx', - description='Mail Server', - metavar='MX'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag("priority", description="Priority", flag_type=int, metavar="PRIORITY"), + Flag("mx", description="Mail Server", metavar="MX"), ], ) @@ -1789,39 +1742,35 @@ def mx_add(args): # Implementation of sub command 'mx_remove' # ################################################ + def mx_remove(args): - """Remove MX record for host. - """ + """Remove MX record for host.""" # Get host info or raise exception info = host_info_by_name(args.name) - mx_id = _mx_in_mxs(info['mxs'], args.priority, args.mx) + mx_id = _mx_in_mxs(info["mxs"], args.priority, args.mx) if mx_id is None: - cli_warning("{} has no MX records with priority {} and mail exhange {}".format( - info['name'], args.priority, args.mx)) + cli_warning( + "{} has no MX records with priority {} and mail exhange {}".format( + info["name"], args.priority, args.mx + ) + ) path = f"/api/v1/mxs/{mx_id}" history.record_delete(path, mx_id) delete(path) - cli_info("deleted MX from {}".format(info['name']), True) + cli_info("deleted MX from {}".format(info["name"]), True) # Add 'mx_remove' as a sub command to the 'host' command host.add_command( - prog='mx_remove', - description=' Remove MX record for host.', - short_desc='Remove MX record.', + prog="mx_remove", + description=" Remove MX record for host.", + short_desc="Remove MX record.", callback=mx_remove, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('priority', - description='Priority', - type=int, - metavar='PRIORITY'), - Flag('mx', - description='Mail Server', - metavar='TEXT'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag("priority", description="Priority", flag_type=int, metavar="PRIORITY"), + Flag("mx", description="Mail Server", metavar="TEXT"), ], ) @@ -1830,30 +1779,28 @@ def mx_remove(args): # Implementation of sub command 'mx_show' # ############################################## + def mx_show(args): - """Show all MX records for host. - """ + """Show all MX records for host.""" info = host_info_by_name(args.name) path = "/api/v1/mxs/" params = { - "host": info['id'], + "host": info["id"], } history.record_get(path) mxs = get_list(path, params=params) print_mx(mxs, padding=5) - cli_info("showed MX records for {}".format(info['name'])) + cli_info("showed MX records for {}".format(info["name"])) # Add 'mx_show' as a sub command to the 'host' command host.add_command( - prog='mx_show', - description='Show all MX records for host.', - short_desc='Show MX records.', + prog="mx_show", + description="Show all MX records for host.", + short_desc="Show MX records.", callback=mx_show, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), + Flag("name", description="Host target name.", metavar="NAME"), ], ) @@ -1869,9 +1816,9 @@ def mx_show(args): # Implementation of sub command 'naptr_add' # ############################################### + def naptr_add(args): - """Add a NAPTR record to host. - """ + """Add a NAPTR record to host.""" # Get host info or raise exception info = host_info_by_name(args.name) @@ -1894,41 +1841,42 @@ def naptr_add(args): # Add 'naptr_add' as a sub command to the 'host' command host.add_command( - prog='naptr_add', - description='Add a NAPTR record to host.', - short_desc='Add NAPTR record.', + prog="naptr_add", + description="Add a NAPTR record to host.", + short_desc="Add NAPTR record.", callback=naptr_add, flags=[ - Flag('-name', - description='Name of the target host.', - required=True, - metavar='NAME'), - Flag('-preference', - description='NAPTR preference.', - type=int, - required=True, - metavar='PREFERENCE'), - Flag('-order', - description='NAPTR order.', - type=int, - required=True, - metavar='ORDER'), - Flag('-flag', - description='NAPTR flag.', - required=True, - metavar='FLAG'), - Flag('-service', - description='NAPTR service.', - required=True, - metavar='SERVICE'), - Flag('-regex', - description='NAPTR regexp.', - required=True, - metavar='REGEXP'), - Flag('-replacement', - description='NAPTR replacement.', - required=True, - metavar='REPLACEMENT'), + Flag( + "-name", + description="Name of the target host.", + required=True, + metavar="NAME", + ), + Flag( + "-preference", + description="NAPTR preference.", + flag_type=int, + required=True, + metavar="PREFERENCE", + ), + Flag( + "-order", + description="NAPTR order.", + flag_type=int, + required=True, + metavar="ORDER", + ), + Flag("-flag", description="NAPTR flag.", required=True, metavar="FLAG"), + Flag( + "-service", description="NAPTR service.", required=True, metavar="SERVICE" + ), + Flag("-regex", description="NAPTR regexp.", required=True, metavar="REGEXP"), + Flag( + "-replacement", + description="NAPTR replacement.", + required=True, + metavar="REPLACEMENT", + ), ], ) @@ -1937,10 +1885,10 @@ def naptr_add(args): # Implementation of sub command 'naptr_remove' # ################################################## + def naptr_remove(args): - """ - naptr_remove - Remove NAPTR record. + """naptr_remove + Remove NAPTR record. """ # Get host info or raise exception info = host_info_by_name(args.name) @@ -1950,13 +1898,20 @@ def naptr_remove(args): path = "/api/v1/naptrs/" params = { "replacement__contains": args.replacement, - "host": info['id'], + "host": info["id"], } history.record_get(path) naptrs = get_list(path, params=params) data = None - attrs = ('preference', 'order', 'flag', 'service', 'regex', 'replacement',) + attrs = ( + "preference", + "order", + "flag", + "service", + "regex", + "replacement", + ) for naptr in naptrs: if all(naptr[attr] == getattr(args, attr) for attr in attrs): data = naptr @@ -1973,40 +1928,41 @@ def naptr_remove(args): # Add 'naptr_remove' as a sub command to the 'host' command host.add_command( - prog='naptr_remove', - description='Remove NAPTR record.', + prog="naptr_remove", + description="Remove NAPTR record.", callback=naptr_remove, flags=[ - Flag('-name', - description='Name of the target host.', - required=True, - metavar='NAME'), - Flag('-preference', - description='NAPTR preference.', - type=int, - required=True, - metavar='PREFERENCE'), - Flag('-order', - description='NAPTR order.', - type=int, - required=True, - metavar='ORDER'), - Flag('-flag', - description='NAPTR flag.', - required=True, - metavar='FLAG'), - Flag('-service', - description='NAPTR service.', - required=True, - metavar='SERVICE'), - Flag('-regex', - description='NAPTR regexp.', - required=True, - metavar='REGEXP'), - Flag('-replacement', - description='NAPTR replacement.', - required=True, - metavar='REPLACEMENT'), + Flag( + "-name", + description="Name of the target host.", + required=True, + metavar="NAME", + ), + Flag( + "-preference", + description="NAPTR preference.", + flag_type=int, + required=True, + metavar="PREFERENCE", + ), + Flag( + "-order", + description="NAPTR order.", + flag_type=int, + required=True, + metavar="ORDER", + ), + Flag("-flag", description="NAPTR flag.", required=True, metavar="FLAG"), + Flag( + "-service", description="NAPTR service.", required=True, metavar="SERVICE" + ), + Flag("-regex", description="NAPTR regexp.", required=True, metavar="REGEXP"), + Flag( + "-replacement", + description="NAPTR replacement.", + required=True, + metavar="REPLACEMENT", + ), ], ) @@ -2015,33 +1971,43 @@ def naptr_remove(args): # Implementation of sub command 'naptr_show' # ################################################ + def _naptr_show(info): path = "/api/v1/naptrs/" params = { - "host": info['id'], + "host": info["id"], } history.record_get(path) naptrs = get_list(path, params=params) - headers = ("NAPTRs:", "Preference", "Order", "Flag", "Service", "Regex", "Replacement") - row_format = '{:<14}' * len(headers) + headers = ( + "NAPTRs:", + "Preference", + "Order", + "Flag", + "Service", + "Regex", + "Replacement", + ) + row_format = "{:<14}" * len(headers) if naptrs: print(row_format.format(*headers)) for naptr in naptrs: - print(row_format.format( - '', - naptr["preference"], - naptr["order"], - naptr["flag"], - naptr["service"], - naptr["regex"] or '""', - naptr["replacement"], - )) + print( + row_format.format( + "", + naptr["preference"], + naptr["order"], + naptr["flag"], + naptr["service"], + naptr["regex"] or '""', + naptr["replacement"], + ) + ) return len(naptrs) def naptr_show(args): - """Show all NAPTR records for host. - """ + """Show all NAPTR records for host.""" info = host_info_by_name(args.name) num_naptrs = _naptr_show(info) if num_naptrs == 0: @@ -2051,14 +2017,12 @@ def naptr_show(args): # Add 'naptr_show' as a sub command to the 'host' command host.add_command( - prog='naptr_show', - description='Show all NAPTR records for host.', - short_desc='Show NAPTR records.', + prog="naptr_show", + description="Show all NAPTR records for host.", + short_desc="Show NAPTR records.", callback=naptr_show, flags=[ - Flag('name', - description='Name of the target host.', - metavar='NAME'), + Flag("name", description="Name of the target host.", metavar="NAME"), ], ) @@ -2074,9 +2038,9 @@ def naptr_show(args): # Implementation of sub command 'ptr_change' # ################################################ + def ptr_change(args): - """Move PTR record from to . - """ + """Move PTR record from to .""" # Get host info or raise exception old_info = host_info_by_name(args.old) new_info = host_info_by_name(args.new) @@ -2087,11 +2051,9 @@ def ptr_change(args): # check that old host has a PTR record with the given ip if not len(old_info["ptr_overrides"]): - cli_warning("no PTR record for {} with ip {}".format(old_info["name"], - args.ip)) + cli_warning("no PTR record for {} with ip {}".format(old_info["name"], args.ip)) if old_info["ptr_overrides"][0]["ipaddress"] != args.ip: - cli_warning("{} PTR record doesn't match {}".format(old_info["name"], - args.ip)) + cli_warning("{} PTR record doesn't match {}".format(old_info["name"], args.ip)) # change PTR record data = { @@ -2101,36 +2063,33 @@ def ptr_change(args): path = "/api/v1/ptroverrides/{}".format(old_info["ptr_overrides"][0]["id"]) history.record_patch(path, data, old_info["ptr_overrides"][0]) patch(path, **data) - cli_info("changed owner of PTR record {} from {} to {}".format( - args.ip, - old_info["name"], - new_info["name"], - ), print_msg=True) + cli_info( + "changed owner of PTR record {} from {} to {}".format( + args.ip, + old_info["name"], + new_info["name"], + ), + print_msg=True, + ) # Add 'ptr_change' as a sub command to the 'host' command host.add_command( - prog='ptr_change', - description='Move PTR record from OLD to NEW.', - short_desc='Move PTR record.', + prog="ptr_change", + description="Move PTR record from OLD to NEW.", + short_desc="Move PTR record.", callback=ptr_change, flags=[ - Flag('-ip', - description='IP of PTR record. May be IPv4 or IPv6.', - short_desc='IP of PTR record.', - required=True, - metavar='IP'), - Flag('-old', - description='Name of old host.', - required=True, - metavar='NAME'), - Flag('-new', - description='Name of new host.', - required=True, - metavar='NAME'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag( + "-ip", + description="IP of PTR record. May be IPv4 or IPv6.", + short_desc="IP of PTR record.", + required=True, + metavar="IP", + ), + Flag("-old", description="Name of old host.", required=True, metavar="NAME"), + Flag("-new", description="Name of new host.", required=True, metavar="NAME"), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -2139,41 +2098,37 @@ def ptr_change(args): # Implementation of sub command 'ptr_remove' # ################################################ + def ptr_remove(args): - """Remove PTR record from host. - """ + """Remove PTR record from host.""" # Get host info or raise exception info = host_info_by_name(args.name) - for ptr in info['ptr_overrides']: - if ptr['ipaddress'] == args.ip: - ptr_id = ptr['id'] + for ptr in info["ptr_overrides"]: + if ptr["ipaddress"] == args.ip: + ptr_id = ptr["id"] break else: - cli_warning("no PTR record for {} with ip {}".format(info["name"], - args.ip)) + cli_warning("no PTR record for {} with ip {}".format(info["name"], args.ip)) # Delete record path = f"/api/v1/ptroverrides/{ptr_id}" history.record_delete(path, ptr_id) delete(path) - cli_info("deleted PTR record {} for {}".format(args.ip, info["name"]), - print_msg=True) + cli_info( + "deleted PTR record {} for {}".format(args.ip, info["name"]), print_msg=True + ) # Add 'ptr_remove' as a sub command to the 'host' command host.add_command( - prog='ptr_remove', - description='Remove PTR record from host.', - short_desc='Remove PTR record.', + prog="ptr_remove", + description="Remove PTR record from host.", + short_desc="Remove PTR record.", callback=ptr_remove, flags=[ - Flag('ip', - description='IP of PTR record. May be IPv4 or IPv6.', - metavar='IP'), - Flag('name', - description='Name of host.', - metavar='NAME'), + Flag("ip", description="IP of PTR record. May be IPv4 or IPv6.", metavar="IP"), + Flag("name", description="Name of host.", metavar="NAME"), ], ) @@ -2182,9 +2137,9 @@ def ptr_remove(args): # Implementation of sub command 'ptr_add' # ############################################# + def ptr_add(args): - """Create a PTR record for host. - """ + """Create a PTR record for host.""" # Ip sanity check if not is_valid_ip(args.ip): cli_warning("invalid ip: {}".format(args.ip)) @@ -2204,12 +2159,13 @@ def ptr_add(args): if len(ptrs): cli_warning("{} already exist in a PTR record".format(args.ip)) # check if host is in mreg controlled zone, must force if not - if info['zone'] is None and not args.force: - cli_warning("{} isn't in a zone controlled by MREG, must force" - .format(info["name"])) + if info["zone"] is None and not args.force: + cli_warning( + "{} isn't in a zone controlled by MREG, must force".format(info["name"]) + ) network = get_network_by_ip(args.ip) - reserved_addresses = get_network_reserved_ips(network['network']) + reserved_addresses = get_network_reserved_ips(network["network"]) if args.ip in reserved_addresses and not args.force: cli_warning("Address is reserved. Requires force") @@ -2221,26 +2177,19 @@ def ptr_add(args): path = "/api/v1/ptroverrides/" history.record_post(path, "", data, undoable=False) post(path, **data) - cli_info("Added PTR record {} to {}".format(args.ip, info["name"]), - print_msg=True) + cli_info("Added PTR record {} to {}".format(args.ip, info["name"]), print_msg=True) # Add 'ptr_add' as a sub command to the 'host' command host.add_command( - prog='ptr_add', - description='Create a PTR record for host.', - short_desc='Add PTR record.', + prog="ptr_add", + description="Create a PTR record for host.", + short_desc="Add PTR record.", callback=ptr_add, flags=[ - Flag('ip', - description='IP of PTR record. May be IPv4 or IPv6.', - metavar='IP'), - Flag('name', - description='Name of host.', - metavar='NAME'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag("ip", description="IP of PTR record. May be IPv4 or IPv6.", metavar="IP"), + Flag("name", description="Name of host.", metavar="NAME"), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -2249,10 +2198,9 @@ def ptr_add(args): # Implementation of sub command 'ptr_show' # ############################################## -def ptr_show(args): - """Show PTR record matching given ip. - """ +def ptr_show(args): + """Show PTR record matching given ip.""" if not is_valid_ip(args.ip): cli_warning(f"{args.ip} is not a valid IP") @@ -2275,15 +2223,13 @@ def ptr_show(args): # Add 'ptr_show' as a sub command to the 'host' command host.add_command( - prog='ptr_show', - description='Show PTR record matching given ip (empty input shows all ' - 'PTR records).', - short_desc='Show PTR record.', + prog="ptr_show", + description="Show PTR record matching given ip (empty input shows all " + "PTR records).", + short_desc="Show PTR record.", callback=ptr_show, flags=[ - Flag('ip', - description='IP of PTR record. May be IPv4 or IPv6.', - metavar='IP'), + Flag("ip", description="IP of PTR record. May be IPv4 or IPv6.", metavar="IP"), ], ) @@ -2299,10 +2245,9 @@ def ptr_show(args): # Implementation of sub command 'srv_add' # ############################################# -def srv_add(args): - """Add SRV record. - """ +def srv_add(args): + """Add SRV record.""" sname = clean_hostname(args.name) check_zone_for_hostname(sname, False, require_zone=True) @@ -2310,14 +2255,14 @@ def srv_add(args): info = host_info_by_name(args.host) # Require force if target host not in MREG zone - check_zone_for_hostname(info['name'], args.force) + check_zone_for_hostname(info["name"], args.force) data = { "name": sname, "priority": args.priority, "weight": args.weight, "port": args.port, - "host": info['id'], + "host": info["id"], "ttl": args.ttl, } @@ -2325,42 +2270,26 @@ def srv_add(args): path = "/api/v1/srvs/" history.record_post(path, "", data, undoable=False) post(path, **data) - cli_info("Added SRV record {} with target {}".format(sname, info['name']), - print_msg=True) + cli_info( + "Added SRV record {} with target {}".format(sname, info["name"]), print_msg=True + ) # Add 'srv_add' as a sub command to the 'host' command host.add_command( - prog='srv_add', - description='Add SRV record.', + prog="srv_add", + description="Add SRV record.", callback=srv_add, flags=[ - Flag('-name', - description='SRV service.', - required=True, - metavar='SERVICE'), - Flag('-priority', - description='SRV priority.', - required=True, - metavar='PRIORITY'), - Flag('-weight', - description='SRV weight.', - required=True, - metavar='WEIGHT'), - Flag('-port', - description='SRV port.', - required=True, - metavar='PORT'), - Flag('-host', - description='Host target name.', - required=True, - metavar='NAME'), - Flag('-ttl', - description='TTL value', - metavar='TTL'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag("-name", description="SRV service.", required=True, metavar="SERVICE"), + Flag( + "-priority", description="SRV priority.", required=True, metavar="PRIORITY" + ), + Flag("-weight", description="SRV weight.", required=True, metavar="WEIGHT"), + Flag("-port", description="SRV port.", required=True, metavar="PORT"), + Flag("-host", description="Host target name.", required=True, metavar="NAME"), + Flag("-ttl", description="TTL value", metavar="TTL"), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -2369,9 +2298,9 @@ def srv_add(args): # Implementation of sub command 'srv_remove' # ################################################ + def srv_remove(args): - """Remove SRV record. - """ + """Remove SRV record.""" info = host_info_by_name(args.host) sname = clean_hostname(args.name) @@ -2379,7 +2308,7 @@ def srv_remove(args): path = "/api/v1/srvs/" params = { "name": sname, - "host": info['id'], + "host": info["id"], } history.record_get(path) srvs = get_list(path, params=params) @@ -2387,7 +2316,12 @@ def srv_remove(args): cli_warning(f"no service named {sname}") data = None - attrs = ('name', 'priority', 'weight', 'port',) + attrs = ( + "name", + "priority", + "weight", + "port", + ) for srv in srvs: if all(srv[attr] == getattr(args, attr) for attr in attrs): data = srv @@ -2405,33 +2339,33 @@ def srv_remove(args): # Add 'srv_remove' as a sub command to the 'host' command host.add_command( - prog='srv_remove', - description='Remove SRV record.', + prog="srv_remove", + description="Remove SRV record.", callback=srv_remove, flags=[ - Flag('-name', - description='SRV service.', - required=True, - metavar='SERVICE'), - Flag('-priority', - description='SRV priority.', - type=int, - required=True, - metavar='PRIORITY'), - Flag('-weight', - description='SRV weight.', - type=int, - required=True, - metavar='WEIGHT'), - Flag('-port', - description='SRV port.', - type=int, - required=True, - metavar='PORT'), - Flag('-host', - description='Host target name.', - required=True, - metavar='NAME'), + Flag("-name", description="SRV service.", required=True, metavar="SERVICE"), + Flag( + "-priority", + description="SRV priority.", + flag_type=int, + required=True, + metavar="PRIORITY", + ), + Flag( + "-weight", + description="SRV weight.", + flag_type=int, + required=True, + metavar="WEIGHT", + ), + Flag( + "-port", + description="SRV port.", + flag_type=int, + required=True, + metavar="PORT", + ), + Flag("-host", description="Host target name.", required=True, metavar="NAME"), ], ) @@ -2440,21 +2374,24 @@ def srv_remove(args): # Implementation of sub command 'srv_show' # ############################################## + def _srv_show(srvs=None, host_id=None): assert srvs is not None or host_id is not None hostid2name = dict() host_ids = set() def print_srv(srv: dict, hostname: str, padding: int = 14) -> None: - """Pretty print given srv""" - print("SRV: {1:<{0}} {2:^6} {3:^6} {4:^6} {5}".format( - padding, - srv["name"], - srv["priority"], - srv["weight"], - srv["port"], - hostname, - )) + """Pretty print given srv.""" + print( + "SRV: {1:<{0}} {2:^6} {3:^6} {4:^6} {5}".format( + padding, + srv["name"], + srv["priority"], + srv["weight"], + srv["port"], + hostname, + ) + ) if srvs is None: path = "/api/v1/srvs/" @@ -2473,12 +2410,12 @@ def print_srv(srv: dict, hostname: str, padding: int = 14) -> None: for srv in srvs: if len(srv["name"]) > padding: padding = len(srv["name"]) - host_ids.add(str(srv['host'])) + host_ids.add(str(srv["host"])) - arg = ','.join(host_ids) + arg = ",".join(host_ids) hosts = get_list("/api/v1/hosts/", params={"id__in": arg}) for host in hosts: - hostid2name[host['id']] = host['name'] + hostid2name[host["id"]] = host["name"] prev_name = "" for srv in srvs: @@ -2486,13 +2423,11 @@ def print_srv(srv: dict, hostname: str, padding: int = 14) -> None: srv["name"] = "" else: prev_name = srv["name"] - print_srv(srv, hostid2name[srv['host']], padding) + print_srv(srv, hostid2name[srv["host"]], padding) def srv_show(args): - """Show SRV records for the service. - """ - + """Show SRV records for the service.""" sname = clean_hostname(args.service) # Get all matching SRV records @@ -2511,14 +2446,12 @@ def srv_show(args): # Add 'srv_show' as a sub command to the 'host' command host.add_command( - prog='srv_show', - description='Show SRV records for the service.', - short_desc='Show SRV records.', + prog="srv_show", + description="Show SRV records for the service.", + short_desc="Show SRV records.", callback=srv_show, flags=[ - Flag('service', - description='Host target name.', - metavar='SERVICE'), + Flag("service", description="Host target name.", metavar="SERVICE"), ], ) @@ -2534,10 +2467,9 @@ def srv_show(args): # Implementation of sub command 'sshfp_add' # ############################################# -def sshfp_add(args): - """Add SSHFP record. - """ +def sshfp_add(args): + """Add SSHFP record.""" # Get host info or raise exception info = host_info_by_name(args.name) @@ -2552,28 +2484,24 @@ def sshfp_add(args): path = "/api/v1/sshfps/" history.record_post(path, "", data, undoable=False) post(path, **data) - cli_info("Added SSHFP record {} for host {}" - .format(args.fingerprint, info["name"]), print_msg=True) + cli_info( + "Added SSHFP record {} for host {}".format(args.fingerprint, info["name"]), + print_msg=True, + ) # Add 'sshfp_add' as a sub command to the 'host' command host.add_command( - prog='sshfp_add', - description='Add SSHFP record.', + prog="sshfp_add", + description="Add SSHFP record.", callback=sshfp_add, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('algorithm', - description='SSH algorithm.', - metavar='ALGORITHM'), - Flag('hash_type', - description='Hash type.', - metavar='HASH_TYPE'), - Flag('fingerprint', - description='Hexadecimal fingerprint.', - metavar='FINGERPRINT'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag("algorithm", description="SSH algorithm.", metavar="ALGORITHM"), + Flag("hash_type", description="Hash type.", metavar="HASH_TYPE"), + Flag( + "fingerprint", description="Hexadecimal fingerprint.", metavar="FINGERPRINT" + ), ], ) @@ -2582,18 +2510,22 @@ def sshfp_add(args): # Implementation of sub command 'sshfp_remove' # ################################################ + def sshfp_remove(args): """Remove SSHFP record with a given fingerprint from the host. - A missing fingerprint removes all SSHFP records for the host. + A missing fingerprint removes all SSHFP records for the host. """ def _delete_sshfp_record(sshfp: dict, hname: str): path = f"/api/v1/sshfps/{sshfp['id']}" history.record_delete(path, sshfp, redoable=False) delete(path) - cli_info("removed SSHFP record with fingerprint {} for {}".format(sshfp["fingerprint"], - hname), - print_msg=True) + cli_info( + "removed SSHFP record with fingerprint {} for {}".format( + sshfp["fingerprint"], hname + ), + print_msg=True, + ) # Get host info or raise exception info = host_info_by_name(args.name) @@ -2616,9 +2548,12 @@ def _delete_sshfp_record(sshfp: dict, hname: str): _delete_sshfp_record(sshfp, info["name"]) found = True if not found: - cli_info("found no SSHFP record with fingerprint {} for {}".format(args.fingerprint, - info["name"]), - print_msg=True) + cli_info( + "found no SSHFP record with fingerprint {} for {}".format( + args.fingerprint, info["name"] + ), + print_msg=True, + ) else: for sshfp in sshfps: _delete_sshfp_record(sshfp, info["name"]) @@ -2626,17 +2561,17 @@ def _delete_sshfp_record(sshfp: dict, hname: str): # Add 'sshfp_remove' as a sub command to the 'host' command host.add_command( - prog='sshfp_remove', - description='Remove SSHFP record with a given fingerprint from the host. ' - 'A missing fingerprint removes all SSHFP records for the host.', + prog="sshfp_remove", + description="Remove SSHFP record with a given fingerprint from the host. " + "A missing fingerprint removes all SSHFP records for the host.", callback=sshfp_remove, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('-fingerprint', - description='Hexadecimal fingerprint.', - metavar='FINGERPRINT'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag( + "-fingerprint", + description="Hexadecimal fingerprint.", + metavar="FINGERPRINT", + ), ], ) @@ -2645,30 +2580,32 @@ def _delete_sshfp_record(sshfp: dict, hname: str): # Implementation of sub command 'sshfp_show' # ############################################## + def _sshfp_show(info): path = "/api/v1/sshfps/" params = { - "host": info['id'], + "host": info["id"], } history.record_get(path) sshfps = get_list(path, params=params) headers = ("SSHFPs:", "Algorithm", "Type", "Fingerprint") - row_format = '{:<14}' * len(headers) + row_format = "{:<14}" * len(headers) if sshfps: print(row_format.format(*headers)) for sshfp in sshfps: - print(row_format.format( - '', - sshfp["algorithm"], - sshfp["hash_type"], - sshfp["fingerprint"], - )) + print( + row_format.format( + "", + sshfp["algorithm"], + sshfp["hash_type"], + sshfp["fingerprint"], + ) + ) return len(sshfps) -def sshfp_show(args): - """Show SSHFP records for the host. - """ +def sshfp_show(args): + """Show SSHFP records for the host.""" # Get host info or raise exception info = host_info_by_name(args.name) num_sshfps = _sshfp_show(info) @@ -2678,14 +2615,12 @@ def sshfp_show(args): # Add 'sshfp_show' as a sub command to the 'host' command host.add_command( - prog='sshfp_show', - description='Show SSHFP records for the host.', - short_desc='Show SSHFP record.', + prog="sshfp_show", + description="Show SSHFP records for the host.", + short_desc="Show SSHFP record.", callback=sshfp_show, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), + Flag("name", description="Host target name.", metavar="NAME"), ], ) @@ -2701,6 +2636,7 @@ def sshfp_show(args): # Implementation of sub command 'ttl_remove' # ################################################ + def ttl_remove(args): """Remove explicit TTL for host. If is an alias the alias host is updated. @@ -2719,15 +2655,13 @@ def ttl_remove(args): # Add 'ttl_remove' as a sub command to the 'host' command host.add_command( - prog='ttl_remove', - description='Remove explicit TTL for host. If NAME is an alias the alias ' - 'host is updated.', - short_desc='Remove TTL record.', + prog="ttl_remove", + description="Remove explicit TTL for host. If NAME is an alias the alias " + "host is updated.", + short_desc="Remove TTL record.", callback=ttl_remove, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), + Flag("name", description="Host target name.", metavar="NAME"), ], ) @@ -2736,18 +2670,18 @@ def ttl_remove(args): # Implementation of sub command 'ttl_set' # ############################################# + def ttl_set(args): """Set ttl for name. Valid values are 300 <= TTL <= 68400 or "default". If is an alias the alias host is updated. """ - target_type, info = get_info_by_name(args.name) # TTL sanity check if not is_valid_ttl(args.ttl): cli_warning( - "invalid TTL value: {} (target host {})".format(args.ttl, - info["name"])) + "invalid TTL value: {} (target host {})".format(args.ttl, info["name"]) + ) old_data = {"ttl": info["ttl"] or ""} new_data = {"ttl": args.ttl if args.ttl != "default" else ""} @@ -2761,18 +2695,14 @@ def ttl_set(args): # Add 'ttl_set' as a sub command to the 'host' command host.add_command( - prog='ttl_set', - description='Set ttl for host. Valid values are 300 <= TTL <= 68400 or ' - '"default". If NAME is an alias the alias host is updated.', - short_desc='Set TTL record.', + prog="ttl_set", + description="Set ttl for host. Valid values are 300 <= TTL <= 68400 or " + '"default". If NAME is an alias the alias host is updated.', + short_desc="Set TTL record.", callback=ttl_set, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('ttl', - description='New TTL.', - metavar='TTL'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag("ttl", description="New TTL.", metavar="TTL"), ], ) @@ -2781,9 +2711,9 @@ def ttl_set(args): # Implementation of sub command 'ttl_show' # ############################################## + def ttl_show(args): - """Show ttl for name. If is an alias the alias hosts TTL is shown. - """ + """Show ttl for name. If is an alias the alias hosts TTL is shown.""" info = host_info_by_name(args.name) target_type, info = get_info_by_name(args.name) print_ttl(info["ttl"]) @@ -2792,14 +2722,12 @@ def ttl_show(args): # Add 'ttl_show' as a sub command to the 'host' command host.add_command( - prog='ttl_show', - description='Show ttl for name.', - short_desc='Show TTL.', + prog="ttl_show", + description="Show ttl for name.", + short_desc="Show TTL.", callback=ttl_show, flags=[ - Flag('name', - description='Name', - metavar='NAME'), + Flag("name", description="Name", metavar="NAME"), ], ) @@ -2815,6 +2743,7 @@ def ttl_show(args): # Implementation of sub command 'txt_add' # ############################################# + def txt_add(args): """Add a txt record to host. must be enclosed in double quotes if it contains more than one word. @@ -2824,10 +2753,7 @@ def txt_add(args): if any(args.text == i["txt"] for i in info["txts"]): cli_warning("The TXT record already exists for {}".format(info["name"])) - data = { - "host": info["id"], - "txt": args.text - } + data = {"host": info["id"], "txt": args.text} # Add TXT record to host path = "/api/v1/txts/" history.record_post(path, "", data, undoable=False) @@ -2837,18 +2763,18 @@ def txt_add(args): # Add 'txt_add' as a sub command to the 'host' command host.add_command( - prog='txt_add', - description='Add a txt record to host. TEXT must be enclosed in double ' - 'quotes if it contains more than one word.', - short_desc='Add TXT record.', + prog="txt_add", + description="Add a txt record to host. TEXT must be enclosed in double " + "quotes if it contains more than one word.", + short_desc="Add TXT record.", callback=txt_add, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('text', - description='TXT record text. Must be quoted if contains spaces.', - metavar='TEXT'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag( + "text", + description="TXT record text. Must be quoted if contains spaces.", + metavar="TEXT", + ), ], ) @@ -2857,9 +2783,9 @@ def txt_add(args): # Implementation of sub command 'txt_remove' # ################################################ + def txt_remove(args): - """Remove TXT record for host with . - """ + """Remove TXT record for host with .""" # Get host info or raise exception info = host_info_by_name(args.name) hostname = info["name"] @@ -2880,20 +2806,18 @@ def txt_remove(args): # Add 'txt_remove' as a sub command to the 'host' command host.add_command( - prog='txt_remove', - description=' Remove TXT record for host matching TEXT.', - short_desc='Remove TXT record.', + prog="txt_remove", + description=" Remove TXT record for host matching TEXT.", + short_desc="Remove TXT record.", callback=txt_remove, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), - Flag('text', - description='TXT record text. Must be quoted if contains spaces.', - metavar='TEXT'), - Flag('-force', - action='store_true', - description='Enable force.'), + Flag("name", description="Host target name.", metavar="NAME"), + Flag( + "text", + description="TXT record text. Must be quoted if contains spaces.", + metavar="TEXT", + ), + Flag("-force", action="store_true", description="Enable force."), ], ) @@ -2902,13 +2826,13 @@ def txt_remove(args): # Implementation of sub command 'txt_show' # ############################################## + def txt_show(args): - """Show all TXT records for host. - """ + """Show all TXT records for host.""" info = host_info_by_name(args.name) path = "/api/v1/txts/" params = { - "host": info['id'], + "host": info["id"], } history.record_get(path) txts = get_list(path, params=params) @@ -2919,13 +2843,11 @@ def txt_show(args): # Add 'txt_show' as a sub command to the 'host' command host.add_command( - prog='txt_show', - description='Show all TXT records for host.', - short_desc='Show TXT records.', + prog="txt_show", + description="Show all TXT records for host.", + short_desc="Show TXT records.", callback=txt_show, flags=[ - Flag('name', - description='Host target name.', - metavar='NAME'), + Flag("name", description="Host target name.", metavar="NAME"), ], ) diff --git a/mreg_cli/label.py b/mreg_cli/label.py index 63152a6b..813f972c 100644 --- a/mreg_cli/label.py +++ b/mreg_cli/label.py @@ -1,116 +1,134 @@ from .cli import Flag, cli from .history import history -from .log import cli_info, cli_warning, cli_error -from .util import get, get_list, post, patch, delete, print_table +from .log import cli_info, cli_warning +from .util import delete, get, get_list, patch, post, print_table label = cli.add_command( - prog='label', - description='Manage labels.', + prog="label", + description="Manage labels.", ) + def label_add(args): - if ' ' in args.name: + if " " in args.name: print("The label name can't contain spaces.") return - data = { - "name": args.name, - "description": args.description - } + data = {"name": args.name, "description": args.description} path = "/api/v1/labels/" history.record_post(path, "", data, undoable=False) post(path, **data) - cli_info(f"Added label \"{args.name}\"", True) + cli_info(f'Added label "{args.name}"', True) + label.add_command( - prog='add', - description='Add a label', - short_desc='Add a label', + prog="add", + description="Add a label", + short_desc="Add a label", callback=label_add, flags=[ - Flag('name', short_desc='Label name', description='The name of the new label'), - Flag('description', description='The purpose of the label') - ] + Flag("name", short_desc="Label name", description="The name of the new label"), + Flag("description", description="The purpose of the label"), + ], ) + def label_list(args): - labels = get_list("/api/v1/labels/", params={"ordering":"name"}) + labels = get_list("/api/v1/labels/", params={"ordering": "name"}) if not labels: cli_info("No labels", True) return - print_table(('Name','Description'), ('name','description'), labels) + print_table(("Name", "Description"), ("name", "description"), labels) + + +label.add_command(prog="list", description="List labels", callback=label_list, flags=[]) -label.add_command( - prog='list', - description='List labels', - callback=label_list, - flags=[] -) def label_delete(args): path = f"/api/v1/labels/name/{args.name}" history.record_delete(path, dict(), undoable=False) delete(path) - cli_info(f"Removed label \"{args.name}\"", True) + cli_info(f'Removed label "{args.name}"', True) + label.add_command( - prog='remove', - description='Remove a label', + prog="remove", + description="Remove a label", callback=label_delete, flags=[ - Flag('name', short_desc='Label name', description='The name of the label to remove') - ] + Flag( + "name", + short_desc="Label name", + description="The name of the label to remove", + ) + ], ) + def label_info(args): path = f"/api/v1/labels/name/{args.name}" label = get(path).json() print("Name: ", label["name"]) print("Description: ", label["description"]) - rolelist = get_list("/api/v1/hostpolicy/roles/", params={"labels__name":args.name}) + rolelist = get_list("/api/v1/hostpolicy/roles/", params={"labels__name": args.name}) print("Roles with this label: ") if rolelist: for r in rolelist: - print(" "+r["name"]) + print(" " + r["name"]) else: print(" None") - permlist = get_list("/api/v1/permissions/netgroupregex/", params={"labels__name":args.name}) + permlist = get_list( + "/api/v1/permissions/netgroupregex/", params={"labels__name": args.name} + ) print("Permissions with this label:") if permlist: - print_table(("IP range", "Group", "Reg.exp."), ("range","group","regex"), permlist, indent=4) + print_table( + ("IP range", "Group", "Reg.exp."), + ("range", "group", "regex"), + permlist, + indent=4, + ) else: print(" None") + label.add_command( - prog='info', - description='Show details about a label', + prog="info", + description="Show details about a label", callback=label_info, - flags=[ - Flag('name', short_desc='Label name', description='The name of the label') - ] + flags=[Flag("name", short_desc="Label name", description="The name of the label")], ) + def label_rename(args): path = f"/api/v1/labels/name/{args.oldname}" res = get(path, ok404=True) if not res: cli_warning(f'Label "{args.oldname}" does not exist.') - data = { - "name": args.newname - } + data = {"name": args.newname} if args.desc: data["description"] = args.desc patch(path, **data) - cli_info("Renamed label \"{}\" to \"{}\"".format(args.oldname, args.newname), True) + cli_info('Renamed label "{}" to "{}"'.format(args.oldname, args.newname), True) + label.add_command( - prog='rename', - description='Rename a label and/or change the description', + prog="rename", + description="Rename a label and/or change the description", callback=label_rename, flags=[ - Flag('oldname', short_desc='Old name', description='The old (current) name of the label'), - Flag('newname', short_desc='New name', description='The new name of the label'), - Flag('-desc', metavar='DESCRIPTION', short_desc='New description', description='The new description of the label') - ] + Flag( + "oldname", + short_desc="Old name", + description="The old (current) name of the label", + ), + Flag("newname", short_desc="New name", description="The new name of the label"), + Flag( + "-desc", + metavar="DESCRIPTION", + short_desc="New description", + description="The new description of the label", + ), + ], ) diff --git a/mreg_cli/log.py b/mreg_cli/log.py index edfbfee1..2c73b698 100644 --- a/mreg_cli/log.py +++ b/mreg_cli/log.py @@ -4,9 +4,8 @@ from datetime import datetime from typing import NoReturn, Optional, Type -from .exceptions import CliError, CliWarning - from . import recorder +from .exceptions import CliError, CliWarning logfile = None @@ -37,7 +36,7 @@ def cli_error( """Write a ERROR log entry.""" pre = _prefix_from_stack() s = "{} {} [ERROR] {}: {}".format( - datetime.now().isoformat(sep=' ', timespec="seconds"), + datetime.now().isoformat(sep=" ", timespec="seconds"), getpass.getuser(), pre, msg, @@ -61,7 +60,7 @@ def cli_warning( """Write a WARNING log entry.""" pre = _prefix_from_stack() s = "{} {} [WARNING] {}: {}".format( - datetime.now().isoformat(sep=' ', timespec="seconds"), + datetime.now().isoformat(sep=" ", timespec="seconds"), getpass.getuser(), pre, msg, @@ -82,7 +81,7 @@ def cli_info(msg: str, print_msg: bool = False) -> None: """Write an OK log entry.""" pre = _prefix_from_stack() s = "{} {} [OK] {}: {}".format( - datetime.now().isoformat(sep=' ', timespec="seconds"), + datetime.now().isoformat(sep=" ", timespec="seconds"), getpass.getuser(), pre, msg, diff --git a/mreg_cli/log.pyi b/mreg_cli/log.pyi index 5bf477b6..ed57b2cc 100644 --- a/mreg_cli/log.pyi +++ b/mreg_cli/log.pyi @@ -1,59 +1,38 @@ -from typing import NoReturn, Optional, Type, overload, TYPE_CHECKING +from typing import TYPE_CHECKING, NoReturn, Optional, Type, overload + if TYPE_CHECKING: from typing_extensions import Literal - @overload -def cli_error(msg: str) -> NoReturn: - ... - - +def cli_error(msg: str) -> NoReturn: ... @overload def cli_error( msg: str, raise_exception: "Literal[True]" = True, exception: Type[Exception] = ... -) -> NoReturn: - ... - - +) -> NoReturn: ... @overload def cli_error( - msg: str, raise_exception: "Literal[False]" = False, exception: Type[Exception] = ... -) -> None: - ... - - + msg: str, + raise_exception: "Literal[False]" = False, + exception: Type[Exception] = ..., +) -> None: ... @overload def cli_error( msg: str, raise_exception: bool = ..., exception: Type[Exception] = ... -) -> Optional[NoReturn]: - ... - +) -> Optional[NoReturn]: ... @overload -def cli_warning(msg: str) -> NoReturn: - ... - - +def cli_warning(msg: str) -> NoReturn: ... @overload def cli_warning( msg: str, raise_exception: "Literal[True]" = True, exception: Type[Exception] = ... -) -> NoReturn: - ... - - +) -> NoReturn: ... @overload def cli_warning( - msg: str, raise_exception: "Literal[False]" = False, exception: Type[Exception] = ... -) -> None: - ... - - + msg: str, + raise_exception: "Literal[False]" = False, + exception: Type[Exception] = ..., +) -> None: ... @overload def cli_warning( msg: str, raise_exception: bool = ..., exception: Type[Exception] = ... -) -> Optional[NoReturn]: - ... - - - -def cli_info(msg: str, print_msg: bool = ...) -> None: - ... \ No newline at end of file +) -> Optional[NoReturn]: ... +def cli_info(msg: str, print_msg: bool = ...) -> None: ... diff --git a/mreg_cli/main.py b/mreg_cli/main.py index 10ef74f2..3fff2773 100644 --- a/mreg_cli/main.py +++ b/mreg_cli/main.py @@ -7,14 +7,14 @@ from prompt_toolkit import HTML from prompt_toolkit.shortcuts import CompleteStyle, PromptSession -from . import config, log, util, recorder +from . import config, log, recorder, util from .cli import cli, source logger = logging.getLogger(__name__) def setup_logging(verbosity): - """ configure logging if verbosity is not None """ + """Configure logging if verbosity is not None.""" if verbosity is None: root = logging.getLogger() root.addHandler(logging.NullHandler()) @@ -24,7 +24,6 @@ def setup_logging(verbosity): def main(): - # Read config file first, to provide defaults conf = {} configpath = config.get_config_file() @@ -35,81 +34,86 @@ def main(): parser = argparse.ArgumentParser(description="The MREG cli") - connect_args = parser.add_argument_group('connection settings') + connect_args = parser.add_argument_group("connection settings") connect_args.add_argument( - '--url', - default=conf.get('url', config.get_default_url()), + "--url", + default=conf.get("url", config.get_default_url()), help="use mreg server at %(metavar)s (default: %(default)s)", - metavar='URL', + metavar="URL", ) connect_args.add_argument( - '-u', '--user', - default=conf.get('user', getpass.getuser()), + "-u", + "--user", + default=conf.get("user", getpass.getuser()), help="authenticate as %(metavar)s (default: %(default)s)", - metavar='USER', + metavar="USER", ) - mreg_args = parser.add_argument_group('mreg settings') + mreg_args = parser.add_argument_group("mreg settings") mreg_args.add_argument( - '-d', '--domain', - default=conf.get('domain', config.get_default_domain()), + "-d", + "--domain", + default=conf.get("domain", config.get_default_domain()), help="default %(metavar)s (default: %(default)s)", - metavar='DOMAIN', + metavar="DOMAIN", ) mreg_args.add_argument( - '-p', '--prompt', + "-p", + "--prompt", default="mreg", help="default %(metavar)s (default: %(default)s)", - metavar='PROMPT', + metavar="PROMPT", ) - output_args = parser.add_argument_group('output settings') + output_args = parser.add_argument_group("output settings") output_args.add_argument( - '-v', '--verbosity', - dest='verbosity', - action='count', + "-v", + "--verbosity", + dest="verbosity", + action="count", default=None, help="show debug messages on stderr", ) output_args.add_argument( - '-l', '--logfile', - dest='logfile', + "-l", + "--logfile", + dest="logfile", help="write log to %(metavar)s", - metavar='LOGFILE', + metavar="LOGFILE", ) output_args.add_argument( - '--show-token', - dest='show_token', - action='store_true', + "--show-token", + dest="show_token", + action="store_true", help="show API token after login", ) output_args.add_argument( - '--record', - dest='record_traffic', + "--record", + dest="record_traffic", help="Record all server/client traffic to %(metavar)s", - metavar='RECFILE', + metavar="RECFILE", ) output_args.add_argument( - '--source', - dest='source', + "--source", + dest="source", help="Read commands from %(metavar)s", - metavar='SOURCE', + metavar="SOURCE", ) args = parser.parse_args() setup_logging(args.verbosity) - logger.debug(f'args: {args}') + logger.debug(f"args: {args}") conf = {k: v for k, v in vars(args).items() if v} util.set_config(conf) - if 'logfile' in conf: - log.logfile = conf['logfile'] + if "logfile" in conf: + log.logfile = conf["logfile"] rec = recorder.Recorder() - if 'record_traffic' in conf: - rec.start_recording(conf['record_traffic']) + if "record_traffic" in conf: + rec.start_recording(conf["record_traffic"]) if "user" not in conf: print("Username not set in config or as argument") @@ -121,39 +125,41 @@ def main(): try: util.login1(conf["user"], conf["url"]) except (EOFError, KeyboardInterrupt): - print('') - raise SystemExit() + print("") + raise SystemExit() from None if args.show_token: print(util.session.headers["Authorization"]) # Must import the commands, for the side effects of creating the commands - # when importing. - from . import dhcp # noqa: F401 - from . import history # noqa: F401 - from . import group # noqa: F401 - from . import host # noqa: F401 - from . import label - from . import network # noqa: F401 + # when importing. Ensure that the noqa comments are updated when new + # commands are added, otherwise the import will be removed by ruff. + from . import dhcp # noqa: F401, + from . import group # noqa: F401 + from . import history # noqa: F401 + from . import host # noqa: F401 + from . import label # noqa: F401 + from . import network # noqa: F401 from . import permission # noqa: F401 from . import policy # noqa: F401 - from . import zone # noqa: F401 - from . import bacnet # must be imported after host + from . import zone # noqa: F401 # session is a PromptSession object from prompt_toolkit which handles # some configurations of the prompt for us: the text of the prompt; the # completer; and other visual things. - session = PromptSession(message=HTML(f'{args.prompt}> '), - search_ignore_case=True, - completer=cli, - complete_while_typing=True, - complete_style=CompleteStyle.MULTI_COLUMN) + session = PromptSession( + message=HTML(f"{args.prompt}> "), + search_ignore_case=True, + completer=cli, + complete_while_typing=True, + complete_style=CompleteStyle.MULTI_COLUMN, + ) # Welcome text for the app - print('Type -h for help.') + print("Type -h for help.") # if the --source parameter was given, read commands from the source file and then exit - if 'source' in conf: - source([conf['source']], 'verbosity' in conf, False) + if "source" in conf: + source([conf["source"]], "verbosity" in conf, False) return # The app runs in an infinite loop and is expected to exit using sys.exit() @@ -163,7 +169,7 @@ def main(): except KeyboardInterrupt: continue except EOFError: - raise SystemExit() + raise SystemExit() from None try: for line in lines.splitlines(): # If recording commands, submit the command line. @@ -176,5 +182,5 @@ def main(): print(e) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/mreg_cli/network.py b/mreg_cli/network.py index d2f53b9f..64757b62 100644 --- a/mreg_cli/network.py +++ b/mreg_cli/network.py @@ -1,12 +1,13 @@ -from argparse import Namespace import ipaddress -from typing import Any, Dict, Union import urllib.parse +from argparse import Namespace +from typing import Any, Dict, Union from .cli import Flag, cli from .history import history -from .log import cli_info, cli_warning, cli_error +from .log import cli_error, cli_info, cli_warning from .util import ( + convert_wildcard_to_regex, delete, get, get_list, @@ -24,7 +25,6 @@ patch, post, string_to_int, - convert_wildcard_to_regex, ) ################################### @@ -32,9 +32,9 @@ ################################### network = cli.add_command( - prog='network', - description='Manage networks.', - short_desc='Manage networks', + prog="network", + description="Manage networks.", + short_desc="Manage networks", ) @@ -43,7 +43,7 @@ def get_network_range_from_input(net: str) -> str: net = net[:-1] if is_valid_ip(net): network = get_network(net) - return network['network'] + return network["network"] elif is_valid_network(net): return net else: @@ -52,10 +52,13 @@ def get_network_range_from_input(net: str) -> str: # helper methods def print_network_unused(count: int, padding: int = 25) -> None: - "Pretty print amount of unused addresses" + "Pretty print amount of unused addresses." assert isinstance(count, int) print( - "{1:<{0}}{2}{3}".format(padding, "Unused addresses:", count, " (excluding reserved adr.)")) + "{1:<{0}}{2}{3}".format( + padding, "Unused addresses:", count, " (excluding reserved adr.)" + ) + ) def print_network_excluded_ranges(info: dict, padding: int = 25) -> None: @@ -63,23 +66,26 @@ def print_network_excluded_ranges(info: dict, padding: int = 25) -> None: return count = 0 for i in info: - start_ip = ipaddress.ip_address(i['start_ip']) - end_ip = ipaddress.ip_address(i['end_ip']) + start_ip = ipaddress.ip_address(i["start_ip"]) + end_ip = ipaddress.ip_address(i["end_ip"]) count += int(end_ip) - int(start_ip) if end_ip == start_ip: count += 1 print("{1:<{0}}{2} ipaddresses".format(padding, "Excluded ranges:", count)) for i in info: - print("{1:<{0}}{2} -> {3}".format(padding, '', i['start_ip'], i['end_ip'])) + print("{1:<{0}}{2} -> {3}".format(padding, "", i["start_ip"], i["end_ip"])) def print_network_reserved(ip_range: str, reserved: int, padding: int = 25) -> None: - "Pretty print ip range and reserved addresses list" + "Pretty print ip range and reserved addresses list." assert isinstance(ip_range, str) assert isinstance(reserved, int) network = ipaddress.ip_network(ip_range) - print("{1:<{0}}{2} - {3}".format(padding, "IP-range:", network.network_address, - network.broadcast_address)) + print( + "{1:<{0}}{2} - {3}".format( + padding, "IP-range:", network.network_address, network.broadcast_address + ) + ) print("{1:<{0}}{2}".format(padding, "Reserved host addresses:", reserved)) print("{1:<{0}}{2}{3}".format(padding, "", network.network_address, " (net)")) res = get_network_reserved_ips(ip_range) @@ -91,7 +97,11 @@ def print_network_reserved(ip_range: str, reserved: int, padding: int = 25) -> N for host in res: print("{1:<{0}}{2}".format(padding, "", host)) if broadcast: - print("{1:<{0}}{2}{3}".format(padding, "", network.broadcast_address, " (broadcast)")) + print( + "{1:<{0}}{2}{3}".format( + padding, "", network.broadcast_address, " (broadcast)" + ) + ) def print_network(info: int, text: str, padding: int = 25) -> None: @@ -102,9 +112,9 @@ def print_network(info: int, text: str, padding: int = 25) -> None: # Implementation of sub command 'create' # ########################################## + def create(args): - """Create a new network - """ + """Create a new network.""" frozen = True if args.frozen else False if args.vlan: string_to_int(args.vlan, "VLAN") @@ -115,47 +125,45 @@ def create(args): networks_existing = get_list("/api/v1/networks/") for network in networks_existing: - network_object = ipaddress.ip_network(network['network']) + network_object = ipaddress.ip_network(network["network"]) if network_object.overlaps(ipaddress.ip_network(args.network)): - cli_warning("Overlap found between new network {} and existing " - "network {}".format(ipaddress.ip_network(args.network), - network['network'])) - - post("/api/v1/networks/", network=args.network, description=args.desc, vlan=args.vlan, - category=args.category, location=args.location, frozen=frozen) + cli_warning( + "Overlap found between new network {} and existing " + "network {}".format( + ipaddress.ip_network(args.network), network["network"] + ) + ) + + post( + "/api/v1/networks/", + network=args.network, + description=args.desc, + vlan=args.vlan, + category=args.category, + location=args.location, + frozen=frozen, + ) cli_info("created network {}".format(args.network), True) network.add_command( - prog='create', - description='Create a new network', - short_desc='Create a new network', + prog="create", + description="Create a new network", + short_desc="Create a new network", callback=create, flags=[ - Flag('-network', - description='Network.', - required=True, - metavar='NETWORK'), - Flag('-desc', - description='Network description.', - required=True, - metavar='DESCRIPTION'), - Flag('-vlan', - description='VLAN.', - default=None, - metavar='VLAN'), - Flag('-category', - description='Category.', - default=None, - metavar='Category'), - Flag('-location', - description='Location.', - default=None, - metavar='LOCATION'), - Flag('-frozen', - description='Set frozen network.', - action='store_true'), - ] + Flag("-network", description="Network.", required=True, metavar="NETWORK"), + Flag( + "-desc", + description="Network description.", + required=True, + metavar="DESCRIPTION", + ), + Flag("-vlan", description="VLAN.", default=None, metavar="VLAN"), + Flag("-category", description="Category.", default=None, metavar="Category"), + Flag("-location", description="Location.", default=None, metavar="LOCATION"), + Flag("-frozen", description="Set frozen network.", action="store_true"), + ], ) @@ -165,23 +173,24 @@ def create(args): def info(args): - """Display network info - """ + """Display network info.""" for net in args.networks: print_network_info(net) network.add_command( - prog='info', - description='Display network info for one or more networks.', - short_desc='Display network info.', + prog="info", + description="Display network info for one or more networks.", + short_desc="Display network info.", callback=info, flags=[ - Flag('networks', - description='One or more networks.', - nargs='+', - metavar='NETWORK'), - ] + Flag( + "networks", + description="One or more networks.", + nargs="+", + metavar="NETWORK", + ), + ], ) @@ -264,7 +273,7 @@ def find(args: Namespace): if not params: cli_warning("Need at least one search criteria") - path = f"/api/v1/networks/" + path = "/api/v1/networks/" networks = get_list(path, params) if not networks: @@ -349,7 +358,7 @@ def find(args: Namespace): "-limit", description="Maximum number of networks to print", metavar="LIMIT", - type=int, + flag_type=int, ), Flag( "-silent", @@ -364,10 +373,9 @@ def find(args: Namespace): # Implementation of sub command 'list_unused_addresses' # ######################################################### -def list_unused_addresses(args): - """Lists all the unused addresses for a network - """ +def list_unused_addresses(args): + """Lists all the unused addresses for a network.""" ip_range = get_network_range_from_input(args.network) unused = get_network_unused_list(ip_range) if not unused: @@ -378,15 +386,13 @@ def list_unused_addresses(args): network.add_command( - prog='list_unused_addresses', - description='Lists all the unused addresses for a network', - short_desc='Lists unused addresses', + prog="list_unused_addresses", + description="Lists all the unused addresses for a network", + short_desc="Lists unused addresses", callback=list_unused_addresses, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + ], ) @@ -394,9 +400,9 @@ def list_unused_addresses(args): # Implementation of sub command 'list_used_addresses' # ####################################################### + def list_used_addresses(args): - """Lists all the used addresses for a network - """ + """Lists all the used addresses for a network.""" ip_range = get_network_range_from_input(args.network) urlencoded_ip_range = urllib.parse.quote(ip_range) @@ -425,15 +431,13 @@ def list_used_addresses(args): network.add_command( - prog='list_used_addresses', - description='Lists all the used addresses for a network', - short_desc='Lists all the used addresses for a network', + prog="list_used_addresses", + description="Lists all the used addresses for a network", + short_desc="Lists all the used addresses for a network", callback=list_used_addresses, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + ], ) @@ -441,14 +445,16 @@ def list_used_addresses(args): # Implementation of sub command 'remove' # ########################################## + def remove(args): - """Remove network - """ + """Remove network.""" ipaddress.ip_network(args.network) host_list = get_network_used_list(args.network) if host_list: - cli_warning("Network contains addresses that are in use. Remove hosts " - "before deletion") + cli_warning( + "Network contains addresses that are in use. Remove hosts " + "before deletion" + ) if not args.force: cli_warning("Must force.") @@ -458,18 +464,14 @@ def remove(args): network.add_command( - prog='remove', - description='Remove network', - short_desc='Remove network', + prog="remove", + description="Remove network", + short_desc="Remove network", callback=remove, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -477,39 +479,32 @@ def remove(args): # Implementation of sub command 'add_excluded_range' # ###################################################### + def add_excluded_range(args): - """Add an excluded range to a network""" + """Add an excluded range to a network.""" info = get_network(args.network) - network = info['network'] + network = info["network"] if not is_valid_ip(args.start_ip): cli_error(f"Start ipaddress {args.start_ip} not valid") if not is_valid_ip(args.end_ip): cli_error(f"End ipaddress {args.end_ip} not valid") path = f"/api/v1/networks/{urllib.parse.quote(network)}/excluded_ranges/" - data = {'network': info['id'], - 'start_ip': args.start_ip, - 'end_ip': args.end_ip} + data = {"network": info["id"], "start_ip": args.start_ip, "end_ip": args.end_ip} post(path, **data) cli_info(f"Added exclude range to {network}", True) network.add_command( - prog='add_excluded_range', - description='Add an excluded range to a network', - short_desc='Add an excluded range to a network', + prog="add_excluded_range", + description="Add an excluded range to a network", + short_desc="Add an excluded range to a network", callback=add_excluded_range, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('start_ip', - description='Start ipaddress', - metavar='STARTIP'), - Flag('end_ip', - description='End ipaddress', - metavar='ENDIP'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("start_ip", description="Start ipaddress", metavar="STARTIP"), + Flag("end_ip", description="End ipaddress", metavar="ENDIP"), + ], ) @@ -517,45 +512,40 @@ def add_excluded_range(args): # Implementation of sub command 'remove_excluded_range' # ######################################################### + def remove_excluded_range(args): - """Remove an excluded range to a network""" + """Remove an excluded range to a network.""" info = get_network(args.network) - network = info['network'] + network = info["network"] if not is_valid_ip(args.start_ip): cli_error(f"Start ipaddress {args.start_ip} not valid") if not is_valid_ip(args.end_ip): cli_error(f"End ipaddress {args.end_ip} not valid") - if not info['excluded_ranges']: - cli_error(f'Network {network} has no excluded ranges') + if not info["excluded_ranges"]: + cli_error(f"Network {network} has no excluded ranges") - for i in info['excluded_ranges']: - if i['start_ip'] == args.start_ip and i['end_ip'] == args.end_ip: + for i in info["excluded_ranges"]: + if i["start_ip"] == args.start_ip and i["end_ip"] == args.end_ip: path = f"/api/v1/networks/{urllib.parse.quote(network)}/excluded_ranges/{i['id']}" break else: - cli_error('Found no matching exclude range.') + cli_error("Found no matching exclude range.") delete(path) cli_info(f"Removed exclude range from {network}", True) network.add_command( - prog='remove_excluded_range', - description='Remove an excluded range to a network', - short_desc='Remove an excluded range to a network', + prog="remove_excluded_range", + description="Remove an excluded range to a network", + short_desc="Remove an excluded range to a network", callback=remove_excluded_range, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('start_ip', - description='Start ipaddress', - metavar='STARTIP'), - Flag('end_ip', - description='End ipaddress', - metavar='ENDIP'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("start_ip", description="Start ipaddress", metavar="STARTIP"), + Flag("end_ip", description="End ipaddress", metavar="ENDIP"), + ], ) @@ -563,32 +553,30 @@ def remove_excluded_range(args): # Implementation of sub command 'set_category' # ################################################ + def set_category(args): - """Set category tag for network - """ + """Set category tag for network.""" network = get_network(args.network) if not is_valid_category_tag(args.category): cli_warning("Not a valid category tag") path = f"/api/v1/networks/{urllib.parse.quote(network['network'])}" patch(path, category=args.category) - cli_info("updated category tag to '{}' for {}" - .format(args.category, network['network']), True) + cli_info( + "updated category tag to '{}' for {}".format(args.category, network["network"]), + True, + ) network.add_command( - prog='set_category', - description='Set category tag for network', - short_desc='Set category tag for network', + prog="set_category", + description="Set category tag for network", + short_desc="Set category tag for network", callback=set_category, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('category', - description='Category tag.', - metavar='CATEGORY-TAG'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("category", description="Category tag.", metavar="CATEGORY-TAG"), + ], ) @@ -596,29 +584,29 @@ def set_category(args): # Implementation of sub command 'set_description' # ################################################### + def set_description(args): - """Set description for network - """ + """Set description for network.""" network = get_network(args.network) path = f"/api/v1/networks/{urllib.parse.quote(network['network'])}" patch(path, description=args.description) - cli_info("updated description to '{}' for {}".format(args.description, - network['network']), True) + cli_info( + "updated description to '{}' for {}".format( + args.description, network["network"] + ), + True, + ) network.add_command( - prog='set_description', # - description='Set description for network', - short_desc='Set description for network', + prog="set_description", # + description="Set description for network", + short_desc="Set description for network", callback=set_description, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('description', - description='Network description.', - metavar='DESC'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("description", description="Network description.", metavar="DESC"), + ], ) @@ -626,10 +614,9 @@ def set_description(args): # Implementation of sub command 'set_dns_delegated' # ##################################################### -def set_dns_delegated(args): - """Set that DNS-administration is being handled elsewhere. - """ +def set_dns_delegated(args): + """Set that DNS-administration is being handled elsewhere.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" @@ -638,15 +625,13 @@ def set_dns_delegated(args): network.add_command( - prog='set_dns_delegated', - description='Set that DNS-administration is being handled elsewhere.', - short_desc='Set that DNS-administration is being handled elsewhere.', + prog="set_dns_delegated", + description="Set that DNS-administration is being handled elsewhere.", + short_desc="Set that DNS-administration is being handled elsewhere.", callback=set_dns_delegated, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + ], ) @@ -654,10 +639,9 @@ def set_dns_delegated(args): # Implementation of sub command 'set_frozen' # ############################################## -def set_frozen(args): - """Freeze a network. - """ +def set_frozen(args): + """Freeze a network.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" @@ -666,15 +650,13 @@ def set_frozen(args): network.add_command( - prog='set_frozen', - description='Freeze a network.', - short_desc='Freeze a network.', + prog="set_frozen", + description="Freeze a network.", + short_desc="Freeze a network.", callback=set_frozen, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + ], ) @@ -682,10 +664,9 @@ def set_frozen(args): # Implementation of sub command 'set_location' # ################################################ -def set_location(args): - """Set location tag for network - """ +def set_location(args): + """Set location tag for network.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) if not is_valid_location_tag(args.location): @@ -693,23 +674,20 @@ def set_location(args): path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" patch(path, location=args.location) - cli_info("updated location tag to '{}' for {}" - .format(args.location, ip_range), True) + cli_info( + "updated location tag to '{}' for {}".format(args.location, ip_range), True + ) network.add_command( - prog='set_location', - description='Set location tag for network', - short_desc='Set location tag for network', + prog="set_location", + description="Set location tag for network", + short_desc="Set location tag for network", callback=set_location, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('location', - description='Location tag.', - metavar='LOCATION-TAG'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("location", description="Location tag.", metavar="LOCATION-TAG"), + ], ) @@ -717,33 +695,31 @@ def set_location(args): # Implementation of sub command 'set_reserved' # ################################################ -def set_reserved(args): - """Set number of reserved hosts. - """ +def set_reserved(args): + """Set number of reserved hosts.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) reserved = args.number path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" patch(path, reserved=reserved) - cli_info(f"updated reserved to '{reserved}' for {ip_range}", - print_msg=True) + cli_info(f"updated reserved to '{reserved}' for {ip_range}", print_msg=True) network.add_command( - prog='set_reserved', - description='Set number of reserved hosts.', - short_desc='Set number of reserved hosts.', + prog="set_reserved", + description="Set number of reserved hosts.", + short_desc="Set number of reserved hosts.", callback=set_reserved, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('number', - description='Number of reserved hosts.', - type=int, - metavar='NUM'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag( + "number", + description="Number of reserved hosts.", + flag_type=int, + metavar="NUM", + ), + ], ) @@ -751,10 +727,9 @@ def set_reserved(args): # Implementation of sub command 'set_vlan' # ############################################ -def set_vlan(args): - """Set VLAN for network - """ +def set_vlan(args): + """Set VLAN for network.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" @@ -763,19 +738,14 @@ def set_vlan(args): network.add_command( - prog='set_vlan', # - description='Set VLAN for network', - short_desc='Set VLAN for network', + prog="set_vlan", # + description="Set VLAN for network", + short_desc="Set VLAN for network", callback=set_vlan, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - Flag('vlan', - description='VLAN.', - type=int, - metavar='VLAN'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + Flag("vlan", description="VLAN.", flag_type=int, metavar="VLAN"), + ], ) @@ -783,10 +753,9 @@ def set_vlan(args): # Implementation of sub command 'unset_dns_delegated' # ####################################################### -def unset_dns_delegated(args): - """Set that DNS-administration is not being handled elsewhere. - """ +def unset_dns_delegated(args): + """Set that DNS-administration is not being handled elsewhere.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" @@ -795,15 +764,13 @@ def unset_dns_delegated(args): network.add_command( - prog='unset_dns_delegated', - description='Set that DNS-administration is not being handled elsewhere.', - short_desc='Set that DNS-administration is not being handled elsewhere.', + prog="unset_dns_delegated", + description="Set that DNS-administration is not being handled elsewhere.", + short_desc="Set that DNS-administration is not being handled elsewhere.", callback=unset_dns_delegated, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + ], ) @@ -811,10 +778,9 @@ def unset_dns_delegated(args): # Implementation of sub command 'unset_frozen' # ################################################ -def unset_frozen(args): - """Unfreeze a network. - """ +def unset_frozen(args): + """Unfreeze a network.""" ip_range = get_network_range_from_input(args.network) get_network(ip_range) path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}" @@ -823,13 +789,11 @@ def unset_frozen(args): network.add_command( - prog='unset_frozen', - description='Unfreeze a network.', - short_desc='Unfreeze a network.', + prog="unset_frozen", + description="Unfreeze a network.", + short_desc="Unfreeze a network.", callback=unset_frozen, flags=[ - Flag('network', - description='Network.', - metavar='NETWORK'), - ] + Flag("network", description="Network.", metavar="NETWORK"), + ], ) diff --git a/mreg_cli/permission.py b/mreg_cli/permission.py index 91c9d385..34a4fec7 100644 --- a/mreg_cli/permission.py +++ b/mreg_cli/permission.py @@ -3,16 +3,25 @@ from .cli import Flag, cli from .history import history from .log import cli_info, cli_warning -from .util import convert_wildcard_to_regex, delete, get_list, get, is_valid_network, post, patch, print_table +from .util import ( + convert_wildcard_to_regex, + delete, + get, + get_list, + is_valid_network, + patch, + post, + print_table, +) ################################### # Add the main command 'access' # ################################### permission = cli.add_command( - prog='permission', - description='Manage permissions.', - short_desc='Manage permissions', + prog="permission", + description="Manage permissions.", + short_desc="Manage permissions", ) @@ -22,14 +31,14 @@ def network_list(args): - """ - Lists permissions for networks - """ + """Lists permissions for networks.""" # Replace with a.supernet_of(b) when python 3.7 is required def _supernet_of(a, b): - return (a.network_address <= b.network_address and - a.broadcast_address >= b.broadcast_address) + return ( + a.network_address <= b.network_address + and a.broadcast_address >= b.broadcast_address + ) params = { "ordering": "range,group", @@ -43,9 +52,10 @@ def _supernet_of(a, b): if args.range is not None: argnetwork = ipaddress.ip_network(args.range) for i in permissions: - permnet = ipaddress.ip_network(i['range']) - if argnetwork.version == permnet.version and \ - _supernet_of(argnetwork, ipaddress.ip_network(i['range'])): + permnet = ipaddress.ip_network(i["range"]) + if argnetwork.version == permnet.version and _supernet_of( + argnetwork, ipaddress.ip_network(i["range"]) + ): data.append(i) else: data = permissions @@ -56,34 +66,34 @@ def _supernet_of(a, b): # Add label names to the result labelnames = {} - info = get_list('/api/v1/labels/') + info = get_list("/api/v1/labels/") if info: for i in info: - labelnames[i['id']] = i['name'] + labelnames[i["id"]] = i["name"] for row in data: labels = [] - for j in row['labels']: + for j in row["labels"]: labels.append(labelnames[j]) - row['labels'] = ', '.join(labels) + row["labels"] = ", ".join(labels) headers = ("Range", "Group", "Regex", "Labels") - keys = ('range', 'group', 'regex', 'labels') + keys = ("range", "group", "regex", "labels") print_table(headers, keys, data) permission.add_command( - prog='network_list', - description='List permissions for networks', - short_desc='List permissions for networks', + prog="network_list", + description="List permissions for networks", + short_desc="List permissions for networks", callback=network_list, flags=[ - Flag('-group', - description='Group with access (supports wildcards)', - metavar='GROUP'), - Flag('-range', - description='Network range', - metavar='RANGE'), - ] + Flag( + "-group", + description="Group with access (supports wildcards)", + metavar="GROUP", + ), + Flag("-range", description="Network range", metavar="RANGE"), + ], ) ########################################## @@ -92,17 +102,14 @@ def _supernet_of(a, b): def network_add(args): - """ - Add permission for network - """ - + """Add permission for network.""" if not is_valid_network(args.range): - cli_warning(f'Invalid range: {args.range}') + cli_warning(f"Invalid range: {args.range}") data = { - 'range': args.range, - 'group': args.group, - 'regex': args.regex, + "range": args.range, + "group": args.group, + "regex": args.regex, } path = "/api/v1/permissions/netgroupregex/" history.record_post(path, "", data) @@ -111,21 +118,15 @@ def network_add(args): permission.add_command( - prog='network_add', - description='Add permission for network', - short_desc='Add permission for network', + prog="network_add", + description="Add permission for network", + short_desc="Add permission for network", callback=network_add, flags=[ - Flag('range', - description='Network range', - metavar='RANGE'), - Flag('group', - description='Group with access', - metavar='GROUP'), - Flag('regex', - description='Regular expression', - metavar='REGEX'), - ] + Flag("range", description="Network range", metavar="RANGE"), + Flag("group", description="Group with access", metavar="GROUP"), + Flag("regex", description="Regular expression", metavar="REGEX"), + ], ) @@ -133,11 +134,9 @@ def network_add(args): # Implementation of sub command 'remove' # ########################################## -def network_remove(args): - """ - Remove permission for networks - """ +def network_remove(args): + """Remove permission for networks.""" params = { "group": args.group, "range": args.range, @@ -150,29 +149,23 @@ def network_remove(args): return assert len(permissions) == 1, "Should only match one permission" - id = permissions[0]['id'] - path = f"/api/v1/permissions/netgroupregex/{id}" + identifier = permissions[0]["id"] + path = f"/api/v1/permissions/netgroupregex/{identifier}" history.record_delete(path, dict(), undoable=False) delete(path) cli_info(f"Removed permission for {args.range}", True) permission.add_command( - prog='network_remove', - description='Remove permission for network', - short_desc='Remove permission for network', + prog="network_remove", + description="Remove permission for network", + short_desc="Remove permission for network", callback=network_remove, flags=[ - Flag('range', - description='Network range', - metavar='RANGE'), - Flag('group', - description='Group with access', - metavar='GROUP'), - Flag('regex', - description='Regular expression', - metavar='REGEX'), - ] + Flag("range", description="Network range", metavar="RANGE"), + Flag("group", description="Group with access", metavar="GROUP"), + Flag("regex", description="Regular expression", metavar="REGEX"), + ], ) @@ -180,14 +173,14 @@ def network_remove(args): # Implementation of sub commands 'label_add' and 'label_remove' ################################################################# -def add_label_to_permission(args): - """Add a label to a permission triplet""" +def add_label_to_permission(args): + """Add a label to a permission triplet.""" # find the permission query = { - 'group': args.group, - 'range': args.range, - 'regex': args.regex, + "group": args.group, + "range": args.range, + "regex": args.regex, } permissions = get_list("/api/v1/permissions/netgroupregex/", params=query) @@ -196,20 +189,20 @@ def add_label_to_permission(args): return assert len(permissions) == 1, "Should only match one permission" - id = permissions[0]['id'] - path = f"/api/v1/permissions/netgroupregex/{id}" + identifier = permissions[0]["id"] + path = f"/api/v1/permissions/netgroupregex/{identifier}" # find the label - labelpath = f'/api/v1/labels/name/{args.label}' + labelpath = f"/api/v1/labels/name/{args.label}" res = get(labelpath, ok404=True) if not res: - cli_warning(f'Could not find a label with name {args.label!r}') + cli_warning(f"Could not find a label with name {args.label!r}") label = res.json() # check if the permission object already has the label perm = get(path).json() if label["id"] in perm["labels"]: - cli_warning(f'The permission already has the label {args.label!r}') + cli_warning(f"The permission already has the label {args.label!r}") # patch the permission ar = perm["labels"] @@ -217,32 +210,27 @@ def add_label_to_permission(args): patch(path, labels=ar) cli_info(f"Added the label {args.label!r} to the permission.", print_msg=True) + permission.add_command( - prog='label_add', - description='Add a label to a permission', + prog="label_add", + description="Add a label to a permission", callback=add_label_to_permission, flags=[ - Flag('range', - description='Network range', - metavar='RANGE'), - Flag('group', - description='Group with access', - metavar='GROUP'), - Flag('regex', - description='Regular expression', - metavar='REGEX'), - Flag('label', - description='The label you want to add') - ] + Flag("range", description="Network range", metavar="RANGE"), + Flag("group", description="Group with access", metavar="GROUP"), + Flag("regex", description="Regular expression", metavar="REGEX"), + Flag("label", description="The label you want to add"), + ], ) + def remove_label_from_permission(args): - """Remove a label from a permission""" + """Remove a label from a permission.""" # find the permission query = { - 'group': args.group, - 'range': args.range, - 'regex': args.regex, + "group": args.group, + "range": args.range, + "regex": args.regex, } permissions = get_list("/api/v1/permissions/netgroupregex/", params=query) @@ -251,42 +239,36 @@ def remove_label_from_permission(args): return assert len(permissions) == 1, "Should only match one permission" - id = permissions[0]['id'] - path = f"/api/v1/permissions/netgroupregex/{id}" + identifier = permissions[0]["id"] + path = f"/api/v1/permissions/netgroupregex/{identifier}" # find the label - labelpath = f'/api/v1/labels/name/{args.label}' + labelpath = f"/api/v1/labels/name/{args.label}" res = get(labelpath, ok404=True) if not res: - cli_warning(f'Could not find a label with name {args.label!r}') + cli_warning(f"Could not find a label with name {args.label!r}") label = res.json() # check if the permission object has the label perm = get(path).json() - if not label["id"] in perm["labels"]: + if label["id"] not in perm["labels"]: cli_warning(f"The permission doesn't have the label {args.label!r}") # patch the permission ar = perm["labels"] ar.remove(label["id"]) - patch(path, params={'labels':ar}, use_json=True) + patch(path, params={"labels": ar}, use_json=True) cli_info(f"Removed the label {args.label!r} from the permission.", print_msg=True) + permission.add_command( - prog='label_remove', - description='Remove a label from a permission', + prog="label_remove", + description="Remove a label from a permission", callback=remove_label_from_permission, flags=[ - Flag('range', - description='Network range', - metavar='RANGE'), - Flag('group', - description='Group with access', - metavar='GROUP'), - Flag('regex', - description='Regular expression', - metavar='REGEX'), - Flag('label', - description='The label you want to remove') - ] + Flag("range", description="Network range", metavar="RANGE"), + Flag("group", description="Group with access", metavar="GROUP"), + Flag("regex", description="Regular expression", metavar="REGEX"), + Flag("label", description="The label you want to remove"), + ], ) diff --git a/mreg_cli/policy.py b/mreg_cli/policy.py index 7c800662..e3a59e10 100644 --- a/mreg_cli/policy.py +++ b/mreg_cli/policy.py @@ -1,20 +1,26 @@ -from itertools import chain - from .cli import Flag, cli from .history import history from .history_log import get_history_items, print_history_items from .log import cli_error, cli_info, cli_warning -from .util import (convert_wildcard_to_regex, delete, get, get_list, host_info_by_name, - patch, post, print_table) +from .util import ( + convert_wildcard_to_regex, + delete, + get, + get_list, + host_info_by_name, + patch, + post, + print_table, +) ################################## # Add the main command 'policy' # ################################## policy = cli.add_command( - prog='policy', - description='Manage policies for hosts.', - short_desc='Manage policies', + prog="policy", + description="Manage policies for hosts.", + short_desc="Manage policies", ) # Utils @@ -27,7 +33,7 @@ def _get_atom(name): def get_atom(name): ret = _get_atom(name) if not ret: - cli_warning(f'Atom {name!r} does not exist') + cli_warning(f"Atom {name!r} does not exist") return ret[0] @@ -38,17 +44,18 @@ def _get_role(name): def get_role(name): ret = _get_role(name) if not ret: - cli_warning(f'Role {name!r} does not exist') + cli_warning(f"Role {name!r} does not exist") return ret[0] + def get_atom_or_role(name): atom = _get_atom(name) if atom: - return 'atom', atom[0] + return "atom", atom[0] role = _get_role(name) if role: - return 'role', role[0] - cli_warning(f'Could not find an atom or a role with name: {name!r}') + return "role", role[0] + cli_warning(f"Could not find an atom or a role with name: {name!r}") """ @@ -58,82 +65,66 @@ def get_atom_or_role(name): def atom_create(args): # .name .description - """ - Create a new atom - """ - + """Create a new atom.""" ret = _get_atom(args.name) if ret: cli_error(f'Atom "{args.name}" already in use') - data = { - 'name': args.name, - 'description': args.description - } + data = {"name": args.name, "description": args.description} if args.created: - data['create_date'] = args.created + data["create_date"] = args.created - path = '/api/v1/hostpolicy/atoms/' + path = "/api/v1/hostpolicy/atoms/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Created new atom {args.name}", print_msg=True) policy.add_command( - prog='atom_create', - description='Create a new atom', - short_desc='Create a new atom', + prog="atom_create", + description="Create a new atom", + short_desc="Create a new atom", callback=atom_create, flags=[ - Flag('name', - description='Atom name', - metavar='NAME'), - Flag('description', - description='Description', - metavar='DESCRIPTION'), - Flag('-created', - description='Created date', - metavar='CREATED'), - ] + Flag("name", description="Atom name", metavar="NAME"), + Flag("description", description="Description", metavar="DESCRIPTION"), + Flag("-created", description="Created date", metavar="CREATED"), + ], ) def atom_delete(args): # .name - """ - Delete an atom - """ - + """Delete an atom.""" get_atom(args.name) - info = get_list("/api/v1/hostpolicy/roles/", params={"atoms__name__exact": args.name}) - inuse = [i['name'] for i in info] + info = get_list( + "/api/v1/hostpolicy/roles/", params={"atoms__name__exact": args.name} + ) + inuse = [i["name"] for i in info] if inuse: - roles = ', '.join(inuse) - cli_error(f'Atom {args.name} used in roles: {roles}') + roles = ", ".join(inuse) + cli_error(f"Atom {args.name} used in roles: {roles}") - path = f'/api/v1/hostpolicy/atoms/{args.name}' + path = f"/api/v1/hostpolicy/atoms/{args.name}" history.record_delete(path, dict()) delete(path) cli_info(f"Deleted atom {args.name}", print_msg=True) policy.add_command( - prog='atom_delete', - description='Delete an atom', - short_desc='Delete an atom', + prog="atom_delete", + description="Delete an atom", + short_desc="Delete an atom", callback=atom_delete, flags=[ - Flag('name', - description='Atom name', - metavar='NAME'), - ] + Flag("name", description="Atom name", metavar="NAME"), + ], ) - """ Implementation of sub command 'role_create' """ @@ -141,145 +132,117 @@ def atom_delete(args): def role_create(args): # .role .description - """ - Create a new role - """ - + """Create a new role.""" ret = _get_role(args.name) if ret: - cli_error(f'Role name {args.name!r} already in use') + cli_error(f"Role name {args.name!r} already in use") - data = { - 'name': args.name, - 'description': args.description - } + data = {"name": args.name, "description": args.description} if args.created: - data['create_date'] = args.created + data["create_date"] = args.created - path = '/api/v1/hostpolicy/roles/' + path = "/api/v1/hostpolicy/roles/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Created new role {args.name!r}", print_msg=True) policy.add_command( - prog='role_create', - description='Create a new role', - short_desc='Create a new role', + prog="role_create", + description="Create a new role", + short_desc="Create a new role", callback=role_create, flags=[ - Flag('name', - description='Role name', - metavar='NAME'), - Flag('description', - description='Description', - metavar='DESCRIPTION'), - Flag('-created', - description='Created date', - metavar='CREATED'), - ] + Flag("name", description="Role name", metavar="NAME"), + Flag("description", description="Description", metavar="DESCRIPTION"), + Flag("-created", description="Created date", metavar="CREATED"), + ], ) def role_delete(args): # .name - """ - Delete a role - """ - + """Delete a role.""" info = get_role(args.name) - inuse = [i['name'] for i in info['hosts']] + inuse = [i["name"] for i in info["hosts"]] if inuse: - hosts = ', '.join(inuse) - cli_error(f'Role {args.name!r} used on hosts: {hosts}') + hosts = ", ".join(inuse) + cli_error(f"Role {args.name!r} used on hosts: {hosts}") - path = f'/api/v1/hostpolicy/roles/{args.name}' + path = f"/api/v1/hostpolicy/roles/{args.name}" history.record_delete(path, dict()) delete(path) cli_info(f"Deleted role {args.name!r}", print_msg=True) policy.add_command( - prog='role_delete', - description='Delete a role', - short_desc='Delete a role', + prog="role_delete", + description="Delete a role", + short_desc="Delete a role", callback=role_delete, flags=[ - Flag('name', - description='Role name', - metavar='NAME'), - ] + Flag("name", description="Role name", metavar="NAME"), + ], ) def add_atom(args): - """ - Make an atom member of a role - """ - + """Make an atom member of a role.""" info = get_role(args.role) - for atom in info['atoms']: - if args.atom == atom['name']: - cli_info(f"Atom {args.atom!r} already a member of role {args.role!r}", print_msg=True) + for atom in info["atoms"]: + if args.atom == atom["name"]: + cli_info( + f"Atom {args.atom!r} already a member of role {args.role!r}", + print_msg=True, + ) return get_atom(args.atom) - data = {'name': args.atom} - path = f'/api/v1/hostpolicy/roles/{args.role}/atoms/' + data = {"name": args.atom} + path = f"/api/v1/hostpolicy/roles/{args.role}/atoms/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Added atom {args.atom!r} to role {args.role!r}", print_msg=True) policy.add_command( - prog='add_atom', - description='Make an atom member of a role', - short_desc='Make an atom member of a role', + prog="add_atom", + description="Make an atom member of a role", + short_desc="Make an atom member of a role", callback=add_atom, flags=[ - Flag('role', - description='Role name', - metavar='ROLE'), - Flag('atom', - description='Atom name', - metavar='ATOM'), - ] + Flag("role", description="Role name", metavar="ROLE"), + Flag("atom", description="Atom name", metavar="ATOM"), + ], ) def remove_atom(args): - """ - Remove an atom member from a role - """ - + """Remove an atom member from a role.""" info = get_role(args.role) - for atom in info['atoms']: - if args.atom == atom['name']: + for atom in info["atoms"]: + if args.atom == atom["name"]: break else: cli_warning(f"Atom {args.atom!r} not a member of {args.role!r}") - path = f'/api/v1/hostpolicy/roles/{args.role}/atoms/{args.atom}' + path = f"/api/v1/hostpolicy/roles/{args.role}/atoms/{args.atom}" history.record_delete(path, dict()) delete(path) cli_info(f"Removed atom {args.atom!r} from role {args.role!r}", print_msg=True) policy.add_command( - prog='remove_atom', - description='Remove an atom member from a role', - short_desc='Remove an atom member from a role', + prog="remove_atom", + description="Remove an atom member from a role", + short_desc="Remove an atom member from a role", callback=remove_atom, flags=[ - Flag('role', - description='Role name', - metavar='ROLE'), - Flag('atom', - description='Atom name', - metavar='ATOM'), - ] + Flag("role", description="Role name", metavar="ROLE"), + Flag("atom", description="Atom name", metavar="ATOM"), + ], ) @@ -287,265 +250,240 @@ def remove_atom(args): # Implementation of sub command 'info' # ######################################## + def info(args): - """ - Show info about an atom or role - """ + """Show info about an atom or role.""" def _print(key, value, padding=14): print("{1:<{0}} {2}".format(padding, key, value)) for name in args.name: policy, info = get_atom_or_role(name) - _print('Name:', info['name']) - _print('Created:', info['create_date']) - _print('Description:', info['description']) + _print("Name:", info["name"]) + _print("Created:", info["create_date"]) + _print("Description:", info["description"]) - if policy == 'atom': + if policy == "atom": print("Roles where this atom is a member:") - if info['roles']: - for i in info['roles']: - _print('', i['name']) + if info["roles"]: + for i in info["roles"]: + _print("", i["name"]) else: - print('None') + print("None") else: print("Atom members:") - if info['atoms']: - for i in info['atoms']: - _print('', i['name']) + if info["atoms"]: + for i in info["atoms"]: + _print("", i["name"]) else: - _print('', 'None') + _print("", "None") print("Labels:") - for i in info['labels']: + for i in info["labels"]: lb = get(f"/api/v1/labels/{i}").json() - _print('', lb["name"]) - if not info['labels']: - _print('', 'None') + _print("", lb["name"]) + if not info["labels"]: + _print("", "None") policy.add_command( - prog='info', - description='Show info about an atom or role', - short_desc='atom/role info', + prog="info", + description="Show info about an atom or role", + short_desc="atom/role info", callback=info, flags=[ - Flag('name', - description='atom/role name', - nargs='+', - metavar='NAME'), - ] + Flag("name", description="atom/role name", nargs="+", metavar="NAME"), + ], ) def atom_history(args): - """Show history for name""" - items = get_history_items(args.name, 'hostpolicy_atom', data_relation='atoms') + """Show history for name.""" + items = get_history_items(args.name, "hostpolicy_atom", data_relation="atoms") print_history_items(args.name, items) policy.add_command( - prog='atom_history', - description='Show history for atom name', - short_desc='Show history for atom name', + prog="atom_history", + description="Show history for atom name", + short_desc="Show history for atom name", callback=atom_history, flags=[ - Flag('name', - description='Host name', - metavar='NAME'), + Flag("name", description="Host name", metavar="NAME"), ], ) def role_history(args): - """Show history for name""" - items = get_history_items(args.name, 'hostpolicy_role', data_relation='roles') + """Show history for name.""" + items = get_history_items(args.name, "hostpolicy_role", data_relation="roles") print_history_items(args.name, items) policy.add_command( - prog='role_history', - description='Show history for role name', - short_desc='Show history for role name', + prog="role_history", + description="Show history for role name", + short_desc="Show history for role name", callback=role_history, flags=[ - Flag('name', - description='Host name', - metavar='NAME'), + Flag("name", description="Host name", metavar="NAME"), ], ) def list_atoms(args): - """ - List all atoms by given filters - """ + """List all atoms by given filters.""" def _print(key, value, padding=20): print("{1:<{0}} {2}".format(padding, key, value)) params = {} - param, value = convert_wildcard_to_regex('name', args.name, True) + param, value = convert_wildcard_to_regex("name", args.name, True) params[param] = value info = get_list("/api/v1/hostpolicy/atoms/", params=params) if info: for i in info: - _print(i['name'], repr(i['description'])) + _print(i["name"], repr(i["description"])) else: - print('No match') + print("No match") policy.add_command( - prog='list_atoms', - description='List all atoms by given filters', - short_desc='List all atoms by given filters', + prog="list_atoms", + description="List all atoms by given filters", + short_desc="List all atoms by given filters", callback=list_atoms, flags=[ - Flag('name', - description='Atom name, or part of name. You can use * as a wildcard.', - metavar='FILTER'), - ] + Flag( + "name", + description="Atom name, or part of name. You can use * as a wildcard.", + metavar="FILTER", + ), + ], ) def list_roles(args): - """ - List all roles by given filters - """ - + """List all roles by given filters.""" params = {} - param, value = convert_wildcard_to_regex('name', args.name, True) + param, value = convert_wildcard_to_regex("name", args.name, True) params[param] = value info = get_list("/api/v1/hostpolicy/roles/", params=params) if not info: - print('No match') + print("No match") return labelnames = {} - labellist = get_list(f'/api/v1/labels/') + labellist = get_list("/api/v1/labels/") if labellist: for i in labellist: - labelnames[i['id']] = i['name'] + labelnames[i["id"]] = i["name"] rows = [] for i in info: # show label names instead of id numbers labels = [] - for j in i['labels']: + for j in i["labels"]: labels.append(labelnames[j]) - i['labels'] = ', '.join(labels) + i["labels"] = ", ".join(labels) rows.append(i) - print_table( ('Role','Description','Labels'), ('name','description','labels'), rows) + print_table( + ("Role", "Description", "Labels"), ("name", "description", "labels"), rows + ) policy.add_command( - prog='list_roles', - description='List all roles by given filters', - short_desc='List all roles by given filters', + prog="list_roles", + description="List all roles by given filters", + short_desc="List all roles by given filters", callback=list_roles, flags=[ - Flag('name', - description='Role name, or part of name. You can use * as a wildcard.', - metavar='FILTER'), - ] + Flag( + "name", + description="Role name, or part of name. You can use * as a wildcard.", + metavar="FILTER", + ), + ], ) def list_hosts(args): - """ - List hosts which use the given role - """ + """List hosts which use the given role.""" info = get_role(args.name) - if info['hosts']: - print('Name:') - for i in info['hosts']: - print(" " + i['name']) + if info["hosts"]: + print("Name:") + for i in info["hosts"]: + print(" " + i["name"]) else: - print('No host uses this role') + print("No host uses this role") policy.add_command( - prog='list_hosts', - description='List hosts which use the given role', - short_desc='List hosts which use the given role', + prog="list_hosts", + description="List hosts which use the given role", + short_desc="List hosts which use the given role", callback=list_hosts, flags=[ - Flag('name', - description='Role name', - metavar='NAME'), - ] + Flag("name", description="Role name", metavar="NAME"), + ], ) def list_members(args): - """ - List atom members for a role - """ + """List atom members for a role.""" info = get_role(args.name) - if info['atoms']: - print('Name:') - for i in info['atoms']: - print(" " + i['name']) + if info["atoms"]: + print("Name:") + for i in info["atoms"]: + print(" " + i["name"]) else: - print('No atom members') + print("No atom members") policy.add_command( - prog='list_members', - description='List all members of a role', - short_desc='List role members', + prog="list_members", + description="List all members of a role", + short_desc="List role members", callback=list_members, flags=[ - Flag('name', - description='Role name', - metavar='NAME'), - ] + Flag("name", description="Role name", metavar="NAME"), + ], ) def host_add(args): - """ - Add host(s) to role - """ - + """Add host(s) to role.""" get_role(args.role) info = [] for name in args.hosts: info.append(host_info_by_name(name, follow_cname=False)) for i in info: - name = i['name'] + name = i["name"] data = { - 'name': name, + "name": name, } - path = f'/api/v1/hostpolicy/roles/{args.role}/hosts/' + path = f"/api/v1/hostpolicy/roles/{args.role}/hosts/" history.record_post(path, "", data, undoable=False) post(path, **data) cli_info(f"Added host '{name}' to role '{args.role}'", print_msg=True) policy.add_command( - prog='host_add', - description='Add host(s) to role', - short_desc='Add host(s) to role', + prog="host_add", + description="Add host(s) to role", + short_desc="Add host(s) to role", callback=host_add, flags=[ - Flag('role', - description='role', - metavar='ROLE'), - Flag('hosts', - description='hosts', - nargs='+', - metavar='HOST'), - ] + Flag("role", description="role", metavar="ROLE"), + Flag("hosts", description="hosts", nargs="+", metavar="HOST"), + ], ) def host_list(args): - """ - List host roles - """ + """List host roles.""" def _print(hostname, roleinfo): if not roleinfo: @@ -560,7 +498,7 @@ def _print(hostname, roleinfo): info.append(host_info_by_name(name)) for i in info: - name = i['name'] + name = i["name"] path = "/api/v1/hostpolicy/roles/" params = { "hosts__name": name, @@ -569,80 +507,64 @@ def _print(hostname, roleinfo): policy.add_command( - prog='host_list', - description='List roles for host(s)', - short_desc='List roles for host(s)', + prog="host_list", + description="List roles for host(s)", + short_desc="List roles for host(s)", callback=host_list, flags=[ - Flag('hosts', - description='hosts', - nargs='+', - metavar='HOST'), - ] + Flag("hosts", description="hosts", nargs="+", metavar="HOST"), + ], ) def host_remove(args): - """ - Remove host(s) from role - """ - + """Remove host(s) from role.""" get_role(args.role) info = [] for name in args.hosts: info.append(host_info_by_name(name, follow_cname=False)) for i in info: - name = i['name'] - path = f'/api/v1/hostpolicy/roles/{args.role}/hosts/{name}' + name = i["name"] + path = f"/api/v1/hostpolicy/roles/{args.role}/hosts/{name}" history.record_delete(path, dict()) delete(path) cli_info(f"Removed host '{name}' from role '{args.role}'", print_msg=True) policy.add_command( - prog='host_remove', - description='Remove host(s) from role', - short_desc='Remove host(s) from role', + prog="host_remove", + description="Remove host(s) from role", + short_desc="Remove host(s) from role", callback=host_remove, flags=[ - Flag('role', - description='role', - metavar='ROLE'), - Flag('hosts', - description='host', - nargs='+', - metavar='HOST'), - ] + Flag("role", description="role", metavar="ROLE"), + Flag("hosts", description="host", nargs="+", metavar="HOST"), + ], ) def rename(args): - """Rename an atom/role - """ + """Rename an atom/role.""" if _get_atom(args.oldname): - path = f'/api/v1/hostpolicy/atoms/{args.oldname}' + path = f"/api/v1/hostpolicy/atoms/{args.oldname}" elif _get_role(args.oldname): - path = f'/api/v1/hostpolicy/roles/{args.oldname}' + path = f"/api/v1/hostpolicy/roles/{args.oldname}" else: - cli_warning('Could not find an atom or role with name {args.name!r}') + cli_warning("Could not find an atom or role with name {args.name!r}") patch(path, name=args.newname) cli_info(f"Renamed {args.oldname!r} to {args.newname!r}", True) policy.add_command( - prog='rename', - description='Rename an atom or role', - short_desc='Rename an atom or role', + prog="rename", + description="Rename an atom or role", + short_desc="Rename an atom or role", callback=rename, flags=[ - Flag('oldname', - description='Existing name', - metavar='OLDNAME'), - Flag('newname', - description='New name', - metavar='NEWNAME'), - ] + Flag("oldname", description="Existing name", metavar="OLDNAME"), + Flag("newname", description="New name", metavar="NEWNAME"), + ], ) ################################################### @@ -651,31 +573,28 @@ def rename(args): def set_description(args): - """Set description for atom/role - """ + """Set description for atom/role.""" if _get_atom(args.name): - path = f'/api/v1/hostpolicy/atoms/{args.name}' + path = f"/api/v1/hostpolicy/atoms/{args.name}" elif _get_role(args.name): - path = f'/api/v1/hostpolicy/roles/{args.name}' + path = f"/api/v1/hostpolicy/roles/{args.name}" else: - cli_warning('Could not find an atom or role with name {args.name!r}') + cli_warning("Could not find an atom or role with name {args.name!r}") patch(path, description=args.description) - cli_info(f"updated description to {args.description!r} for {args.name!r}", print_msg=True) + cli_info( + f"updated description to {args.description!r} for {args.name!r}", print_msg=True + ) policy.add_command( - prog='set_description', - description='Set description for an atom or role', - short_desc='Set description for an atom or role', + prog="set_description", + description="Set description for an atom or role", + short_desc="Set description for an atom or role", callback=set_description, flags=[ - Flag('name', - description='Name', - metavar='NAME'), - Flag('description', - description='Description.', - metavar='DESC'), - ] + Flag("name", description="Name", metavar="NAME"), + Flag("description", description="Description.", metavar="DESC"), + ], ) @@ -683,52 +602,54 @@ def set_description(args): # Implementation of sub commands 'label_add' and 'label_remove' ################################################################# + def add_label_to_role(args): - """Add a label to a role""" + """Add a label to a role.""" # find the role - path = f'/api/v1/hostpolicy/roles/{args.role}' + path = f"/api/v1/hostpolicy/roles/{args.role}" res = get(path, ok404=True) if not res: - cli_warning(f'Could not find a role with name {args.role!r}') + cli_warning(f"Could not find a role with name {args.role!r}") role = res.json() # find the label - labelpath = f'/api/v1/labels/name/{args.label}' + labelpath = f"/api/v1/labels/name/{args.label}" res = get(labelpath, ok404=True) if not res: - cli_warning(f'Could not find a label with name {args.label!r}') + cli_warning(f"Could not find a label with name {args.label!r}") label = res.json() # check if the role already has the label if label["id"] in role["labels"]: - cli_warning(f'The role {args.role!r} already has the label {args.label!r}') + cli_warning(f"The role {args.role!r} already has the label {args.label!r}") # patch the role ar = role["labels"] ar.append(label["id"]) patch(path, labels=ar) - cli_info(f"Added the label {args.label!r} to the role {args.role!r}.", print_msg=True) + cli_info( + f"Added the label {args.label!r} to the role {args.role!r}.", print_msg=True + ) + policy.add_command( - prog='label_add', - description='Add a label to a role', + prog="label_add", + description="Add a label to a role", callback=add_label_to_role, - flags=[ - Flag('label'), - Flag('role') - ] + flags=[Flag("label"), Flag("role")], ) + def remove_label_from_role(args): - """Remove a label from a role""" + """Remove a label from a role.""" # find the role - path = f'/api/v1/hostpolicy/roles/{args.role}' + path = f"/api/v1/hostpolicy/roles/{args.role}" res = get(path, ok404=True) if not res: - cli_warning(f'Could not find a role with name {args.role!r}') + cli_warning(f"Could not find a role with name {args.role!r}") role = res.json() # find the label - labelpath = f'/api/v1/labels/name/{args.label}' + labelpath = f"/api/v1/labels/name/{args.label}" res = get(labelpath, ok404=True) if not res: - cli_warning(f'Could not find a label with name {args.label!r}') + cli_warning(f"Could not find a label with name {args.label!r}") label = res.json() # check if the role has the label if label["id"] not in role["labels"]: @@ -736,15 +657,15 @@ def remove_label_from_role(args): # patch the role ar = role["labels"] ar.remove(label["id"]) - patch(path, use_json=True, params={'labels':ar}) - cli_info(f"Removed the label {args.label!r} from the role {args.role!r}.", print_msg=True) + patch(path, use_json=True, params={"labels": ar}) + cli_info( + f"Removed the label {args.label!r} from the role {args.role!r}.", print_msg=True + ) + policy.add_command( - prog='label_remove', - description='Remove a label from a role', + prog="label_remove", + description="Remove a label from a role", callback=remove_label_from_role, - flags=[ - Flag('label'), - Flag('role') - ] + flags=[Flag("label"), Flag("role")], ) diff --git a/mreg_cli/recorder.py b/mreg_cli/recorder.py index ae4a249e..5eca8838 100644 --- a/mreg_cli/recorder.py +++ b/mreg_cli/recorder.py @@ -1,11 +1,13 @@ +import atexit import json import os -from urllib.parse import urlparse, urlencode -import atexit +from typing import Any, Dict +from urllib.parse import urlencode, urlparse + import requests -from typing import Dict, Any -def remove_dict_key_recursive(obj, key:str) -> None: + +def remove_dict_key_recursive(obj, key: str) -> None: if isinstance(obj, list): for elem in obj: remove_dict_key_recursive(elem, key) @@ -18,10 +20,11 @@ def remove_dict_key_recursive(obj, key:str) -> None: for other_value in obj.values(): remove_dict_key_recursive(other_value, key) -class Recorder: +class Recorder: # Singleton __instance = None + def __new__(cls): if Recorder.__instance is None: i = Recorder.__instance = object.__new__(cls) @@ -35,7 +38,9 @@ def __getattr__(self, name): if self != Recorder.__instance: return getattr(Recorder.__instance, name) else: - raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name)) + raise AttributeError( + "%r object has no attribute %r" % (self.__class__.__name__, name) + ) def save_recording(self) -> None: i = Recorder.__instance @@ -45,6 +50,7 @@ def save_recording(self) -> None: """ Start recording http traffic, commands and console output to the given filename. Warning! If the file exists, it will be deleted/overwritten. """ + def start_recording(self, filename: str) -> None: i = Recorder.__instance i.recording = True @@ -52,59 +58,80 @@ def start_recording(self, filename: str) -> None: atexit.register(Recorder.save_recording, self) try: os.remove(filename) - except: + except OSError: pass def is_recording(self) -> bool: return Recorder.__instance.recording - def record_command(self, cmd:str) -> None: + def record_command(self, cmd: str) -> None: if not self.is_recording(): return # trim spaces, remove comments cmd = cmd.lstrip() - if cmd.find("#")>-1: - cmd = cmd[0:cmd.find("#")].rstrip() - # don't log empty commands - if cmd == '': # Compare to empty string to avoid being tripped up by strings having false-like values (0, False, etc) + if cmd.find("#") > -1: + cmd = cmd[0 : cmd.find("#")].rstrip() + # Don't log empty commands. Compare to empty string to avoid being tripped + # up by strings having false-like values (0, False, etc) + if cmd == "": return - x = {'command':cmd} + x = {"command": cmd} Recorder.__instance.recorded_data.append(x) - def record_output(self, output:str) -> None: + def record_output(self, output: str) -> None: if not self.is_recording(): return - x = {'output':output} + x = {"output": output} Recorder.__instance.recorded_data.append(x) """ Returns only the path + query string components of a url """ - def urlpath(self, url:str, params:str) -> str: + + def urlpath(self, url: str, params: str) -> str: if params: url = f"{url}?{urlencode(params)}" up = urlparse(url) - if up.query != '': # Compare to empty string to avoid being tripped up by strings having false-like values (0, False, etc) - return up.path + '?' + up.query + # Compare to empty string to avoid being tripped up by strings having + # false-like values (0, False, etc) + if up.query != "": + return up.path + "?" + up.query else: return up.path """ Records an http call (method, url and postdata) and the response. """ - def record(self, method: str, url: str, params: str, data: Dict[str, Any], result: requests.Response) -> None: + + def record( + self, + method: str, + url: str, + params: str, + data: Dict[str, Any], + result: requests.Response, + ) -> None: if not self.is_recording(): return x = { - 'method': method.upper(), - 'url': self.urlpath(url, params), - 'data': data, - 'status': result.status_code + "method": method.upper(), + "url": self.urlpath(url, params), + "data": data, + "status": result.status_code, } try: obj = result.json() - keys_to_remove = ['id','created_at','updated_at','serialno','serialno_updated_at','create_date'] + keys_to_remove = [ + "id", + "created_at", + "updated_at", + "serialno", + "serialno_updated_at", + "create_date", + ] for key in keys_to_remove: remove_dict_key_recursive(obj, key) - x['response'] = obj + x["response"] = obj except requests.JSONDecodeError: - s = result.content.decode('utf-8').strip() - if s != "": # Compare to empty string to avoid being tripped up by strings having false-like values (0, False, etc) - x['response'] = s + s = result.content.decode("utf-8").strip() + # Compare to empty string to avoid being tripped up by strings having + # false-like values (0, False, etc) + if s != "": + x["response"] = s Recorder.__instance.recorded_data.append(x) diff --git a/mreg_cli/types.py b/mreg_cli/types.py index 6db953aa..66ad478b 100644 --- a/mreg_cli/types.py +++ b/mreg_cli/types.py @@ -2,6 +2,7 @@ if TYPE_CHECKING: from typing import Any + from typing_extensions import Protocol class ResponseLike(Protocol): diff --git a/mreg_cli/util.py b/mreg_cli/util.py index 3204f460..16266f1a 100644 --- a/mreg_cli/util.py +++ b/mreg_cli/util.py @@ -5,43 +5,42 @@ import re import sys from typing import ( + TYPE_CHECKING, Any, Dict, Iterable, + List, NoReturn, Optional, - List, Sequence, Tuple, Union, - overload, cast, - TYPE_CHECKING, + overload, ) if TYPE_CHECKING: - from .types import ResponseLike from typing_extensions import Literal + from .types import ResponseLike + import urllib.parse import requests - from prompt_toolkit import prompt +from . import recorder from .exceptions import CliError, HostNotFoundWarning from .history import history from .log import cli_error, cli_warning -from . import recorder - location_tags = [] # type: List[str] category_tags = [] # type: List[str] session = requests.Session() -session.headers.update({'User-Agent': 'mreg-cli'}) +session.headers.update({"User-Agent": "mreg-cli"}) -mreg_auth_token_file = os.path.join(str(os.getenv('HOME')), '.mreg-cli_auth_token') +mreg_auth_token_file = os.path.join(str(os.getenv("HOME")), ".mreg-cli_auth_token") logger = logging.getLogger(__name__) @@ -61,7 +60,7 @@ def set_config(cfg: dict) -> None: def host_exists(name: str) -> bool: - """Checks if a host with the given name exists""" + """Checks if a host with the given name exists.""" path = "/api/v1/hosts/" params = { "name": name, @@ -71,20 +70,23 @@ def host_exists(name: str) -> bool: # Response data sanity checks if len(hosts) > 1: - cli_error("host exist check received more than one exact match for \"{}\"".format(name)) + cli_error( + 'host exist check received more than one exact match for "{}"'.format(name) + ) if len(hosts) == 0: return False if hosts[0]["name"] != name: - cli_error("host exist check received from API \"{}\" when searched for \"{}\"".format( - hosts[0]["name"], - name, - )) + cli_error( + 'host exist check received from API "{}" when searched for "{}"'.format( + hosts[0]["name"], + name, + ) + ) return True def host_info_by_name_or_ip(name_or_ip: str) -> dict: - """ - Return a dict with host information about the given host, or the host owning the given ip. + """Return a dict with host information about the given host, or the host owning the given ip. :param name_or_ip: Either a host name on short or long form or an ipv4/ipv6 address. :return: A dict of the JSON object received with the host information @@ -104,9 +106,7 @@ def _host_info_by_name(name: str, follow_cname: bool = True) -> Optional[dict]: elif follow_cname: # All host info data is returned from the API path = "/api/v1/hosts/" - params = { - "cnames__name": name - } + params = {"cnames__name": name} history.record_get(path) hosts = get_list(path, params=params) if len(hosts) == 1: @@ -114,17 +114,14 @@ def _host_info_by_name(name: str, follow_cname: bool = True) -> Optional[dict]: return None - def host_info_by_name(name: str, follow_cname: bool = True) -> dict: - """ - Return a dict with host information about the given host. + """Return a dict with host information about the given host. :param name: A host name on either short or long form. :param follow_cname: Indicate whether or not to check if name is a cname. If True (default) if will attempt to get the host via the cname. :return: A dict of the JSON object received with the host information """ - # Get longform of name name = clean_hostname(name) hostinfo = _host_info_by_name(name, follow_cname=follow_cname) @@ -157,10 +154,7 @@ def _srv_info_by_name(name: str) -> Optional[dict]: def get_info_by_name(name: str) -> Tuple[str, dict]: - """ - Get host, cname or srv by name. - """ - + """Get host, cname or srv by name.""" name = clean_hostname(name) info = _host_info_by_name(name, follow_cname=False) if info is not None: @@ -175,21 +169,21 @@ def get_info_by_name(name: str) -> Tuple[str, dict]: def first_unused_ip_from_network(network: dict) -> str: - """ - Returns the first unused ip from a given network. + """Returns the first unused ip from a given network. Assumes network exists. :param network: dict with network info. - :return: Ip address string + :return: Ip address string. """ - - unused = get_network_first_unused(network['network']) + unused = get_network_first_unused(network["network"]) if not unused: - cli_warning("No free addresses remaining on network {}".format(network['network'])) + cli_warning( + "No free addresses remaining on network {}".format(network["network"]) + ) return unused def ip_in_mreg_net(ip: str) -> bool: - """Return true if the ip is in a MREG controlled network""" + """Return true if the ip is in a MREG controlled network.""" net = get_network_by_ip(ip) return bool(net) @@ -217,8 +211,8 @@ def login1(user: str, url: str) -> None: if os.path.isfile(mreg_auth_token_file): try: - with open(mreg_auth_token_file, encoding='utf-8') as tokenfile: - tokenuser, token = tokenfile.readline().split('¤') + with open(mreg_auth_token_file, encoding="utf-8") as tokenfile: + tokenuser, token = tokenfile.readline().split("¤") if tokenuser == user: session.headers.update({"Authorization": f"Token {token}"}) except PermissionError: @@ -232,10 +226,12 @@ def login1(user: str, url: str) -> None: # Find a better URL.. but so far so good try: - ret = session.get(requests.compat.urljoin(mregurl, "/api/v1/hosts/"), - params={"page_size": 1}, - timeout=5) - except requests.exceptions.ConnectionError as e: + ret = session.get( + requests.compat.urljoin(mregurl, "/api/v1/hosts/"), + params={"page_size": 1}, + timeout=5, + ) + except requests.exceptions.ConnectionError: error(f"Could not connect to {url}") if ret.status_code == 401: @@ -254,7 +250,7 @@ def login(user: str, url: str) -> None: def logout() -> None: - path = requests.compat.urljoin(mregurl, '/api/token-logout/') + path = requests.compat.urljoin(mregurl, "/api/token-logout/") # Try to logout, and ignore errors try: session.post(path) @@ -263,8 +259,7 @@ def logout() -> None: def update_token() -> None: - password = prompt("You need to re-autenticate\nEnter password: ", - is_password=True) + password = prompt("You need to re-autenticate\nEnter password: ", is_password=True) try: _update_token(username, password) except CliError as e: @@ -272,10 +267,9 @@ def update_token() -> None: def _update_token(username: str, password: str) -> None: - tokenurl = requests.compat.urljoin(mregurl, '/api/token-auth/') + tokenurl = requests.compat.urljoin(mregurl, "/api/token-auth/") try: - result = requests.post(tokenurl, {'username': username, - 'password': password}) + result = requests.post(tokenurl, {"username": username, "password": password}) except requests.exceptions.ConnectionError as err: error(err) except requests.exceptions.SSLError as e: @@ -283,18 +277,18 @@ def _update_token(username: str, password: str) -> None: if not result.ok: try: res = result.json() - except: + except json.JSONDecodeError: res = result.text if result.status_code == 400: - if 'non_field_errors' in res: + if "non_field_errors" in res: cli_error("Invalid username/password") else: cli_error(res) - token = result.json()['token'] + token = result.json()["token"] session.headers.update({"Authorization": f"Token {token}"}) try: - with open(mreg_auth_token_file, 'w', encoding='utf-8') as tokenfile: - tokenfile.write(f'{username}¤{token}') + with open(mreg_auth_token_file, "w", encoding="utf-8") as tokenfile: + tokenfile.write(f"{username}¤{token}") except FileNotFoundError: pass except PermissionError: @@ -302,9 +296,9 @@ def _update_token(username: str, password: str) -> None: set_file_permissions(mreg_auth_token_file, 0o600) -def result_check(result: "ResponseLike", type: str, url: str) -> None: +def result_check(result: "ResponseLike", operation_type: str, url: str) -> None: if not result.ok: - message = f"{type} \"{url}\": {result.status_code}: {result.reason}" + message = f'{operation_type} "{url}": {result.status_code}: {result.reason}' try: body = result.json() except ValueError: @@ -315,37 +309,39 @@ def result_check(result: "ResponseLike", type: str, url: str) -> None: def _request_wrapper( - type: str, + operation_type: str, path: str, - params: Dict[str, str] = {}, + params: Dict[str, str] = None, ok404: bool = False, first: bool = True, use_json: bool = False, **data, ) -> Optional["ResponseLike"]: + if params is None: + params = {} url = requests.compat.urljoin(mregurl, path) rec = recorder.Recorder() if use_json: - result = getattr(session, type)(url, json=params, timeout=HTTP_TIMEOUT) + result = getattr(session, operation_type)( + url, json=params, timeout=HTTP_TIMEOUT + ) else: - result = getattr(session, type)( + result = getattr(session, operation_type)( url, params=params, data=data, timeout=HTTP_TIMEOUT ) - result = cast( - requests.Response, result - ) # convince mypy that result is a Response + result = cast(requests.Response, result) # convince mypy that result is a Response if rec.is_recording(): - rec.record(type, url, params, data, result) + rec.record(operation_type, url, params, data, result) if first and result.status_code == 401: update_token() - return _request_wrapper(type, path, first=False, **data) + return _request_wrapper(operation_type, path, first=False, **data) elif result.status_code == 404 and ok404: return None - result_check(result, type.upper(), url) + result_check(result, operation_type.upper(), url) return result @@ -374,15 +370,22 @@ def get(path: str, params: Dict[str, str] = ...) -> "ResponseLike": def get( - path: str, params: Dict[str, str] = {}, ok404: bool = False + path: str, params: Dict[str, str] = None, ok404: bool = False ) -> Optional["ResponseLike"]: """Uses requests to make a get request.""" + if params is None: + params = {} return _request_wrapper("get", path, params=params, ok404=ok404) -def get_list(path: Optional[str], params: dict = {}, ok404: bool = False) -> List[dict]: +def get_list( + path: Optional[str], params: dict = None, ok404: bool = False +) -> List[dict]: """Uses requests to make a get request. - Will iterate over paginated results and return result as list.""" + Will iterate over paginated results and return result as list. + """ + if params is None: + params = {} ret = [] # type: List[dict] while path: resp = get(path, params=params, ok404=ok404) @@ -398,20 +401,26 @@ def get_list(path: Optional[str], params: dict = {}, ok404: bool = False) -> Lis return ret -def post(path: str, params: dict = {}, **kwargs: Any) -> Optional["ResponseLike"]: - """Uses requests to make a post request. Assumes that all kwargs are data fields""" +def post(path: str, params: dict = None, **kwargs: Any) -> Optional["ResponseLike"]: + """Uses requests to make a post request. Assumes that all kwargs are data fields.""" + if params is None: + params = {} return _request_wrapper("post", path, params=params, **kwargs) def patch( - path: str, params: dict = {}, use_json: bool = False, **kwargs: Any + path: str, params: dict = None, use_json: bool = False, **kwargs: Any ) -> Optional["ResponseLike"]: - """Uses requests to make a patch request. Assumes that all kwargs are data fields""" + """Uses requests to make a patch request. Assumes that all kwargs are data fields.""" + if params is None: + params = {} return _request_wrapper("patch", path, params=params, use_json=use_json, **kwargs) -def delete(path: str, params: dict = {}) -> Optional["ResponseLike"]: +def delete(path: str, params: dict = None) -> Optional["ResponseLike"]: """Uses requests to make a delete request.""" + if params is None: + params = {} return _request_wrapper("delete", path, params=params) @@ -421,8 +430,9 @@ def delete(path: str, params: dict = {}) -> Optional["ResponseLike"]: # # ################################################################################ + def cname_exists(cname: str) -> bool: - """Check if a cname exists""" + """Check if a cname exists.""" if len(get_list("/api/v1/cnames/", params={"name": cname})): return True else: @@ -435,6 +445,7 @@ def cname_exists(cname: str) -> bool: # # ################################################################################ + def resolve_name_or_ip(name_or_ip: str) -> str: """Tries to find a host from the given name/ip. Raises an exception if not.""" if is_valid_ip(name_or_ip): @@ -444,7 +455,7 @@ def resolve_name_or_ip(name_or_ip: str) -> str: def resolve_ip(ip: str) -> str: - """Returns host name associated with ip""" + """Returns host name associated with ip.""" path = "/api/v1/hosts/" params = { "ipaddresses__ipaddress": ip, @@ -454,10 +465,12 @@ def resolve_ip(ip: str) -> str: # Response data sanity check if len(hosts) > 1: - cli_error("resolve ip got multiple matches for ip \"{}\"".format(ip)) + cli_error('resolve ip got multiple matches for ip "{}"'.format(ip)) if len(hosts) == 0: - cli_warning("{} doesnt belong to any host".format(ip), exception=HostNotFoundWarning) + cli_warning( + "{} doesnt belong to any host".format(ip), exception=HostNotFoundWarning + ) return hosts[0]["name"] @@ -486,7 +499,7 @@ def resolve_input_name(name: str) -> str: def clean_hostname(name: Union[str, bytes]) -> str: - """ Converts from short to long hostname, if no domain found. """ + """Converts from short to long hostname, if no domain found.""" # bytes? if not isinstance(name, (str, bytes)): cli_warning("Invalid input for hostname: {}".format(name)) @@ -505,14 +518,15 @@ def clean_hostname(name: Union[str, bytes]) -> str: return name[:-1] # If a dot in name, assume long name. - if '.' in name: + if "." in name: return name # Append domain name if in config and it does not end with it - if 'domain' in config and not name.endswith(config['domain']): - return "{}.{}".format(name, config['domain']) + if "domain" in config and not name.endswith(config["domain"]): + return "{}.{}".format(name, config["domain"]) return name + ################################################################################ # # # Network utility # @@ -537,7 +551,7 @@ def get_network_by_ip(ip: str) -> Dict[str, Any]: def get_network(ip: str) -> Dict[str, Any]: - "Returns network associated with given range or IP" + "Returns network associated with given range or IP." if is_valid_network(ip): path = f"/api/v1/networks/{urllib.parse.quote(ip)}" history.record_get(path) @@ -552,42 +566,42 @@ def get_network(ip: str) -> Dict[str, Any]: def get_network_used_count(ip_range: str) -> int: - "Return a count of the addresses in use on a given network" + "Return a count of the addresses in use on a given network." path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}/used_count" history.record_get(path) return get(path).json() def get_network_used_list(ip_range: str) -> List[str]: - "Return a list of the addresses in use on a given network" + "Return a list of the addresses in use on a given network." path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}/used_list" history.record_get(path) return get(path).json() def get_network_unused_count(ip_range: str) -> int: - "Return a count of the unused addresses on a given network" + "Return a count of the unused addresses on a given network." path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}/unused_count" history.record_get(path) return get(path).json() def get_network_unused_list(ip_range: str) -> List[str]: - "Return a list of the unused addresses on a given network" + "Return a list of the unused addresses on a given network." path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}/unused_list" history.record_get(path) return get(path).json() def get_network_first_unused(ip_range: str) -> str: - "Returns the first unused address on a network, if any" + "Returns the first unused address on a network, if any." path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}/first_unused" history.record_get(path) return get(path).json() def get_network_reserved_ips(ip_range: str) -> List[str]: - "Returns the first unused address on a network, if any" + "Returns the first unused address on a network, if any." path = f"/api/v1/networks/{urllib.parse.quote(ip_range)}/reserved_list" history.record_get(path) return get(path).json() @@ -606,13 +620,14 @@ def string_to_int(value: Any, error_tag: str) -> int: # # ################################################################################ + def is_valid_ip(ip: str) -> bool: """Check if ip is valid ipv4 og ipv6.""" return is_valid_ipv4(ip) or is_valid_ipv6(ip) def is_valid_ipv4(ip: str) -> bool: - """Check if ip is valid ipv4""" + """Check if ip is valid ipv4.""" try: ipaddress.IPv4Address(ip) except ValueError: @@ -622,7 +637,7 @@ def is_valid_ipv4(ip: str) -> bool: def is_valid_ipv6(ip: str) -> bool: - """Check if ip is valid ipv6""" + """Check if ip is valid ipv6.""" try: ipaddress.IPv6Address(ip) except ValueError: @@ -632,7 +647,7 @@ def is_valid_ipv6(ip: str) -> bool: def is_valid_network(net: str) -> bool: - """Check if net is a valid network""" + """Check if net is a valid network.""" if is_valid_ip(net): return False try: @@ -643,7 +658,7 @@ def is_valid_network(net: str) -> bool: def is_valid_mac(mac: str) -> bool: - """Check if mac is a valid MAC address""" + """Check if mac is a valid MAC address.""" return bool(re.match(r"^([a-fA-F0-9]{2}[\.:-]?){5}[a-fA-F0-9]{2}$", mac)) @@ -660,7 +675,7 @@ def is_valid_ttl(ttl: Union[int, str, bytes]) -> bool: # int? def is_valid_email(email: Union[str, bytes]) -> bool: - """Check if email looks like a valid email""" + """Check if email looks like a valid email.""" if not isinstance(email, str): try: email = email.decode() @@ -670,60 +685,59 @@ def is_valid_email(email: Union[str, bytes]) -> bool: def is_valid_location_tag(loc: str) -> bool: - """Check if valid location tag""" + """Check if valid location tag.""" return loc in location_tags def is_valid_category_tag(cat: str) -> bool: - """Check if valid location tag""" + """Check if valid location tag.""" return cat in category_tags def format_mac(mac: str) -> str: - """ - Create a strict 'aa:bb:cc:11:22:33' MAC address. + """Create a strict 'aa:bb:cc:11:22:33' MAC address. Replaces any other delimiters with a colon and turns it into all lower case. """ - mac = re.sub('[.:-]', '', mac).lower() - return ":".join(["%s" % (mac[i:i+2]) for i in range(0, 12, 2)]) + mac = re.sub("[.:-]", "", mac).lower() + return ":".join(["%s" % (mac[i : i + 2]) for i in range(0, 12, 2)]) -def convert_wildcard_to_regex(param: str, arg: str, autoWildcards: bool = False) -> Tuple[str, str]: - """ - Convert wildcard filter "foo*bar*" to something DRF will understand. +def convert_wildcard_to_regex( + param: str, arg: str, autoWildcards: bool = False +) -> Tuple[str, str]: + """Convert wildcard filter "foo*bar*" to something DRF will understand. E.g. "foo*bar*" -> "?name__regex=$foo.*bar.*" """ - if '*' not in arg: + if "*" not in arg: if autoWildcards: - arg = f'*{arg}*' + arg = f"*{arg}*" else: return (param, arg) - args = arg.split('*') + args = arg.split("*") args_len = len(args) - 1 - regex = '' + regex = "" for i, piece in enumerate(args): if i == 0 and piece: - regex += f'^{piece}' + regex += f"^{piece}" elif i == args_len and piece: - regex += f'{piece}$' + regex += f"{piece}$" elif piece: - regex += f'.*{piece}.*' -# if i == 0 and piece: -# parts.append(f'{param}__startswith={piece}') -# elif i == args_len and piece: -# parts.append(f'{param}__endswith={piece}') -# elif piece: -# parts.append(f'{param}__contains={piece}') - - if arg == '*': - regex = '.' + regex += f".*{piece}.*" + # if i == 0 and piece: + # parts.append(f'{param}__startswith={piece}') + # elif i == args_len and piece: + # parts.append(f'{param}__endswith={piece}') + # elif piece: + # parts.append(f'{param}__contains={piece}') - return (f'{param}__regex', regex) + if arg == "*": + regex = "." + return (f"{param}__regex", regex) ################################################################################ @@ -744,7 +758,7 @@ def print_table( longest = len(header) for d in data: longest = max(longest, len(str(d[key]))) - raw_format += '{:<%d} ' % longest + raw_format += "{:<%d} " % longest print(raw_format.format(*headers)) for d in data: diff --git a/mreg_cli/zone.py b/mreg_cli/zone.py index ddcc5510..c6cabe9c 100644 --- a/mreg_cli/zone.py +++ b/mreg_cli/zone.py @@ -8,14 +8,14 @@ ################################# zone = cli.add_command( - prog='zone', - description='Manage zones.', + prog="zone", + description="Manage zones.", ) def _verify_nameservers(nameservers, force): if not nameservers: - cli_warning('At least one nameserver is required') + cli_warning("At least one nameserver is required") errors = [] for nameserver in nameservers: @@ -25,8 +25,8 @@ def _verify_nameservers(nameservers, force): if not force: errors.append(f"{nameserver} is not in mreg, must force") else: - if info['zone'] is not None: - if not info['ipaddresses'] and not force: + if info["zone"] is not None: + if not info["ipaddresses"] and not force: errors.append(f"{nameserver} has no A-record/glue, must force") if errors: cli_warning("\n".join(errors)) @@ -37,10 +37,10 @@ def print_ns(info: str, hostname: str, ttl: str, padding: int = 20) -> None: def zone_basepath(name): - basepath = '/api/v1/zones/' - if name.endswith('.arpa'): - return f'{basepath}reverse/' - return f'{basepath}forward/' + basepath = "/api/v1/zones/" + if name.endswith(".arpa"): + return f"{basepath}reverse/" + return f"{basepath}forward/" def zone_path(name): @@ -59,9 +59,9 @@ def get_zone(name): # Implementation of sub command 'create' # ########################################## + def create(args): - """Create new zone. - """ + """Create new zone.""" _verify_nameservers(args.ns, args.force) path = zone_basepath(args.zone) post(path, name=args.zone, email=args.email, primary_ns=args.ns) @@ -69,25 +69,16 @@ def create(args): zone.add_command( - prog='create', - description='Create new zone.', - short_desc='Create new zone.', + prog="create", + description="Create new zone.", + short_desc="Create new zone.", callback=create, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('email', - description='Contact email.', - metavar='EMAIL'), - Flag('ns', - description='Nameservers of the zone.', - nargs='+', - metavar='NS'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag("email", description="Contact email.", metavar="EMAIL"), + Flag("ns", description="Nameservers of the zone.", nargs="+", metavar="NS"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -95,42 +86,36 @@ def create(args): # Implementation of sub command 'delegation_create' # ##################################################### + def delegation_create(args): - """Create a new zone delegation. """ + """Create a new zone delegation.""" _, path = get_zone(args.zone) if not args.delegation.endswith(f".{args.zone}"): cli_warning(f"Delegation '{args.delegation}' is not in '{args.zone}'") _verify_nameservers(args.ns, args.force) - post(f"{path}/delegations/", - name=args.delegation, - nameservers=args.ns, - comment=args.comment) + post( + f"{path}/delegations/", + name=args.delegation, + nameservers=args.ns, + comment=args.comment, + ) cli_info("created zone delegation {}".format(args.delegation), True) zone.add_command( - prog='delegation_create', - description='Create new zone delegation.', - short_desc='Create new zone delegation.', + prog="delegation_create", + description="Create new zone delegation.", + short_desc="Create new zone delegation.", callback=delegation_create, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('delegation', - description='Delegation', - metavar='DELEGATION'), - Flag('ns', - description='Nameservers for the delegation.', - nargs='+', - metavar='NS'), - Flag('-comment', - description='Comment with a description', - metavar='COMMENT'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag("delegation", description="Delegation", metavar="DELEGATION"), + Flag( + "ns", description="Nameservers for the delegation.", nargs="+", metavar="NS" + ), + Flag("-comment", description="Comment with a description", metavar="COMMENT"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -138,39 +123,36 @@ def delegation_create(args): # Implementation of sub command 'delete' # ########################################## -def zone_delete(args): - """Delete a zone - """ +def zone_delete(args): + """Delete a zone.""" zone, path = get_zone(args.zone) - hosts = get_list("/api/v1/hosts/", params={"zone": zone['id']}) - zones = get_list(zone_basepath(args.zone), params={"name__endswith": f".{args.zone}"}) + hosts = get_list("/api/v1/hosts/", params={"zone": zone["id"]}) + zones = get_list( + zone_basepath(args.zone), params={"name__endswith": f".{args.zone}"} + ) # XXX: Not a fool proof check, as e.g. SRVs are not hosts. (yet.. ?) if hosts: cli_warning(f"Zone has {len(hosts)} registered entries. Can not delete.") - other_zones = [z['name'] for z in zones if z['name'] != args.zone] + other_zones = [z["name"] for z in zones if z["name"] != args.zone] if other_zones: zone_desc = ", ".join(sorted(other_zones)) cli_warning(f"Zone has registered subzones: '{zone_desc}'. Can not delete") delete(path) - cli_info("deleted zone {}".format(zone['name']), True) + cli_info("deleted zone {}".format(zone["name"]), True) zone.add_command( - prog='delete', - description='Delete a zone', - short_desc='Delete a zone', + prog="delete", + description="Delete a zone", + short_desc="Delete a zone", callback=zone_delete, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -178,8 +160,9 @@ def zone_delete(args): # Implementation of sub command 'delegation_delete' # ##################################################### + def delegation_delete(args): - """Delete a zone delegation. """ + """Delete a zone delegation.""" zone, path = get_zone(args.zone) if not args.delegation.endswith(f".{args.zone}"): cli_warning(f"Delegation '{args.delegation}' is not in '{args.zone}'") @@ -188,18 +171,14 @@ def delegation_delete(args): zone.add_command( - prog='delegation_delete', - description='Delete a zone delegation', - short_desc='Delete a zone delegation', + prog="delegation_delete", + description="Delete a zone delegation", + short_desc="Delete a zone delegation", callback=delegation_delete, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('delegation', - description='Delegation', - metavar='DELEGATION'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag("delegation", description="Delegation", metavar="DELEGATION"), + ], ) @@ -207,24 +186,24 @@ def delegation_delete(args): # Implementation of sub command 'info' # ########################################## + def info(args): - """Show SOA info for a existing zone. - """ + """Show SOA info for a existing zone.""" def print_soa(info: str, text: str, padding: int = 20) -> None: print("{1:<{0}}{2}".format(padding, info, text)) if not args.zone: - cli_warning('Name is required') + cli_warning("Name is required") zone, _ = get_zone(args.zone) print_soa("Zone:", zone["name"]) print_ns("Nameservers:", "hostname", "TTL") - for ns in zone['nameservers']: - ttl = ns['ttl'] if ns['ttl'] else "" - print_ns("", ns['name'], ttl) + for ns in zone["nameservers"]: + ttl = ns["ttl"] if ns["ttl"] else "" + print_ns("", ns["name"], ttl) print_soa("Primary ns:", zone["primary_ns"]) - print_soa("Email:", zone['email']) + print_soa("Email:", zone["email"]) print_soa("Serialnumber:", zone["serialno"]) print_soa("Refresh:", zone["refresh"]) print_soa("Retry:", zone["retry"]) @@ -234,15 +213,13 @@ def print_soa(info: str, text: str, padding: int = 20) -> None: zone.add_command( - prog='info', - description='Delete a zone', - short_desc='Delete a zone', + prog="info", + description="Delete a zone", + short_desc="Delete a zone", callback=info, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + ], ) @@ -250,47 +227,50 @@ def print_soa(info: str, text: str, padding: int = 20) -> None: # Implementation of sub command 'list' # ########################################## -def zone_list(args): - """List all zones. - """ +def zone_list(args): + """List all zones.""" all_zones = [] + def _get_zone_list(zonetype): zones = get_list(f"/api/v1/zones/{zonetype}/") all_zones.extend(zones) if not (args.forward or args.reverse): - cli_warning('Add either -forward or -reverse as argument') + cli_warning("Add either -forward or -reverse as argument") if args.forward: - _get_zone_list('forward') + _get_zone_list("forward") if args.reverse: - _get_zone_list('reverse') - + _get_zone_list("reverse") if all_zones: print("Zones:") for zone in all_zones: - print(' {}'.format(zone['name'])) + print(" {}".format(zone["name"])) else: print("No zones found.") zone.add_command( - prog='list', - description='List zones', - short_desc='List zones', + prog="list", + description="List zones", + short_desc="List zones", callback=zone_list, - flags = [ - Flag('-forward', - action='store_true', - short_desc='List all forward zones', - description='List all forward zones'), - Flag('-reverse', - action='store_true', - short_desc='List all reverse zones', - description='List all reverse zones') - ], + flags=[ + Flag( + "-forward", + action="store_true", + short_desc="List all forward zones", + description="List all forward zones", + ), + Flag( + "-reverse", + action="store_true", + short_desc="List all reverse zones", + description="List all reverse zones", + ), + ], ) @@ -298,42 +278,40 @@ def _get_zone_list(zonetype): # Implementation of sub command 'delegation_list' # ################################################### -def zone_delegation_list(args): - """List a zone's delegations - """ +def zone_delegation_list(args): + """List a zone's delegations.""" _, path = get_zone(args.zone) delegations = get_list(f"{path}/delegations/") if delegations: print("Delegations:") - for i in sorted(delegations, key=lambda kv: kv['name']): - print(' {}'.format(i['name'])) - if i['comment']: - print(' Comment: {}'.format(i['comment'])) + for i in sorted(delegations, key=lambda kv: kv["name"]): + print(" {}".format(i["name"])) + if i["comment"]: + print(" Comment: {}".format(i["comment"])) print_ns("Nameservers:", "hostname", "TTL") - for ns in i['nameservers']: - ttl = ns['ttl'] if ns['ttl'] else "" - print_ns("", ns['name'], ttl) + for ns in i["nameservers"]: + ttl = ns["ttl"] if ns["ttl"] else "" + print_ns("", ns["name"], ttl) else: cli_info(f"No delegations for {args.zone}", True) zone.add_command( - prog='delegation_list', + prog="delegation_list", description="List a zone's delegations", short_desc="List a zone's delegations", callback=zone_delegation_list, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + ], ) ########################################################## # Implementation of sub command 'delegation_comment_set' # ########################################################## + def _get_delegation_path(zone, delegation): if not delegation.endswith(f".{zone}"): cli_warning(f"Delegation '{delegation}' is not in '{zone}'") @@ -343,56 +321,45 @@ def _get_delegation_path(zone, delegation): if delegation is not None: return path else: - cli_error('Delegation {delegation} not found') + cli_error("Delegation {delegation} not found") def zone_delegation_comment_set(args): - """Set a delegation's comment""" - + """Set a delegation's comment.""" path = _get_delegation_path(args.zone, args.delegation) patch(path, comment=args.comment) cli_info(f"Updated comment for {args.delegation}", True) zone.add_command( - prog='delegation_comment_set', + prog="delegation_comment_set", description="Set a comment for zone delegation", short_desc="Set a comment for zone delegation", callback=zone_delegation_comment_set, flags=[ - Flag('zone', - description='Zone name', - metavar='ZONE'), - Flag('delegation', - description='Delegation', - metavar='DELEGATION'), - Flag('comment', - description='Comment', - metavar='COMMENT'), - ] + Flag("zone", description="Zone name", metavar="ZONE"), + Flag("delegation", description="Delegation", metavar="DELEGATION"), + Flag("comment", description="Comment", metavar="COMMENT"), + ], ) -def zone_delegation_comment_remove(args): - """Set a delegation's comment""" +def zone_delegation_comment_remove(args): + """Set a delegation's comment.""" path = _get_delegation_path(args.zone, args.delegation) - patch(path, comment='') + patch(path, comment="") cli_info(f"Removed comment for {args.delegation}", True) zone.add_command( - prog='delegation_comment_remove', + prog="delegation_comment_remove", description="Remove a comment for zone delegation", short_desc="Remove a comment for zone delegation", callback=zone_delegation_comment_remove, flags=[ - Flag('zone', - description='Zone name', - metavar='ZONE'), - Flag('delegation', - description='Delegation', - metavar='DELEGATION'), - ] + Flag("zone", description="Zone name", metavar="ZONE"), + Flag("delegation", description="Delegation", metavar="DELEGATION"), + ], ) @@ -400,9 +367,9 @@ def zone_delegation_comment_remove(args): # Implementation of sub command 'set_ns' # ########################################## + def set_ns(args): - """Update nameservers for an existing zone. - """ + """Update nameservers for an existing zone.""" _verify_nameservers(args.ns, args.force) zone, path = get_zone(args.zone) patch(f"{path}/nameservers", primary_ns=args.ns) @@ -410,22 +377,15 @@ def set_ns(args): zone.add_command( - prog='set_ns', - description='Update nameservers for an existing zone.', - short_desc='Update nameservers for an existing zone.', + prog="set_ns", + description="Update nameservers for an existing zone.", + short_desc="Update nameservers for an existing zone.", callback=set_ns, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('ns', - description='Nameservers of the zone.', - nargs='+', - metavar='NS'), - Flag('-force', - action='store_true', - description='Enable force.'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag("ns", description="Nameservers of the zone.", nargs="+", metavar="NS"), + Flag("-force", action="store_true", description="Enable force."), + ], ) @@ -433,18 +393,25 @@ def set_ns(args): # Implementation of sub command 'set_soa' # ########################################### + def set_soa(args): # .zone .ns .email .serialno .retry .expire .soa_ttl - """Updated the SOA of a zone. - """ + """Updated the SOA of a zone.""" _, path = get_zone(args.zone) data = {} - for i in ('email', 'expire', 'refresh', 'retry', 'serialno', 'soa_ttl',): + for i in ( + "email", + "expire", + "refresh", + "retry", + "serialno", + "soa_ttl", + ): value = getattr(args, i, None) if value is not None: data[i] = value if args.ns: - data['primary_ns'] = args.ns + data["primary_ns"] = args.ns if data: patch(path, **data) @@ -454,41 +421,24 @@ def set_soa(args): zone.add_command( - prog='set_soa', - description='Updated the SOA of a zone.', - short_desc='Updated the SOA of a zone.', + prog="set_soa", + description="Updated the SOA of a zone.", + short_desc="Updated the SOA of a zone.", callback=set_soa, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('-ns', - description='Primary nameserver (SOA MNAME).', - metavar='PRIMARY-NS'), - Flag('-email', - description='Zone contact email.', - metavar='EMAIL'), - Flag('-serialno', - description='Serial number.', - type=int, - metavar='SERIALNO'), - Flag('-refresh', - description='Refresh time.', - type=int, - metavar='REFRESH'), - Flag('-retry', - description='Retry time.', - type=int, - metavar='RETRY'), - Flag('-expire', - description='Expire time.', - type=int, - metavar='EXPIRE'), - Flag('-soa-ttl', - description='SOA Time To Live', - type=int, - metavar='TTL'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag( + "-ns", description="Primary nameserver (SOA MNAME).", metavar="PRIMARY-NS" + ), + Flag("-email", description="Zone contact email.", metavar="EMAIL"), + Flag( + "-serialno", description="Serial number.", flag_type=int, metavar="SERIALNO" + ), + Flag("-refresh", description="Refresh time.", flag_type=int, metavar="REFRESH"), + Flag("-retry", description="Retry time.", flag_type=int, metavar="RETRY"), + Flag("-expire", description="Expire time.", flag_type=int, metavar="EXPIRE"), + Flag("-soa-ttl", description="SOA Time To Live", flag_type=int, metavar="TTL"), + ], ) ################################################### @@ -498,26 +448,20 @@ def set_soa(args): def set_default_ttl(args): # .zone .ttl - """Update the default TTL of a zone. - """ + """Update the default TTL of a zone.""" _, path = get_zone(args.zone) - data = {'default_ttl': args.ttl} + data = {"default_ttl": args.ttl} patch(path, **data) cli_info("set default TTL for {}".format(args.zone), True) zone.add_command( - prog='set_default_ttl', - description='Set the default TTL of a zone.', - short_desc='Set the default TTL of a zone.', + prog="set_default_ttl", + description="Set the default TTL of a zone.", + short_desc="Set the default TTL of a zone.", callback=set_default_ttl, flags=[ - Flag('zone', - description='Zone name.', - metavar='ZONE'), - Flag('ttl', - description='Default Time To Live.', - type=int, - metavar='TTL'), - ] + Flag("zone", description="Zone name.", metavar="ZONE"), + Flag("ttl", description="Default Time To Live.", flag_type=int, metavar="TTL"), + ], ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..069a0f31 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,130 @@ +[build-system] +requires = ["setuptools ~= 58.0"] + +[tool.black] +max-line-length = 99 + +[tool.isort] +profile = "black" +line_length = 99 + +[tool.ruff] +select = [ + "A", # flake8-builtins + # "ANN", # annotations + # "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + # "D", # pydocstyle + "DJ", # flake8-django + "E", # error + # "F", # Pyflakes + "I", # isort + "ISC", # flake8-implicit-str-concat + "INP", # flake8-no-pep420 (implicit namespace packages, you need __init__.py) + # "PL", # pylint (all of the below) + "PLC", # pylint-convention + "PLE", # pylint-error + # "PLR", # pylint-refactor + # "PLW", # pylint-warning + "W", # warnings +] +ignore = [ + # B905 (`zip()` without an explicit `strict=` parameter) is incompatible with older python versions. + "B905", + # D203 (1 blank line required before class docstring) is incompatible with + # D211 (No blank lines allowed before class docstring). We ignore D203. + "D203", + # D213 (Multi-line docstring summary should start at the second line) is incompatible with + # D212 (Multi-line docstring summary should start at the first line). We ignore D213. + "D213", + # ANN101 (Missing type annotation for `self` in method) is infered. + "ANN101", +] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "I", + "N", + "Q", + "S", + "T", + "W", + "ANN", + "ARG", + "BLE", + "COM", + "DJ", + "DTZ", + "EM", + "ERA", + "EXE", + "FBT", + "ICN", + "INP", + "ISC", + "NPY", + "PD", + "PGH", + "PIE", + "PL", + "PT", + "PTH", + "PYI", + "RET", + "RSE", + "RUF", + "SIM", + "SLF", + "TCH", + "TID", + "TRY", + "UP", + "YTT", +] +unfixable = [] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + "migrations", +] +per-file-ignores = {} + +line-length = 99 + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# Assume Python 3.10. +target-version = "py310" + +[tool.ruff.mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 10 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..6727bafd --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +black +isort +ruff +tox \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..da1e446f --- /dev/null +++ b/tox.ini @@ -0,0 +1,28 @@ +[tox] +minversion = 4 +isolated_build = true +skip_missing_interpreters = true +toxworkdir = {env:TOX_WORKDIR:.tox} +envlist = + lint + +[gh-actions] +python = + 3.6: python36 + 3.7: python37 + 3.8: python38 + 3.9: python39 + 3.10: python310 + 3.11: python311 + 3.12: python312 + +[testenv:lint] +skip_install = true +description = Invoke black and ruff on the project. +allowlist_externals = + black + ruff +commands = + black mreg_cli + ruff check --fix mreg_cli +