summaryrefslogtreecommitdiff
path: root/scripts/build-command-op-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-op-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-op-templates')
-rw-r--r--scripts/build-command-op-templates264
1 files changed, 264 insertions, 0 deletions
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates
new file mode 100644
index 0000000..d203fdc
--- /dev/null
+++ b/scripts/build-command-op-templates
@@ -0,0 +1,264 @@
+#!/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-2024 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 re
+import sys
+import os
+import argparse
+import copy
+import functools
+
+from lxml import etree as ET
+from textwrap import fill
+
+# Defaults
+validator_dir = "/opt/vyatta/libexec/validators"
+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
+
+## Load and validate the inputs
+try:
+ xml = ET.parse(input_file)
+except Exception as e:
+ print(f"Failed to load interface definition file {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(f"Interface definition file {input_file} does not match the schema!")
+ sys.exit(1)
+except Exception as e:
+ print(f"Failed to load the XML schema {schema_file}")
+ print(e)
+ sys.exit(1)
+
+if not os.access(output_dir, os.W_OK):
+ print(f"The output directory {output_dir} is not writeable")
+ 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 get_properties(p):
+ props = {}
+
+ if p is None:
+ return props
+
+ # Get the help string
+ try:
+ props["help"] = p.find("help").text
+ except:
+ props["help"] = "No help available"
+
+
+ # 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")
+ comptype = c.find("imagePath")
+
+ # Current backend doesn't support multiple allowed: tags
+ # so we get to emulate it
+ comp_exprs = []
+ for i in lists:
+ comp_exprs.append("echo \"{0}\"".format(i.text))
+ for i in paths:
+ path = re.sub(r'\s+', '/', i.text)
+ comp_exprs.append("ls /opt/vyatta/config/active/{0} 2>/dev/null".format(path))
+ for i in scripts:
+ comp_exprs.append("{0}".format(i.text))
+ if comptype is not None:
+ props["comp_type"] = "imagefiles"
+ comp_exprs.append("echo -n \"<imagefiles>\"")
+ comp_help = " && ".join(comp_exprs)
+ props["comp_help"] = comp_help
+
+ except:
+ props["comp_help"] = []
+
+ return props
+
+
+def make_node_def(props, command):
+ # XXX: replace with a template processor if it grows
+ # out of control
+ node_def = ""
+
+ if "help" in props:
+ help = props["help"]
+ help = fill(help, width=64, subsequent_indent='\t\t\t')
+ node_def += f'help: {help}\n'
+ if "comp_type" in props:
+ node_def += f'comptype: {props["comp_type"]}\n'
+ if "comp_help" in props:
+ node_def += f'allowed: {props["comp_help"]}\n'
+ if command is not None:
+ node_def += f'run: {command.text}\n'
+ if debug:
+ print('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")
+ command = n.find("command")
+ name = n.get("name")
+
+ node_type = n.tag
+
+ my_tmpl_dir.append(name)
+
+ if debug:
+ print(f"Name of the node: {name};\n Created directory: ", end="")
+ os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
+
+ props = get_properties(props_elem)
+
+ nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def")
+ if node_type == "node":
+ if debug:
+ print(f"Processing node {name}")
+
+ # Only create the "node.def" file if it exists but is empty, or if it
+ # does not exist at all.
+ 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, command))
+
+ if children is not None:
+ inner_nodes = children.iterfind("*")
+ for inner_n in inner_nodes:
+ process_node(inner_n, my_tmpl_dir)
+ elif node_type == "tagNode":
+ if debug:
+ print(f"Processing tagNode {name}")
+
+ os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
+
+ # Only create the "node.def" file if it exists but is empty, or if it
+ # does not exist at all.
+ if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0:
+ with open(nodedef_path, "w") as f:
+ f.write('help: {0}\n'.format(props['help']))
+
+ # Create the inner node.tag part
+ my_tmpl_dir.append("node.tag")
+ os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
+ if debug:
+ print("Created path for the tagNode: {}".format(make_path(my_tmpl_dir)), end="")
+
+ # Not sure if we want partially defined tag nodes, write the file unconditionally
+ 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.
+ 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, command))
+
+ if children is not None:
+ inner_nodes = children.iterfind("*")
+ for inner_n in inner_nodes:
+ process_node(inner_n, my_tmpl_dir)
+ elif node_type == "leafNode":
+ # This is a leaf node
+ if debug:
+ print(f"Processing leaf node {name}")
+
+ 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, command))
+ else:
+ print(f"Unknown node_type: {node_type}")
+
+
+def get_node_key(node, attr=None):
+ """ Return the sorting key of an xml node using tag and attributes """
+ if attr is None:
+ return '%s' % node.tag + ':'.join([node.get(attr)
+ for attr in sorted(node.attrib)])
+ if attr in node.attrib:
+ return '%s:%s' % (node.tag, node.get(attr))
+ return '%s' % node.tag
+
+
+def sort_children(node, attr=None):
+ """ Sort children along tag and given attribute. if attr is None, sort
+ along all attributes """
+ if not isinstance(node.tag, str): # PYTHON 2: use basestring instead
+ # not a TAG, it is comment or DATA
+ # no need to sort
+ return
+ # sort child along attr
+ node[:] = sorted(node, key=lambda child: get_node_key(child, attr))
+ # and recurse
+ for child in node:
+ sort_children(child, attr)
+
+root = xml.getroot()
+
+# process_node() processes the XML tree in a fixed order, "node" before "tagNode"
+# before "leafNode". If the generator created a "node.def" file, it can no longer
+# be overwritten - else we would have some stale "node.def" files with an empty
+# help string (T2555). Without the fixed order this would resulted in a case
+# where we get a node and a tagNode with the same name, e.g. "show interfaces
+# ethernet" and "show interfaces ethernet eth0" that the node implementation
+# was not callable from the CLI, rendering this command useless (T3807).
+#
+# This can be fixed by forcing the "node", "tagNode", "leafNode" order by sorting
+# the input XML file automatically (sorting from https://stackoverflow.com/a/46128043)
+# thus adding no additional overhead to the user.
+sort_children(root, 'name')
+
+nodes = root.iterfind("*")
+for n in nodes:
+ process_node(n, [output_dir])