summaryrefslogtreecommitdiff
path: root/scripts/build-command-templates
diff options
context:
space:
mode:
authorkumvijaya <kuvmijaya@gmail.com>2024-09-26 11:31:07 +0530
committerkumvijaya <kuvmijaya@gmail.com>2024-09-26 11:31:07 +0530
commita950059053f7394acfb453cc0d8194aa3dc721fa (patch)
treeeb0acf278f649b5d1417e18e34d728efcd16e745 /scripts/build-command-templates
parentf0815f3e9b212f424f5adb0c572a71119ad4a8a0 (diff)
downloadvyos-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-templates348
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)