File fix_v2_migration_script.patch of Package cobbler

Index: cobbler-3.3.3/scripts/migrate-data-v2-to-v3.py
===================================================================
--- cobbler-3.3.3.orig/scripts/migrate-data-v2-to-v3.py
+++ cobbler-3.3.3/scripts/migrate-data-v2-to-v3.py
@@ -1,5 +1,6 @@
 #!/usr/bin/python3
 
+import argparse
 import os
 import glob
 import simplejson
@@ -7,6 +8,54 @@ import subprocess
 
 import cobbler.api as capi
 
+from cobbler.settings.migrations.V3_3_0 import backup_dir
+
+
+COBBLER_COLLECTION_PATH = "/var/lib/cobbler/collections/"
+COBBLER_TEMPLATES_PATH = "/var/lib/cobbler/templates"
+
+OLD_COBBLER_TEMPLATES_PATH = "/var/lib/cobbler/kickstarts"
+OLD_RHN_TEMPLATES_PATH = "/var/lib/rhn/kickstarts"
+OLD_AUTOINSTALL_TEMPLATES_PATH = "/var/lib/cobbler/autoinstall_templates"
+
+TEMPLATES_PATHS = [
+    COBBLER_TEMPLATES_PATH,
+    OLD_COBBLER_TEMPLATES_PATH,
+    OLD_RHN_TEMPLATES_PATH,
+    OLD_AUTOINSTALL_TEMPLATES_PATH,
+]
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+    "--noapi",
+    action="store_true",
+    help="Do not try to connect to Cobbler API",
+    default=False,
+)
+parser.add_argument(
+    "--noconfigs",
+    action="store_true",
+    help="Do not use use old config.d collections dir schema",
+    default=False,
+)
+parser.add_argument(
+    "-c",
+    "--collections-path",
+    help="Path to Cobbler collections to migrate",
+    default="/var/lib/cobbler/config/",
+)
+parser.add_argument(
+    "--only-fix-autoinstall",
+    action="store_true",
+    help="Run migration of collections only for autoinstall attribute (Implies: --noapi --noconfigs)",
+    default=False,
+)
+
+args = parser.parse_args()
+if args.only_fix_autoinstall:
+    args.noapi = True
+    args.noconfigs = True
+
 
 def serialize_item(collection, item):
     """
@@ -16,9 +65,11 @@ def serialize_item(collection, item):
     @param item dictionary
     """
 
-    filename = "/var/lib/cobbler/collections/%s/%s" % (collection, item['name'])
+    filename = os.path.join(
+        COBBLER_COLLECTION_PATH, "%s/%s" % (collection, item["name"])
+    )
 
-    if capi.CobblerAPI().settings().serializer_pretty_json:
+    if not args.noapi and capi.CobblerAPI().settings().serializer_pretty_json:
         sort_keys = True
         indent = 4
     else:
@@ -32,16 +83,19 @@ def serialize_item(collection, item):
 
     fd.close()
 
+
 def deserialize_raw_old(collection_types):
 
     results = []
 
-    all_files = glob.glob("/var/lib/cobbler/config/%s/*" % collection_types)
+    all_files = glob.glob(
+        os.path.join(args.collections_path, "%s/*" % collection_types)
+    )
 
     for f in all_files:
         fd = open(f)
         json_data = fd.read()
-        _dict = simplejson.loads(json_data, encoding='utf-8')
+        _dict = simplejson.loads(json_data, encoding="utf-8")
         results.append(_dict)
         fd.close()
     return results
@@ -54,9 +108,10 @@ def substitute_paths(value):
             new_value.append(substitute_paths(item))
         value = new_value
     elif isinstance(value, str):
-        value = value.replace('/ks_mirror/','/distro_mirror/')
+        value = value.replace("/ks_mirror/", "/distro_mirror/")
     return value
 
+
 def transform_key(key, value):
     if key in transform:
         ret_value = transform[key](value)
@@ -65,73 +120,210 @@ def transform_key(key, value):
 
     return substitute_paths(ret_value)
 
