diff options
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/build-command-op-templates | 62 | ||||
| -rwxr-xr-x | scripts/build-command-templates | 6 | ||||
| -rwxr-xr-x | scripts/check-properties-collision | 117 | ||||
| -rwxr-xr-x | scripts/generate-activation-scripts-json.py | 55 | ||||
| -rwxr-xr-x | scripts/generate-configd-include-json.py | 2 | ||||
| -rwxr-xr-x | scripts/override-default | 2 | ||||
| -rwxr-xr-x | scripts/override-help | 115 | ||||
| -rwxr-xr-x | scripts/transclude-template | 2 |
8 files changed, 352 insertions, 9 deletions
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates index d203fdcef..94bbd1d07 100755 --- a/scripts/build-command-op-templates +++ b/scripts/build-command-op-templates @@ -3,7 +3,7 @@ # 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> +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -30,6 +30,8 @@ import functools from lxml import etree as ET from textwrap import fill +debug = True + # Defaults validator_dir = "/opt/vyatta/libexec/validators" default_constraint_err_msg = "Invalid value" @@ -116,7 +118,7 @@ def get_properties(p): if comptype is not None: props["comp_type"] = "imagefiles" comp_exprs.append("echo -n \"<imagefiles>\"") - comp_help = " && ".join(comp_exprs) + comp_help = " ; ".join(comp_exprs) props["comp_help"] = comp_help except: @@ -124,6 +126,26 @@ def get_properties(p): return props +def get_standalone(s): + standalone = {} + + if s is None: + return {} + + # Get the help string + try: + standalone["help"] = s.find("help").text + except: + standalone["help"] = "No help available" + + # Get the command -- it's required by the schema + try: + standalone["command"] = s.find("command") + except: + raise AssertionError("Found a <standalone> node without <command>") + + return standalone + def make_node_def(props, command): # XXX: replace with a template processor if it grows @@ -150,19 +172,27 @@ def process_node(n, tmpl_dir): my_tmpl_dir = copy.copy(tmpl_dir) props_elem = n.find("properties") + standalone_elem = n.find("standalone") children = n.find("children") command = n.find("command") name = n.get("name") node_type = n.tag - my_tmpl_dir.append(name) + if name: + my_tmpl_dir.append(name) + else: + # Virtual tag nodes have no names, + # that's a normal situation. + # In that case we create subdirs at the current level. + pass 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) + standalone = get_standalone(standalone_elem) nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") if node_type == "node": @@ -189,7 +219,10 @@ def process_node(n, tmpl_dir): # 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'])) + if standalone: + f.write(make_node_def(standalone, standalone["command"])) + else: + f.write('help: {0}\n'.format(props['help'])) # Create the inner node.tag part my_tmpl_dir.append("node.tag") @@ -209,6 +242,27 @@ def process_node(n, tmpl_dir): inner_nodes = children.iterfind("*") for inner_n in inner_nodes: process_node(inner_n, my_tmpl_dir) + elif node_type == "virtualTagNode": + # The outer structure is already created + + # 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 virtualTagNode: {}".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: diff --git a/scripts/build-command-templates b/scripts/build-command-templates index 36929abb2..9e5576e6d 100755 --- a/scripts/build-command-templates +++ b/scripts/build-command-templates @@ -3,7 +3,7 @@ # 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> +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -177,9 +177,11 @@ def get_properties(p, default=None): for vcg in vcge: group_validator_string = group_validator_string + " --grp " + collect_validators(vcg) + silent = " --silent" if p.findall("constraintSilenceOutput") else "" + 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) + validator_string = "exec \"{0} {1} {2} --value \\\'$VAR(@)\\\'{3}\"; \"{4}\"".format(validator_script, distinct_validator_string, group_validator_string, silent, error_msg) props["constraint"] = validator_string diff --git a/scripts/check-properties-collision b/scripts/check-properties-collision new file mode 100755 index 000000000..4be554f0f --- /dev/null +++ b/scripts/check-properties-collision @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +# Use lxml xpath capability to detect multiple properties elements at the +# same path with differing content. + + +import sys +import glob +import logging +import io +from lxml import etree + + +debug = False + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + + +def canonical_form(e): + # pylint: disable=c-extension-no-member + out = io.BytesIO() + + etree.ElementTree(e).write_c14n(out) + return out.getvalue() + + +def check_properties_collision(dir_name): + # pylint: disable=too-many-locals,too-many-branches,c-extension-no-member + """ + Collect elements with help tag into dictionary indexed by name + attributes of ancestor path. + """ + buffer = io.StringIO() + for fname in glob.glob(f'{dir_name}/*.xml'): + tree = etree.parse(fname) + defv = {} + + xpath_str = '//properties' + xp = tree.xpath(xpath_str) + + for element in xp: + ap = element.xpath('ancestor::*[@name]') + ap_name = [el.get('name') for el in ap] + ap_path_str = ' '.join(ap_name) + defv.setdefault(ap_path_str, []).append(element) + + trivial = [] + for k, v in defv.items(): + if len(v) < 2: + trivial.append(k) + for i in trivial: + del defv[i] + + for k, v in defv.items(): + collisions = [] + for i in v: + # If properties contains more than just a help element: + if len(list(i)) > 1: + collisions.append(i) + if len(collisions) > 1: + property_set = set() + for j in collisions: + prop = canonical_form(j) + property_set.add(prop) + if len(property_set) > 1: + buffer.write(f'Collision in file {fname}:\n') + for e in collisions: + buffer.write( + f'Element {e.tag} at {e.sourceline} with content:\n {canonical_form(e)}\n' + ) + + content = buffer.getvalue() + if content: + logger.info( + 'Collisions detected: multiple <properties> elements at a path with differing content other than <help>.\n' + 'Information beyond the first instance will be ignored in the resulting node.def file.' + ) + logger.info(content) + + buffer.close() + + +def main(): + if len(sys.argv) < 2: + logger.critical('Must specify XML directory!') + sys.exit(1) + + dir_name = sys.argv[1] + + check_properties_collision(dir_name) + + +if __name__ == '__main__': + main() diff --git a/scripts/generate-activation-scripts-json.py b/scripts/generate-activation-scripts-json.py new file mode 100755 index 000000000..e8a7f34b4 --- /dev/null +++ b/scripts/generate-activation-scripts-json.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import re +import json +from pathlib import Path + + +def filter_key(s: Path): + s = s.stem + return re.match(r'\d+\-.+', s) + + +def sort_key(s: Path): + s = s.stem + pre, rem = re.match(r'(\d+)(?:-)(.+)', s).groups() + return int(pre), rem + + +activation_dir = 'src/activation-scripts' +activation_list = 'data/activation-list' +activation_list_init = 'data/activation-init' + +activation_scripts = Path(activation_dir).glob('*.py') + +filtered = filter(filter_key, activation_scripts) +script_list = sorted(filtered, key=sort_key) + +# default on system update +script_dict = dict.fromkeys(map(lambda s: s.stem, script_list), 'enabled') +# exceptions: +# only enabled for script_dict_init +script_dict['00-first-installed-boot'] = 'never' +# for backward compatibility on system update +script_dict['20-ethernet-offload'] = 'off' + +# installed on image creation +script_dict_init = dict.fromkeys(map(lambda s: s.stem, script_list), 'enabled') + +Path(activation_list).write_text(json.dumps(script_dict)) +Path(activation_list_init).write_text(json.dumps(script_dict_init)) diff --git a/scripts/generate-configd-include-json.py b/scripts/generate-configd-include-json.py index b4b627fce..8d0accaf1 100755 --- a/scripts/generate-configd-include-json.py +++ b/scripts/generate-configd-include-json.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as diff --git a/scripts/override-default b/scripts/override-default index 5058e79b3..fe5b07bde 100755 --- a/scripts/override-default +++ b/scripts/override-default @@ -5,7 +5,7 @@ # directive. Must be called before build-command-templates, as the schema # disallows redundancy. # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as diff --git a/scripts/override-help b/scripts/override-help new file mode 100755 index 000000000..16c74bbed --- /dev/null +++ b/scripts/override-help @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +# Use lxml xpath capability to find multiple elements with tag help +# relative to path; copy and replace to override the initial value. + +import sys +import glob +import logging +from copy import deepcopy +from lxml import etree + +debug = False + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + + +def override_element(lst: list): + """ + Allow multiple override elements; use the final one (in document order). + """ + if len(lst) < 2: + logger.debug('passing list of single element to override_element') + return + + # replace element with final override + first_element = lst[0] + # lxml will remove on replace, hence make a copy to avoid cleaning up + # empty elements + final_element = deepcopy(lst[-1]) + parent = first_element.getparent() + parent.replace(first_element, final_element) + + +def collect_and_override(dir_name): + # pylint: disable=too-many-locals,c-extension-no-member + """ + Collect elements with help tag into dictionary indexed by name + attributes of ancestor path. + """ + for fname in glob.glob(f'{dir_name}/*.xml'): + tree = etree.parse(fname) + root = tree.getroot() + defv = {} + + xpath_str = '//help' + xp = tree.xpath(xpath_str) + + for element in xp: + ap = element.xpath('ancestor::*[@name]') + ap_name = [el.get('name') for el in ap] + ap_path_str = ' '.join(ap_name) + defv.setdefault(ap_path_str, []).append(element) + + trivial = [] + for k, v in defv.items(): + if len(v) < 2: + trivial.append(k) + for i in trivial: + del defv[i] + + for k, v in defv.items(): + text_set = set() + for e in v: + text_set.add(e.text) + if len(text_set) > 1: + logger.debug(f'Inconsistent help elements in file {fname}:') + for e in v: + logger.debug( + f'Element is {e.tag} with content {e.text} at {e.sourceline}' + ) + + logger.info(f"overriding help in path '{k}'") + override_element(v) + + revised_str = etree.tostring(root, encoding='unicode', pretty_print=True) + + with open(f'{fname}', 'w') as f: + f.write(revised_str) + + +def main(): + if len(sys.argv) < 2: + logger.critical('Must specify XML directory!') + sys.exit(1) + + dir_name = sys.argv[1] + + collect_and_override(dir_name) + + +if __name__ == '__main__': + main() diff --git a/scripts/transclude-template b/scripts/transclude-template index 767583acd..932f91351 100755 --- a/scripts/transclude-template +++ b/scripts/transclude-template @@ -4,7 +4,7 @@ # interpret #include statements to include nested XML fragments and # snippets in documents. # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as |
