diff options
author | kumvijaya <kuvmijaya@gmail.com> | 2024-09-26 11:31:07 +0530 |
---|---|---|
committer | kumvijaya <kuvmijaya@gmail.com> | 2024-09-26 11:31:07 +0530 |
commit | a950059053f7394acfb453cc0d8194aa3dc721fa (patch) | |
tree | eb0acf278f649b5d1417e18e34d728efcd16e745 /scripts/build-command-templates | |
parent | f0815f3e9b212f424f5adb0c572a71119ad4a8a0 (diff) | |
download | vyos-workflow-test-temp-a950059053f7394acfb453cc0d8194aa3dc721fa.tar.gz vyos-workflow-test-temp-a950059053f7394acfb453cc0d8194aa3dc721fa.zip |
T6732: added same as vyos 1x
Diffstat (limited to 'scripts/build-command-templates')
-rw-r--r-- | scripts/build-command-templates | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/scripts/build-command-templates b/scripts/build-command-templates new file mode 100644 index 0000000..36929ab --- /dev/null +++ b/scripts/build-command-templates @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# +# build-command-template: converts new style command definitions in XML +# to the old style (bunch of dirs and node.def's) command templates +# +# Copyright (C) 2017 VyOS maintainers <maintainers@vyos.net> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + +import sys +import os +import argparse +import copy +import functools + +from lxml import etree as ET +from textwrap import fill + +# Defaults + +#validator_dir = "/usr/libexec/vyos/validators" +validator_dir = "${vyos_validators_dir}" +default_constraint_err_msg = "Invalid value" + + +## Get arguments + +parser = argparse.ArgumentParser(description='Converts new-style XML interface definitions to old-style command templates') +parser.add_argument('--debug', help='Enable debug information output', action='store_true') +parser.add_argument('INPUT_FILE', type=str, help="XML interface definition file") +parser.add_argument('SCHEMA_FILE', type=str, help="RelaxNG schema file") +parser.add_argument('OUTPUT_DIR', type=str, help="Output directory") + +args = parser.parse_args() + +input_file = args.INPUT_FILE +schema_file = args.SCHEMA_FILE +output_dir = args.OUTPUT_DIR +debug = args.debug + +#debug = True + +## Load and validate the inputs + +try: + xml = ET.parse(input_file) +except Exception as e: + print("Failed to load interface definition file {0}".format(input_file)) + print(e) + sys.exit(1) + +try: + relaxng_xml = ET.parse(schema_file) + validator = ET.RelaxNG(relaxng_xml) + + if not validator.validate(xml): + print(validator.error_log) + print("Interface definition file {0} does not match the schema!".format(input_file)) + sys.exit(1) +except Exception as e: + print("Failed to load the XML schema {0}".format(schema_file)) + print(e) + sys.exit(1) + +if not os.access(output_dir, os.W_OK): + print("The output directory {0} is not writeable".format(output_dir)) + sys.exit(1) + +## If we got this far, everything must be ok and we can convert the file + +def make_path(l): + path = functools.reduce(os.path.join, l) + if debug: + print(path) + return path + +def collect_validators(ve): + regexes = [] + regex_elements = ve.findall("regex") + if regex_elements is not None: + regexes = list(map(lambda e: e.text.strip().replace('\\','\\\\'), regex_elements)) + if "" in regexes: + print("Warning: empty regex, node will be accepting any value") + + validator_elements = ve.findall("validator") + validators = [] + if validator_elements is not None: + for v in validator_elements: + v_name = os.path.join(validator_dir, v.get("name")) + + # XXX: lxml returns None for empty arguments + v_argument = None + try: + v_argument = v.get("argument") + except: + pass + if v_argument is None: + v_argument = "" + + validators.append("{0} {1}".format(v_name, v_argument)) + + + regex_args = " ".join(map(lambda s: "--regex \\\'{0}\\\'".format(s), regexes)) + validator_args = " ".join(map(lambda s: "--exec \\\"{0}\\\"".format(s), validators)) + + return regex_args + " " + validator_args + +def get_properties(p, default=None): + props = {} + + if p is None: + return props + + # Get the help string + try: + help = p.find("help").text + if default != None: + # DNS forwarding for instance has multiple defaults - specified as whitespace separated list + tmp = ', '.join(default.text.split()) + help += f' (default: {tmp})' + help = fill(help, width=64, subsequent_indent='\t\t\t') + props["help"] = help + except: + pass + + # Get value help strings + try: + vhe = p.findall("valueHelp") + vh = [] + for v in vhe: + format = v.find("format").text + description = v.find("description").text + if default != None and default.text == format: + description += f' (default)' + # Is no description was specified, keep it empty + if not description: description = '' + vh.append( (format, description) ) + props["val_help"] = vh + except: + props["val_help"] = [] + + # Get the constraint and constraintGroup statements + + error_msg = default_constraint_err_msg + # Get the error message if it's there + try: + error_msg = p.find("constraintErrorMessage").text + except: + pass + + vce = p.find("constraint") + + distinct_validator_string = "" + if vce is not None: + # The old backend doesn't support multiple validators in OR mode + # so we emulate it + + distinct_validator_string = collect_validators(vce) + + vcge = p.findall("constraintGroup") + + group_validator_string = "" + if len(vcge): + for vcg in vcge: + group_validator_string = group_validator_string + " --grp " + collect_validators(vcg) + + if vce is not None or len(vcge): + validator_script = '${vyos_libexec_dir}/validate-value' + validator_string = "exec \"{0} {1} {2} --value \\\'$VAR(@)\\\'\"; \"{3}\"".format(validator_script, distinct_validator_string, group_validator_string, error_msg) + + props["constraint"] = validator_string + + # Get the completion help strings + try: + che = p.findall("completionHelp") + ch = "" + for c in che: + scripts = c.findall("script") + paths = c.findall("path") + lists = c.findall("list") + + # Current backend doesn't support multiple allowed: tags + # so we get to emulate it + comp_exprs = [] + for i in lists: + comp_exprs.append(f'echo "{i.text}"') + for i in paths: + comp_exprs.append(f'/bin/cli-shell-api listNodes {i.text}') + for i in scripts: + comp_exprs.append(f'sh -c "{i.text}"') + comp_help = ' && echo " " && '.join(comp_exprs) + props["comp_help"] = comp_help + except: + props["comp_help"] = [] + + # Get priority + try: + props["priority"] = p.find("priority").text + except: + pass + + # Get "multi" + if p.find("multi") is not None: + props["multi"] = True + + # Get "valueless" + if p.find("valueless") is not None: + props["valueless"] = True + + return props + +def make_node_def(props): + # XXX: replace with a template processor if it grows + # out of control + + node_def = "" + + if "tag" in props: + node_def += "tag:\n" + + if "multi" in props: + node_def += "multi:\n" + + if "type" in props: + # Will always be txt in practice if it's set + node_def += "type: {0}\n".format(props["type"]) + + if "priority" in props: + node_def += "priority: {0}\n".format(props["priority"]) + + if "help" in props: + node_def += "help: {0}\n".format(props["help"]) + + if "val_help" in props: + for v in props["val_help"]: + node_def += "val_help: {0}; {1}\n".format(v[0], v[1]) + + if "comp_help" in props: + node_def += "allowed: {0}\n".format(props["comp_help"]) + + if "constraint" in props: + node_def += "syntax:expression: {0}\n".format(props["constraint"]) + + shim = '${vyshim}' + + if "owner" in props: + if "tag" in props: + node_def += "end: sudo sh -c \"{1} VYOS_TAGNODE_VALUE='$VAR(@)' {0}\"\n".format(props["owner"], shim) + else: + node_def += "end: sudo sh -c \"{1} {0}\"\n".format(props["owner"], shim) + + if debug: + print("The contents of the node.def file:\n", node_def) + + return node_def + +def process_node(n, tmpl_dir): + # Avoid mangling the path from the outer call + my_tmpl_dir = copy.copy(tmpl_dir) + + props_elem = n.find("properties") + children = n.find("children") + + name = n.get("name") + owner = n.get("owner") + node_type = n.tag + + my_tmpl_dir.append(name) + + if debug: + print("Name of the node: {0}. Created directory: {1}\n".format(name, "/".join(my_tmpl_dir)), end="") + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + + props = get_properties(props_elem, n.find("defaultValue")) + if owner: + props["owner"] = owner + # <priority> tag is mandatory if the parent node has an owner + if "priority" not in props: + raise ValueError( + f"<priority> tag should be set for the node <{name}> path '{' '.join(my_tmpl_dir[1:])}'" + ) + + # Type should not be set for non-tag, non-leaf nodes + # For non-valueless leaf nodes, set the type to txt: to make them have some type, + # actual value validation is handled by constraints translated to syntax:expression: + if node_type != "node": + if "valueless" not in props.keys(): + props["type"] = "txt" + if node_type == "tagNode": + props["tag"] = "True" + + if node_type != "leafNode": + if "multi" in props: + raise ValueError("<multi/> tag is only allowed in <leafNode>") + if "valueless" in props: + raise ValueError("<valueless/> is only allowed in <leafNode>") + + nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") + + # Only create the "node.def" file if it exists but is empty, or if it does + # not exist at all. An empty node.def file could be generated by XML paths + # that derive from one another bot having a common base structure like + # "protocols static" + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props)) + + if node_type == "node": + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + process_node(inner_n, my_tmpl_dir) + if node_type == "tagNode": + my_tmpl_dir.append("node.tag") + if debug: + print("Created path for the tagNode:", end="") + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + process_node(inner_n, my_tmpl_dir) + else: + # This is a leaf node + pass + + +root = xml.getroot() + +nodes = root.iterfind("*") +for n in nodes: + if n.tag == "syntaxVersion": + continue + try: + process_node(n, [output_dir]) + except ValueError as e: + print(e) + sys.exit(1) |