+
 # Keys to add to various collections
 add = {
-  "distros": {
-    "boot_loader": "grub",
-  },
-  "profiles": {
-    "next_server": "<<inherit>>",
-  },
-  "systems": {
-    "boot_loader": "<<inherit>>",
-    "next_server": "<<inherit>>",
-    "power_identity_file": "",
-    "power_options": "",
-    "serial_baud_rate": "",
-    "serial_device": "",
-  },
+    "distros": {
+        "boot_loader": "grub",
+    },
+    "profiles": {
+        "next_server": "<<inherit>>",
+    },
+    "systems": {
+        "boot_loader": "<<inherit>>",
+        "next_server": "<<inherit>>",
+        "power_identity_file": "",
+        "power_options": "",
+        "serial_baud_rate": "",
+        "serial_device": "",
+    },
 }
 
 # Keys to remove
 remove = [
-  "ldap_enabled",
-  "ldap_type",
-  "monit_enabled",
-  "redhat_management_server",
-  "template_remote_kickstarts",
+    "ldap_enabled",
+    "ldap_type",
+    "monit_enabled",
+    "redhat_management_server",
+    "template_remote_kickstarts",
 ]
 
 # Keys to rename
 rename = {
-  "kickstart": "autoinstall",
-  "ks_meta": "autoinstall_meta",
+    "kickstart": "autoinstall",
+    "ks_meta": "autoinstall_meta",
 }
 
+
+def _fix_autoinstall(path):
+    # Absolute path, we need to calculate the relative path against its template dir
+    # Examples seen:
+    #   - "" (empty string)
+    #   - <<inherit>>
+    #   - /upload/something.cfg
+    #   - upload/something.cfg
+    #   - /var/lib/rhn/kickstarts/upload/something.cfg
+    #   - /var/lib/cobbler/kickstarts/upload/something.cfg
+    #   - /var/lib/cobbler/autoinstall_templates/default.ks
+    #
+
+    # Empty string found. Nothing to do.
+    if not path:
+        return path
+
+    # Absolute path found
+    if os.path.isabs(path):
+        for tp in TEMPLATES_PATHS:
+            if path.startswith(tp):
+                # Migrate absolute path to relative path.
+                # Example: /var/lib/cobbler/kickstarts/upload/something.cfg -> upload/something.cfg
+                new_path = os.path.relpath(path, tp)
+                print(
+                    "       * Migrate absolute autoinstall path: {} to {}".format(
+                        path, new_path
+                    )
+                )
+                return new_path
+
+            # Wrong absolute path found - maybe this is actually a relative path
+            # where we need to remove the initial slash.
+            # We check first if file would exist on the template paths to validate the migration
+            # Example: /upload/something.cfg -> upload/something.cfg
+            elif os.path.isfile(os.path.join(tp, path.lstrip("/"))):
+                return path.lstrip("/")
+
+        # Absolute path outside of expected template paths.
+        # We do not migrate the content and warn user.
+        print(
+            "       * ERROR: Migrate absolute autoinstall path: {} was not possible. "
+            "Cannot find file.".format(path)
+        )
+        print(
+            "       * Please fix 'autoinstall' attribute for this collection manually."
+        )
+        return path
+
+    # Here the value is already relative path. It might be wrong and we need to fix it
+    else:
+        for tp in TEMPLATES_PATHS:
+            if os.path.isfile(os.path.join(tp, path)):
+                # The value is correct, nothing to do
+                return path
+
+        # Here, it means the file is not found, probably wrong and we need to
+        # figure out where the file is really located.
+        # In case we find a collision, so different possible candidates
+        # then we raise an error and do nothing.
+        items_found = []
+        new_path = None
+        for tp in TEMPLATES_PATHS:
+            for curdir, subdirs, files in os.walk(tp):
+                if os.path.basename(path) in files:
+                    # We fix the wrong value and set the correct one
+                    # in case there is not collisions
+                    # Example: something.cfg -> upload/something.cfg
+                    # Example: foobar/something.cfg -> upload/something.cfg
+                    items_found.append(os.path.join(curdir, os.path.basename(path)))
+                    if not new_path:
+                        new_path = os.path.relpath(
+                            os.path.join(curdir, os.path.basename(path)), tp
+                        )
+
+        # Collisions in names -> raise error and do nothing
+        if len(items_found) > 1:
+            print(
+                "       * ERROR: Migrate relative autoinstall: {} was not possible as"
+                "there are multiple candidates for this file.".format(path)
+            )
+            for item in items_found:
+                print("       -- {}".format(item))
+            print(
+                "       * Please fix 'autoinstall' attribute manually for this collection. "
+                "Put a path which is relative to /var/lib/cobbler/templates"
+            )
+            return path
+        # Template file found and no collisions -> return the fixed value
+        elif new_path:
+            print(
+                "       * Fixed wrong value for autoinstall path: {} to {}".format(
+                    path, new_path
+                )
+            )
+            return new_path
+
+        # At this point, we didn't find the template
+        # so we return same value and do not migrate it
+        if path != "<<inherit>>":
+            print(
+                "       * ERROR: Migrate relative autoinstall: {} was not possible. "
+                "Cannot find file.".format(path)
+            )
+            print(
+                "       * Please fix 'autoinstall' attribute for this collection manually."
+            )
+        return path
+
+
 # Keys to transform - use new key name if renamed
 transform = {
-  "autoinstall": os.path.basename,
+    "autoinstall": _fix_autoinstall,
 }
 
