diff --git a/.travis/config.sh b/.travis/config.sh index d701d46..879e5d2 100644 --- a/.travis/config.sh +++ b/.travis/config.sh @@ -42,9 +42,9 @@ type -f lsr_check_python_version > /dev/null 2>&1 || . ${SCRIPTDIR}/utils.sh if lsr_check_python_version python -lt 3.0 then - PYTHON2_EXCLUDES="tests/ensure_provider_tests.py" + PYTHON2_EXCLUDES="tests/ensure_provider_tests.py,scripts/print_all_options.py" FLAKE8_DEFAULT_EXCLUDES=".svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg" - RUN_PYLINT_EXCLUDE='^(\..*|ensure_provider_tests\.py)$' + RUN_PYLINT_EXCLUDE='^(\..*|ensure_provider_tests\.py|print_all_options\.py)$' RUN_FLAKE8_EXTRA_ARGS="--exclude ${FLAKE8_DEFAULT_EXCLUDES},${PYTHON2_EXCLUDES}" fi # diff --git a/scripts/print_all_options.py b/scripts/print_all_options.py new file mode 100755 index 0000000..ee8ef0c --- /dev/null +++ b/scripts/print_all_options.py @@ -0,0 +1,180 @@ +#!/usr/bin/python3 -tt +# SPDX-License-Identifier: BSD-3-Clause +# Helper to print all options that the module in the network role accepts for +# profiles + +from collections.abc import Mapping +from collections.abc import Sequence +from copy import deepcopy +from unittest import mock +import os +import sys + +PRIORITIES = ( + "name", + "type", + "interface_name", + "mac", + "state", + "persistent_state", + "master", + "slave_type", + "parent", + "ignore_errors", + "force_state_change", + "check_iface_exists", + "autoconnect", + "wait", + "zone", + "mtu", + "ip", + "ethernet", + "ethtool", + "bridge", + "bond", + "team", + "vlan", + "wireless", + "macvlan", + "infiniband", +) + + +import yaml + +parentdir = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) + +with mock.patch.object( + sys, + "path", + [parentdir, os.path.join(parentdir, "module_utils/network_lsr")] + sys.path, +): + with mock.patch.dict( + "sys.modules", + {"ansible": mock.Mock(), "ansible.module_utils": __import__("module_utils")}, + ): + import argument_validator as av + +COMMENT = "@@" +EMPTY = "/EMPTY/" + + +def parse_validator(validator): + default = validator.default_value + if isinstance(validator, av.ArgValidatorDict): + res = {} + for k, v in validator.nested.items(): + if v.name not in ( + "infiniband_transport_mode", + "infiniband_p_key", + "vlan_id", + ) and not isinstance(v, av.ArgValidatorDeprecated): + name = k + if not validator.required: + pass + # name += " DICT optional" + res[name] = parse_validator(v) + elif isinstance(validator, av.ArgValidatorList): + res = [parse_validator(validator.nested)] + elif isinstance(validator, av.ArgValidatorNum): + + minval = validator.val_min + maxval = validator.val_max + comment = f" {COMMENT}" + if not validator.required: + comment += " optional" + if minval is not None: + comment += " mininum=" + str(minval) + if maxval: + if maxval == 0xFFFFFFFF: + maxval = hex(maxval) + comment += " maximum=" + str(maxval) + + if default is not None: + res = str(default) + elif minval is not None: + res = str(minval) + elif maxval is not None: + res = str(maxval) + else: + res = "" + + res += comment + elif isinstance(validator, av.ArgValidatorIP): + res = f"{EMPTY} {COMMENT} IP Address" + elif isinstance(validator, av.ArgValidatorStr): + if default: + res = default + elif validator.enum_values: + res = "|".join(validator.enum_values) + else: + res = EMPTY + if not validator.required: + res += f" {COMMENT} optional" + + # res += " " + str(validator.__class__) + elif isinstance(validator, av.ArgValidatorBool): + if default is not None: + res = "yes" if default else "no" + else: + res = "yes|no" + + if not validator.required: + res += f" {COMMENT} optional" + else: + res = validator.name + f" {COMMENT} FIXME " + str(validator.__class__) + + return res + + +def represent_dict(dumper, data): + """ + Represent dictionary with insert order + """ + value = [] + + for item_key, item_value in data.items(): + node_key = dumper.represent_data(item_key) + node_value = dumper.represent_data(item_value) + value.append((node_key, node_value)) + + return yaml.nodes.MappingNode("tag:yaml.org,2002:map", value) + + +def priority_sorted(data): + if isinstance(data, Sequence) and not isinstance(data, str): + return [priority_sorted(item) for item in data] + + if isinstance(data, Mapping): + sorted_data = {} + for key in sorted(data, key=prioritize): + sorted_data[key] = priority_sorted(data[key]) + return sorted_data + + return deepcopy(data) + + +def prioritize(key): + try: + priority = PRIORITIES.index(key) + except ValueError: + priority = len(PRIORITIES) + return (priority, key) + + +yaml.add_representer(dict, represent_dict) +sorted_data = priority_sorted([parse_validator(av.ArgValidator_DictConnection())]) +yaml_example = ( + yaml.dump( + sorted_data, + explicit_start=True, + default_flow_style=False, + width=100, + ) + .replace(COMMENT, "#") + .replace(EMPTY, "") +) + +# yaml_example = re.sub(r"# ([^:]*):", r": # \1", yaml_example) + +print(yaml_example)