diff options
Diffstat (limited to 'src/helpers/config_dependency.py')
-rw-r--r-- | src/helpers/config_dependency.py | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py new file mode 100644 index 0000000..817bcc6 --- /dev/null +++ b/src/helpers/config_dependency.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# 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 os +import sys +import json +from argparse import ArgumentParser +from argparse import ArgumentTypeError +from graphlib import TopologicalSorter, CycleError + +# addon packages will need to specify the dependency directory +data_dir = '/usr/share/vyos/' +dependency_dir = os.path.join(data_dir, 'config-mode-dependencies') + +def dict_merge(source, destination): + from copy import deepcopy + tmp = deepcopy(destination) + + for key, value in source.items(): + if key not in tmp: + tmp[key] = value + elif isinstance(source[key], dict): + tmp[key] = dict_merge(source[key], tmp[key]) + + return tmp + +def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict: + res = {} + for dep_file in os.listdir(dependency_dir): + if not dep_file.endswith('.json'): + continue + path = os.path.join(dependency_dir, dep_file) + with open(path) as f: + d = json.load(f) + if dep_file == 'vyos-1x.json': + res = dict_merge(res, d) + else: + res = dict_merge(d, res) + + return res + +def graph_from_dependency_dict(d: dict) -> dict: + g = {} + for k in list(d): + g[k] = set() + # add the dependencies for every sub-case; should there be cases + # that are mutally exclusive in the future, the graphs will be + # distinguished + for el in list(d[k]): + g[k] |= set(d[k][el]) + + return g + +def is_acyclic(d: dict) -> bool: + g = graph_from_dependency_dict(d) + ts = TopologicalSorter(g) + try: + # get node iterator + order = ts.static_order() + # try iteration + _ = [*order] + except CycleError: + return False + + return True + +def check_dependency_graph(dependency_dir: str = dependency_dir, + supplement: str = None) -> bool: + d = read_dependency_dict(dependency_dir=dependency_dir) + if supplement is not None: + with open(supplement) as f: + d = dict_merge(json.load(f), d) + + return is_acyclic(d) + +def path_exists(s): + if not os.path.exists(s): + raise ArgumentTypeError("Must specify a valid vyos-1x dependency directory") + return s + +def main(): + parser = ArgumentParser(description='generate and save dict from xml defintions') + parser.add_argument('--dependency-dir', type=path_exists, + default=dependency_dir, + help='location of vyos-1x dependency directory') + parser.add_argument('--supplement', type=str, + help='supplemental dependency file') + args = vars(parser.parse_args()) + + if not check_dependency_graph(**args): + print("dependency error: cycle exists") + sys.exit(1) + + print("dependency graph acyclic") + sys.exit(0) + +if __name__ == '__main__': + main() |