From c1e04cea0817db07b22a8bd8e6b2f2c0a1e682f4 Mon Sep 17 00:00:00 2001
From: John Estabrook <jestabro@vyos.io>
Date: Thu, 10 Mar 2022 10:07:31 -0600
Subject: Revert "component_version: T4291: consolidate read/write functions"

This reverts commit 534f677d36285863decb2cdff179687b4fd690cb.
Revert while investigating failure in vyos-configtest.
---
 python/vyos/component_version.py  | 174 --------------------------------------
 python/vyos/component_versions.py |  57 +++++++++++++
 python/vyos/formatversions.py     | 109 ++++++++++++++++++++++++
 python/vyos/migrator.py           |  26 ++++--
 python/vyos/systemversions.py     |  46 ++++++++++
 5 files changed, 230 insertions(+), 182 deletions(-)
 delete mode 100644 python/vyos/component_version.py
 create mode 100644 python/vyos/component_versions.py
 create mode 100644 python/vyos/formatversions.py
 create mode 100644 python/vyos/systemversions.py

(limited to 'python')

diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
deleted file mode 100644
index b1554828d..000000000
--- a/python/vyos/component_version.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# Copyright 2022 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
-# 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, see <http://www.gnu.org/licenses/>.
-
-"""
-Functions for reading/writing component versions.
-
-The config file version string has the following form:
-
-VyOS 1.3/1.4:
-
-// Warning: Do not remove the following line.
-// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
-// Release version: 1.3.0
-
-VyOS 1.2:
-
-/* Warning: Do not remove the following line. */
-/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */
-/* Release version: 1.2.8 */
-
-"""
-
-import os
-import re
-import sys
-import fileinput
-from vyos.xml import component_version
-from vyos.version import get_version
-
-def from_string(string_line, vintage='vyos'):
-    """
-    Get component version dictionary from string.
-    Return empty dictionary if string contains no config information
-    or raise error if component version string malformed.
-    """
-    version_dict = {}
-
-    if vintage == 'vyos':
-        if re.match(r'// vyos-config-version:.+', string_line):
-            if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line):
-                raise ValueError(f"malformed configuration string: {string_line}")
-
-            for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
-                version_dict[pair[0]] = int(pair[1])
-
-    elif vintage == 'vyatta':
-        if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line):
-            if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line):
-                raise ValueError(f"malformed configuration string: {string_line}")
-
-            for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
-                version_dict[pair[0]] = int(pair[1])
-    else:
-        raise ValueError("Unknown config string vintage")
-
-    return version_dict
-
-def from_file(config_file_name='/opt/vyatta/etc/config/config.boot',
-                      vintage='vyos'):
-    """
-    Get component version dictionary parsing config file line by line
-    """
-    f = open(config_file_name, 'r')
-    for line_in_config in f:
-        version_dict = from_string(line_in_config, vintage=vintage)
-        if version_dict:
-            return version_dict
-
-    # no version information
-    return {}
-
-def from_system():
-    """
-    Get system component version dict.
-    """
-    return component_version()
-
-def legacy_from_system():
-    """
-    legacy function; imported as-is.
-
-    Get component versions from running system; critical failure if
-    unable to read migration directory.
-    """
-    import vyos.defaults
-
-    system_versions = {}
-
-    try:
-        version_info = os.listdir(vyos.defaults.directories['current'])
-    except OSError as err:
-        print("OS error: {}".format(err))
-        sys.exit(1)
-
-    for info in version_info:
-        if re.match(r'[\w,-]+@\d+', info):
-            pair = info.split('@')
-            system_versions[pair[0]] = int(pair[1])
-
-    return system_versions
-
-def format_string(ver: dict) -> str:
-    """
-    Version dict to string.
-    """
-    keys = list(ver)
-    keys.sort()
-    l = []
-    for k in keys:
-        v = ver[k]
-        l.append(f'{k}@{v}')
-    sep = ':'
-    return sep.join(l)
-
-def system_footer(vintage='vyos') -> str:
-    """
-    Version footer as string.
-    """
-    ver_str = format_string(from_system())
-    release = get_version()
-    if vintage == 'vyos':
-        ret_str = (f'// Warning: Do not remove the following line.\n'
-                +  f'// vyos-config-version: "{ver_str}"\n'
-                +  f'// Release version: {release}\n')
-    elif vintage == 'vyatta':
-        ret_str = (f'/* Warning: Do not remove the following line. */\n'
-                +  f'/* === vyatta-config-version: "{ver_str}" === */\n'
-                +  f'/* Release version: {release} */\n')
-    else:
-        raise ValueError("Unknown config string vintage")
-
-    return ret_str
-
-def write_footer(file_name, vintage='vyos'):
-    """
-    Write version footer to file.
-    """
-    footer = system_footer(vintage=vintage)
-    if file_name:
-        with open(file_name, 'a') as f:
-            f.write(footer)
-    else:
-        sys.stdout.write(footer)
-
-def remove_footer(file_name):
-    """
-    Remove old version footer.
-    """
-    for line in fileinput.input(file_name, inplace=True):
-        if re.match(r'/\* Warning:.+ \*/$', line):
-            continue
-        if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line):
-            continue
-        if re.match(r'/\* Release version:.+ \*/$', line):
-            continue
-        if re.match('// vyos-config-version:.+', line):
-            continue
-        if re.match('// Warning:.+', line):
-            continue
-        if re.match('// Release version:.+', line):
-            continue
-        sys.stdout.write(line)
diff --git a/python/vyos/component_versions.py b/python/vyos/component_versions.py
new file mode 100644
index 000000000..90b458aae
--- /dev/null
+++ b/python/vyos/component_versions.py
@@ -0,0 +1,57 @@
+# Copyright 2017 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
+# 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, see <http://www.gnu.org/licenses/>.
+
+"""
+The version data looks like:
+
+/* Warning: Do not remove the following line. */
+/* === vyatta-config-version:
+"cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@8:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1"
+=== */
+/* Release version: 1.2.0-rolling+201806131737 */
+"""
+
+import re
+
+def get_component_version(string_line):
+    """
+    Get component version dictionary from string
+    return empty dictionary if string contains no config information
+    or raise error if component version string malformed
+    """
+    return_value = {}
+    if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line):
+
+        if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line):
+            raise ValueError("malformed configuration string: " + str(string_line))
+
+        for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
+            if pair[0] in return_value.keys():
+                raise ValueError("duplicate unit name: \"" + str(pair[0]) + "\" in string: \"" + string_line + "\"")
+            return_value[pair[0]] = int(pair[1])
+
+    return return_value
+
+
+def get_component_versions_from_file(config_file_name='/opt/vyatta/etc/config/config.boot'):
+    """
+    Get component version dictionary parsing config file line by line
+    """
+    f = open(config_file_name, 'r')
+    for line_in_config in f:
+        component_version = get_component_version(line_in_config)
+        if component_version:
+            return component_version
+    raise ValueError("no config string in file:", config_file_name)
diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py
new file mode 100644
index 000000000..29117a5d3
--- /dev/null
+++ b/python/vyos/formatversions.py
@@ -0,0 +1,109 @@
+# Copyright 2019 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
+# 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, see <http://www.gnu.org/licenses/>.
+
+import sys
+import os
+import re
+import fileinput
+
+def read_vyatta_versions(config_file):
+    config_file_versions = {}
+
+    with open(config_file, 'r') as config_file_handle:
+        for config_line in config_file_handle:
+            if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line):
+                if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line):
+                    raise ValueError("malformed configuration string: "
+                            "{}".format(config_line))
+
+                for pair in re.findall(r'([\w,-]+)@(\d+)', config_line):
+                    config_file_versions[pair[0]] = int(pair[1])
+
+
+    return config_file_versions
+
+def read_vyos_versions(config_file):
+    config_file_versions = {}
+
+    with open(config_file, 'r') as config_file_handle:
+        for config_line in config_file_handle:
+            if re.match(r'// vyos-config-version:.+', config_line):
+                if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line):
+                    raise ValueError("malformed configuration string: "
+                            "{}".format(config_line))
+
+                for pair in re.findall(r'([\w,-]+)@(\d+)', config_line):
+                    config_file_versions[pair[0]] = int(pair[1])
+
+    return config_file_versions
+
+def remove_versions(config_file):
+    """
+    Remove old version string.
+    """
+    for line in fileinput.input(config_file, inplace=True):
+        if re.match(r'/\* Warning:.+ \*/$', line):
+            continue
+        if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line):
+            continue
+        if re.match(r'/\* Release version:.+ \*/$', line):
+            continue
+        if re.match('// vyos-config-version:.+', line):
+            continue
+        if re.match('// Warning:.+', line):
+            continue
+        if re.match('// Release version:.+', line):
+            continue
+        sys.stdout.write(line)
+
+def format_versions_string(config_versions):
+    cfg_keys = list(config_versions.keys())
+    cfg_keys.sort()
+
+    component_version_strings = []
+
+    for key in cfg_keys:
+        cfg_vers = config_versions[key]
+        component_version_strings.append('{}@{}'.format(key, cfg_vers))
+
+    separator = ":"
+    component_version_string = separator.join(component_version_strings)
+
+    return component_version_string
+
+def write_vyatta_versions_foot(config_file, component_version_string,
+                                 os_version_string):
+    if config_file:
+        with open(config_file, 'a') as config_file_handle:
+            config_file_handle.write('/* Warning: Do not remove the following line. */\n')
+            config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string))
+            config_file_handle.write('/* Release version: {} */\n'.format(os_version_string))
+    else:
+        sys.stdout.write('/* Warning: Do not remove the following line. */\n')
+        sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string))
+        sys.stdout.write('/* Release version: {} */\n'.format(os_version_string))
+
+def write_vyos_versions_foot(config_file, component_version_string,
+                               os_version_string):
+    if config_file:
+        with open(config_file, 'a') as config_file_handle:
+            config_file_handle.write('// Warning: Do not remove the following line.\n')
+            config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string))
+            config_file_handle.write('// Release version: {}\n'.format(os_version_string))
+    else:
+        sys.stdout.write('// Warning: Do not remove the following line.\n')
+        sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string))
+        sys.stdout.write('// Release version: {}\n'.format(os_version_string))
+
diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py
index 266a2e58e..a2e0daabd 100644
--- a/python/vyos/migrator.py
+++ b/python/vyos/migrator.py
@@ -17,8 +17,10 @@ import sys
 import os
 import json
 import subprocess
