#!/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 # 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("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 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") # 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: comp_exprs.append("/bin/cli-shell-api listActiveNodes {0} | sed -e \"s/'//g\" && echo".format(i.text)) for i in scripts: comp_exprs.append("{0}".format(i.text)) 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: node_def += "help: {0}\n".format(props["help"]) if "comp_help" in props: node_def += "allowed: {0}\n".format(props["comp_help"]) if command is not None: node_def += "run: {0}\n".format(command.text) 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") command = n.find("command") name = n.get("name") node_type = n.tag my_tmpl_dir.append(name) if debug: print("Name of the node: {};\n Created directory: ".format(name), end="") os.makedirs(make_path(my_tmpl_dir), exist_ok=True) props = get_properties(props_elem) if node_type == "node": if debug: print("Processing node {}".format(name)) 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) if node_type == "tagNode": if debug: print("Processing tag node {}".format(name)) os.makedirs(make_path(my_tmpl_dir), exist_ok=True) nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") if not os.path.exists(nodedef_path): with open(nodedef_path, "w") as f: f.write('help: {0}\n'.format(props['help'])) else: # Something has already generated this file pass # 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 with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "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) else: # This is a leaf node if debug: print("Processing leaf node {}".format(name)) with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f: f.write(make_node_def(props, command)) root = xml.getroot() nodes = root.iterfind("*") for n in nodes: process_node(n, [output_dir])