From b40c52682a25664f7018ab8b4e5ba6f467f10d17 Mon Sep 17 00:00:00 2001
From: John Estabrook <jestabro@vyos.io>
Date: Sun, 30 Aug 2020 08:22:32 -0500
Subject: config: T2636: get_config_dict() returns a list on multi node by
 default

Unless no_multi_convert is True, a single valued multi node will be
returned as a list by get_config_dict(). Modification of Thomas Mangin's
version.
---
 python/vyos/config.py         | 61 ++++++++++++++++++++++++++-----------------
 python/vyos/configdiff.py     |  4 +--
 python/vyos/xml/__init__.py   |  4 +++
 python/vyos/xml/definition.py | 17 ++++++++++++
 4 files changed, 60 insertions(+), 26 deletions(-)

(limited to 'python/vyos')

diff --git a/python/vyos/config.py b/python/vyos/config.py
index 884d6d947..de79a3654 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -67,6 +67,7 @@ import re
 import json
 from copy import deepcopy
 
+import vyos.xml
 import vyos.util
 import vyos.configtree
 from vyos.configsource import ConfigSource, ConfigSourceSession
@@ -193,50 +194,62 @@ class Config(object):
         """
         return self._config_source.show_config(path, default, effective)
 
-    def get_cached_dict(self, effective=False):
+    def get_cached_root_dict(self, effective=False):
         cached = self._dict_cache.get(effective, {})
         if cached:
-            config_dict = cached
+            return cached
+
+        if effective:
+            config = self._running_config
         else:
-            config_dict = {}
+            config = self._session_config
 
-            if effective:
-                if self._running_config:
-                    config_dict = json.loads((self._running_config).to_json())
-            else:
-                if self._session_config:
-                    config_dict = json.loads((self._session_config).to_json())
+        if config:
+            config_dict = json.loads(config.to_json())
+        else:
+            config_dict = {}
 
-            self._dict_cache[effective] = config_dict
+        self._dict_cache[effective] = config_dict
 
         return config_dict
 
-    def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False):
+    def get_config_dict(self, path=[], effective=False, key_mangling=None,
+                        get_first_key=False, no_multi_convert=False):
         """
         Args:
             path (str list): Configuration tree path, can be empty
             effective=False: effective or session config
             key_mangling=None: mangle dict keys according to regex and replacement
             get_first_key=False: if k = path[:-1], return sub-dict d[k] instead of {k: d[k]}
+            no_multi_convert=False: if convert, return single value of multi node as list
 
         Returns: a dict representation of the config under path
         """
-        config_dict = self.get_cached_dict(effective)
+        lpath = self._make_path(path)
+        root_dict = self.get_cached_root_dict(effective)
+        conf_dict = vyos.util.get_sub_dict(root_dict, lpath, get_first_key)
 
-        config_dict = vyos.util.get_sub_dict(config_dict, self._make_path(path), get_first_key)
+        if not key_mangling and no_multi_convert:
+            return deepcopy(conf_dict)
 
-        if key_mangling:
-            if not (isinstance(key_mangling, tuple) and \
-                    (len(key_mangling) == 2) and \
-                    isinstance(key_mangling[0], str) and \
-                    isinstance(key_mangling[1], str)):
-                raise ValueError("key_mangling must be a tuple of two strings")
-            else:
-                config_dict = vyos.util.mangle_dict_keys(config_dict, key_mangling[0], key_mangling[1])
-        else:
-            config_dict = deepcopy(config_dict)
+        xmlpath = lpath if get_first_key else lpath[:-1]
 
-        return config_dict
+        if not key_mangling:
+            conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict)
+            return conf_dict
+
+        if no_multi_convert is False:
+            conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict)
+
+        if not (isinstance(key_mangling, tuple) and \
+                (len(key_mangling) == 2) and \
+                isinstance(key_mangling[0], str) and \
+                isinstance(key_mangling[1], str)):
+            raise ValueError("key_mangling must be a tuple of two strings")
+
+        conf_dict = vyos.util.mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1])
+
+        return conf_dict
 
     def is_multi(self, path):
         """
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index b79893507..0e41fbe27 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -82,8 +82,8 @@ class ConfigDiff(object):
     """
     def __init__(self, config, key_mangling=None):
         self._level = config.get_level()
-        self._session_config_dict = config.get_cached_dict()
-        self._effective_config_dict = config.get_cached_dict(effective=True)
+        self._session_config_dict = config.get_cached_root_dict(effective=False)
+        self._effective_config_dict = config.get_cached_root_dict(effective=True)
         self._key_mangling = key_mangling
 
     # mirrored from Config; allow path arguments relative to level
diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py
index 0f914fed2..0ef0c85ce 100644
--- a/python/vyos/xml/__init__.py
+++ b/python/vyos/xml/__init__.py
@@ -51,6 +51,10 @@ def defaults(lpath, flat=False):
     return load_configuration().defaults(lpath, flat)
 
 
+def multi_to_list(lpath, conf):
+    return load_configuration().multi_to_list(lpath, conf)
+
+
 if __name__ == '__main__':
     print(defaults(['service'], flat=True))
     print(defaults(['service'], flat=False))
diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py
index 098e64f7e..6d6fcb5c7 100644
--- a/python/vyos/xml/definition.py
+++ b/python/vyos/xml/definition.py
@@ -281,6 +281,23 @@ class XML(dict):
 
         return _flatten(lpath, len(lpath), d)
 
+    def multi_to_list(self, lpath, conf):
+        r = {}
+        for k in conf:
+            # key mangling could also be done here
+            # it would prevent two parsing of the config tree
+            # under = k.replace('-','_')
+            under = k
+            fpath = lpath + [k]
+            if isinstance(conf[k],dict):
+                r[under] = self.multi_to_list(fpath, conf[k])
+                continue
+            value = conf[k]
+            if self.is_multi(fpath) and not isinstance(value, list):
+                value = [value]
+            r[under] = value
+        return r
+
     # from functools import lru_cache
     # @lru_cache(maxsize=100)
     # XXX: need to use cachetool instead - for later
-- 
cgit v1.2.3