+import vyos.version
 import vyos.defaults
-import vyos.component_version as component_version
+import vyos.systemversions as systemversions
+import vyos.formatversions as formatversions
 
 class MigratorError(Exception):
     pass
@@ -40,13 +42,13 @@ class Migrator(object):
         cfg_file = self._config_file
         component_versions = {}
 
-        cfg_versions = component_version.from_file(cfg_file, vintage='vyatta')
+        cfg_versions = formatversions.read_vyatta_versions(cfg_file)
 
         if cfg_versions:
             self._config_file_vintage = 'vyatta'
             component_versions = cfg_versions
 
-        cfg_versions = component_version.from_file(cfg_file, vintage='vyos')
+        cfg_versions = formatversions.read_vyos_versions(cfg_file)
 
         if cfg_versions:
             self._config_file_vintage = 'vyos'
@@ -150,11 +152,19 @@ class Migrator(object):
         """
         Write new versions string.
         """
+        versions_string = formatversions.format_versions_string(cfg_versions)
+
+        os_version_string = vyos.version.get_version()
+
         if self._config_file_vintage == 'vyatta':
-            component_version.write_footer(self._config_file, vintage='vyatta')
+            formatversions.write_vyatta_versions_foot(self._config_file,
+                                                      versions_string,
+                                                      os_version_string)
 
         if self._config_file_vintage == 'vyos':
