summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/build-command-op-templates62
-rwxr-xr-xscripts/build-command-templates6
-rwxr-xr-xscripts/check-properties-collision117
-rwxr-xr-xscripts/generate-activation-scripts-json.py55
-rwxr-xr-xscripts/generate-configd-include-json.py2
-rwxr-xr-xscripts/override-default2
-rwxr-xr-xscripts/override-help115
-rwxr-xr-xscripts/transclude-template2
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