+# Create a backup of stored collections before performing migration
+print("Creating a backup of %s" % args.collections_path)
+backup_dir(args.collections_path)
+
 # Convert the old collections to new collections
-for old_type in ['distros.d','files.d','images.d','mgmtclasses.d','packages.d','profiles.d','repos.d','systems.d']:
+for old_type in [
+    "distros.d",
+    "files.d",
+    "images.d",
+    "mgmtclasses.d",
+    "packages.d",
+    "profiles.d",
+    "repos.d",
+    "systems.d",
+]:
     new_type = old_type[:-2]
+
+    # Bypass old collection.d folder schema
+    if args.noconfigs:
+        old_type = new_type
+
     # Load old files
     old_collection = deserialize_raw_old(old_type)
     print("Processing %s:" % old_type)
 
     for old_item in old_collection:
-        print("    Processing %s" % old_item['name'])
+        print("    Processing %s" % old_item["name"])
         new_item = {}
         for key in old_item:
-            if key in remove:
-                continue
-            if key in rename:
-                new_item[rename[key]] = transform_key(rename[key], old_item[key])
-                continue
+            if not args.only_fix_autoinstall:
+                if key in remove:
+                    continue
+                if key in rename:
+                    new_item[rename[key]] = transform_key(rename[key], old_item[key])
+                    continue
             new_item[key] = transform_key(key, old_item[key])
 
-        if new_type in add:
-            new_item.update(add[new_type])
+        if not args.only_fix_autoinstall:
+            if new_type in add:
+                new_item.update(add[new_type])
+
+            # Switch "virtio26" and "generic26" OS version to "generic" distro breed
+            if new_item.get("os_version") in ["generic26", "virtio26"]:
+                new_item["breed"] = "generic"
 
         serialize_item(new_type, new_item)
 
 path_rename = [
-  ("/var/lib/cobbler/kickstarts", "/var/lib/cobbler/templates"),
-  ("/var/www/cobbler/ks_mirror", "/var/www/cobbler/distro_mirror"),
+    (OLD_AUTOINSTALL_TEMPLATES_PATH, COBBLER_TEMPLATES_PATH),
+    (OLD_RHN_TEMPLATES_PATH, COBBLER_TEMPLATES_PATH),
+    (OLD_COBBLER_TEMPLATES_PATH, COBBLER_TEMPLATES_PATH),
+    ("/var/www/cobbler/ks_mirror", "/var/www/cobbler/distro_mirror"),
 ]
 
 # Copy paths
 for old_path, new_path in path_rename:
     if os.path.isdir(old_path):
-        subprocess.run(["cp", "-al", "%s/*" % old_path, "%s/" % new_path], shell=True)
+        subprocess.run("cp -al %s/* %s/" % (old_path, new_path), shell=True)
openSUSE Build Service is sponsored by