-            component_version.write_footer(self._config_file, vintage='vyos')
+            formatversions.write_vyos_versions_foot(self._config_file,
+                                                    versions_string,
+                                                    os_version_string)
 
     def save_json_record(self, component_versions: dict):
         """
@@ -185,7 +195,7 @@ class Migrator(object):
             # This will force calling all migration scripts:
             cfg_versions = {}
 
-        sys_versions = component_version.from_system()
+        sys_versions = systemversions.get_system_component_version()
 
         # save system component versions in json file for easy reference
         self.save_json_record(sys_versions)
@@ -201,7 +211,7 @@ class Migrator(object):
         if not self._changed:
             return
 
-        component_version.remove_footer(cfg_file)
+        formatversions.remove_versions(cfg_file)
 
         self.write_config_file_versions(rev_versions)
 
@@ -222,7 +232,7 @@ class VirtualMigrator(Migrator):
         if not self._changed:
             return
 
-        component_version.remove_footer(cfg_file)
+        formatversions.remove_versions(cfg_file)
 
         self.write_config_file_versions(cfg_versions)
 
diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py
new file mode 100644
index 000000000..f2da76d4f
--- /dev/null
+++ b/python/vyos/systemversions.py
@@ -0,0 +1,46 @@
+# Copyright 2019 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
+# 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import re
+import sys
+import vyos.defaults
+from vyos.xml import component_version
+
+# legacy version, reading from the file names in
+# /opt/vyatta/etc/config-migrate/current
+def get_system_versions():
+    """
+    Get component versions from running system; critical failure if
+    unable to read migration directory.
+    """
+    system_versions = {}
+
+    try:
+        version_info = os.listdir(vyos.defaults.directories['current'])
+    except OSError as err:
+        print("OS error: {}".format(err))
+        sys.exit(1)
+
+    for info in version_info:
+        if re.match(r'[\w,-]+@\d+', info):
+            pair = info.split('@')
+            system_versions[pair[0]] = int(pair[1])
+
+    return system_versions
+
+# read from xml cache
+def get_system_component_version():
+    return component_version()
-- 
cgit v1.2.3