Page MenuHomeVyOS Platform

build-command-op-templates
No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None

build-command-op-templates

#!/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 = "/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:
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))
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])

File Metadata

Mime Type
text/x-script.python
Expires
Fri, Jan 30, 11:45 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3101273
Default Alt Text
build-command-op-templates (8 KB)

Event Timeline