From 0e63712195465c9bf0bf55c369b86961d54dfaac Mon Sep 17 00:00:00 2001
From: John Estabrook <jestabro@vyos.io>
Date: Thu, 27 Oct 2022 12:24:44 -0500
Subject: T4291: consolidate component version string read/write functions

---
 python/vyos/component_version.py  | 192 ++++++++++++++++++++++++++++++++++++++
 python/vyos/component_versions.py |  57 -----------
 python/vyos/formatversions.py     | 109 ----------------------
 python/vyos/migrator.py           |  32 +++----
 python/vyos/systemversions.py     |  46 ---------
 5 files changed, 205 insertions(+), 231 deletions(-)
 create mode 100644 python/vyos/component_version.py
 delete mode 100644 python/vyos/component_versions.py
 delete mode 100644 python/vyos/formatversions.py
 delete mode 100644 python/vyos/systemversions.py

(limited to 'python/vyos')

diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
new file mode 100644
index 000000000..a4e318d08
--- /dev/null
+++ b/python/vyos/component_version.py
@@ -0,0 +1,192 @@
+# 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
+from vyos.defaults import directories
+
+DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot')
+
+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=DEFAULT_CONFIG_PATH, vintage='vyos'):
+    """
+    Get component version dictionary parsing config file line by line
+    """
+    with open(config_file_name, 'r') as f:
+        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():
+    """
+    Get system component version dict from legacy location.
+    This is for a transitional sanity check; the directory will eventually
+    be removed.
+    """
+    system_versions = {}
+    legacy_dir = directories['current']
+
+    # To be removed:
+    if not os.path.isdir(legacy_dir):
+        return system_versions
+
+    try:
+        version_info = os.listdir(legacy_dir)
+    except OSError as err:
+        sys.exit(repr(err))
+
+    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 version_footer(ver: dict, vintage='vyos') -> str:
+    """
+    Version footer as string.
+    """
+    ver_str = format_string(ver)
+    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 system_footer(vintage='vyos') -> str:
+    """
+    System version footer as string.
+    """
+    ver_d = from_system()
+    return version_footer(ver_d, vintage=vintage)
+
+def write_version_footer(ver: dict, file_name, vintage='vyos'):
+    """
+    Write version footer to file.
+    """
+    footer = version_footer(ver=ver, vintage=vintage)
+    if file_name:
+        with open(file_name, 'a') as f:
+            f.write(footer)
+    else:
+        sys.stdout.write(footer)
+
+def write_system_footer(file_name, vintage='vyos'):
+    """
+    Write system version footer to file.
+    """
+    ver_d = from_system()
+    return write_version_footer(ver_d, file_name=file_name, vintage=vintage)
+
+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
deleted file mode 100644
index 90b458aae..000000000
--- a/python/vyos/component_versions.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# 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
deleted file mode 100644
index 29117a5d3..000000000
--- a/python/vyos/formatversions.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# 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 c6e3435ca..45ea8b0eb 100644
--- a/python/vyos/migrator.py
+++ b/python/vyos/migrator.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -17,10 +17,8 @@ import sys
 import os
 import json
 import subprocess
-import vyos.version
 import vyos.defaults
-import vyos.systemversions as systemversions
-import vyos.formatversions as formatversions
+import vyos.component_version as component_version
 
 class MigratorError(Exception):
     pass
@@ -42,13 +40,13 @@ class Migrator(object):
         cfg_file = self._config_file
         component_versions = {}
 
-        cfg_versions = formatversions.read_vyatta_versions(cfg_file)
+        cfg_versions = component_version.from_file(cfg_file, vintage='vyatta')
 
         if cfg_versions:
             self._config_file_vintage = 'vyatta'
             component_versions = cfg_versions
 
-        cfg_versions = formatversions.read_vyos_versions(cfg_file)
+        cfg_versions = component_version.from_file(cfg_file, vintage='vyos')
 
         if cfg_versions:
             self._config_file_vintage = 'vyos'
@@ -157,19 +155,15 @@ 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':
-            formatversions.write_vyatta_versions_foot(self._config_file,
-                                                      versions_string,
-                                                      os_version_string)
+            component_version.write_version_footer(cfg_versions,
+                                                   self._config_file,
+                                                   vintage='vyatta')
 
         if self._config_file_vintage == 'vyos':
-            formatversions.write_vyos_versions_foot(self._config_file,
-                                                    versions_string,
-                                                    os_version_string)
+            component_version.write_version_footer(cfg_versions,
+                                                   self._config_file,
+                                                   vintage='vyos')
 
     def save_json_record(self, component_versions: dict):
         """
@@ -200,7 +194,7 @@ class Migrator(object):
             # This will force calling all migration scripts:
             cfg_versions = {}
 
-        sys_versions = systemversions.get_system_component_version()
+        sys_versions = component_version.from_system()
 
         # save system component versions in json file for easy reference
         self.save_json_record(sys_versions)
@@ -216,7 +210,7 @@ class Migrator(object):
         if not self._changed:
             return
 
-        formatversions.remove_versions(cfg_file)
+        component_version.remove_footer(cfg_file)
 
         self.write_config_file_versions(rev_versions)
 
@@ -237,7 +231,7 @@ class VirtualMigrator(Migrator):
         if not self._changed:
             return
 
-        formatversions.remove_versions(cfg_file)
+        component_version.remove_footer(cfg_file)
 
         self.write_config_file_versions(cfg_versions)
 
diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py
deleted file mode 100644
index f2da76d4f..000000000
--- a/python/vyos/systemversions.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# 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