From bc83f0065e155f4fd7c3343f88d38184cc06f980 Mon Sep 17 00:00:00 2001
From: Uwe Gansert <ug@suse.de>
Date: Tue, 5 Apr 2011 13:11:52 +0200
Subject: [PATCH] added support for suse-distro import

---
 cobbler/modules/manage_import_suse.py |  772 +++++++++++++++++++++++++++++++++
 kickstarts/autoyast_sample.xml        |   45 ++
 2 files changed, 817 insertions(+), 0 deletions(-)
 create mode 100644 cobbler/modules/manage_import_suse.py
 create mode 100644 kickstarts/autoyast_sample.xml

diff --git a/cobbler/modules/manage_import_suse.py b/cobbler/modules/manage_import_suse.py
new file mode 100644
index 0000000..802800e
--- /dev/null
+++ b/cobbler/modules/manage_import_suse.py
@@ -0,0 +1,772 @@
+"""
+--
+-- Copyright (c) 2011 Novell
+-- Uwe Gansert <ug@suse.de>
+--
+-- This software is licensed to you under the GNU General Public License,
+-- version 2 (GPLv2). There is NO WARRANTY for this software, express or
+-- implied, including the implied warranties of MERCHANTABILITY or FITNESS
+-- FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+-- along with this software; if not, see
+-- http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+--
+--
+
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sys
+import glob
+import traceback
+import errno
+from utils import popen2
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+# FIXME: add --quiet depending on if not --verbose?
+RSYNC_CMD =  "rsync -a %s '%s' %s --exclude-from=/etc/cobbler/rsync.exclude --progress"
+
+def register():
+   """
+   The mandatory cobbler module registration hook.
+   """
+   return "manage/import"
+
+
+class ImportSuseManager:
+
+    def __init__(self,config,logger):
+        """
+        Constructor
+        """
+        self.logger        = logger
+        self.config        = config
+        self.api           = config.api
+        self.distros       = config.distros()
+        self.profiles      = config.profiles()
+        self.systems       = config.systems()
+        self.settings      = config.settings()
+        self.repos         = config.repos()
+        self.templar       = templar.Templar(config)
+
+    # required function for import modules
+    def what(self):
+        return "import/suse"
+
+    # required function for import modules
+    def check_for_signature(self,path,cli_breed):
+       signatures = [
+          'suse'
+       ]
+
+       #self.logger.info("scanning %s for a redhat-based distro signature" % path)
+       for signature in signatures:
+           d = os.path.join(path,signature)
+           if os.path.exists(d):
+               self.logger.info("Found a SUSE compatible signature: %s" % signature)
+               return (True,signature)
+
+       if cli_breed and cli_breed in self.get_valid_breeds():
+           self.logger.info("Warning: No distro signature for kernel at %s, using value from command line" % path)
+           return (True,None)
+
+       return (False,None)
+
+    # required function for import modules
+    def run(self,pkgdir,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None,breed=None,os_version=None):
+        self.pkgdir = pkgdir
+        self.mirror = mirror
+        self.mirror_name = mirror_name
+        self.network_root = network_root
+        self.kickstart_file = kickstart_file
+        self.rsync_flags = rsync_flags
+        self.arch = arch
+        self.breed = breed
+        self.os_version = os_version
+
+        # some fixups for the XMLRPC interface, which does not use "None"
+        if self.arch == "":           self.arch           = None
+        if self.mirror == "":         self.mirror         = None
+        if self.mirror_name == "":    self.mirror_name    = None
+        if self.kickstart_file == "": self.kickstart_file = None
+        if self.os_version == "":     self.os_version     = None
+        if self.rsync_flags == "":    self.rsync_flags    = None
+        if self.network_root == "":   self.network_root   = None
+
+        # If no breed was specified on the command line, set it to "suse" for this module
+        if self.breed == None:
+            self.breed = "suse"
+
+        # debug log stuff for testing
+        #self.logger.info("self.pkgdir = %s" % str(self.pkgdir))
+        #self.logger.info("self.mirror = %s" % str(self.mirror))
+        #self.logger.info("self.mirror_name = %s" % str(self.mirror_name))
+        #self.logger.info("self.network_root = %s" % str(self.network_root))
+        #self.logger.info("self.kickstart_file = %s" % str(self.kickstart_file))
+        #self.logger.info("self.rsync_flags = %s" % str(self.rsync_flags))
+        #self.logger.info("self.arch = %s" % str(self.arch))
+        #self.logger.info("self.breed = %s" % str(self.breed))
+        #self.logger.info("self.os_version = %s" % str(self.os_version))
+
+        # both --import and --name are required arguments
+
+        if self.mirror is None:
+            utils.die(self.logger,"import failed.  no --path specified")
+        if self.mirror_name is None:
+            utils.die(self.logger,"import failed.  no --name specified")
+
+        # if --arch is supplied, validate it to ensure it's valid
+
+        if self.arch is not None and self.arch != "":
+            self.arch = self.arch.lower()
+            if self.arch == "x86":
+                # be consistent
+                self.arch = "i386"
+            if self.arch in [ 'i486', 'i586', 'i686' ]:
+                self.arch = "i386"
+            if self.arch not in self.get_valid_arches():
+                utils.die(self.logger,"arch must be one of: %s" % string.join(self.get_valid_arches(),", "))
+
+        # if we're going to do any copying, set where to put things
+        # and then make sure nothing is already there.
+
+        self.path = os.path.normpath( "%s/ks_mirror/%s" % (self.settings.webdir, self.mirror_name) )
+        self.rootdir = os.path.normpath( "%s/ks_mirror/%s" % (self.settings.webdir, self.mirror_name) )
+        if os.path.exists(self.path) and self.arch is None:
+            # FIXME : Raise exception even when network_root is given ?
+            utils.die(self.logger,"Something already exists at this import location (%s).  You must specify --arch to avoid potentially overwriting existing files." % self.path)
+
+        # import takes a --kickstart for forcing selection that can't be used in all circumstances
+
+        if self.kickstart_file and not self.breed:
+            utils.die(self.logger,"Kickstart file can only be specified when a specific breed is selected")
+
+        if self.os_version and not self.breed:
+            utils.die(self.logger,"OS version can only be specified when a specific breed is selected")
+
+        if self.breed and self.breed.lower() not in self.get_valid_breeds():
+            utils.die(self.logger,"Supplied import breed is not supported by this module")
+
+        # if --arch is supplied, make sure the user is not importing a path with a different
+        # arch, which would just be silly.
+
+        if self.arch:
+            # append the arch path to the name if the arch is not already
+            # found in the name.
+            for x in self.get_valid_arches():
+                if self.path.lower().find(x) != -1:
+                    if self.arch != x :
+                        utils.die(self.logger,"Architecture found on pathname (%s) does not fit the one given in command line (%s)"%(x,self.arch))
+                    break
+            else:
+                # FIXME : This is very likely removed later at get_proposed_name, and the guessed arch appended again
+                self.path += ("-%s" % self.arch)
+
+        # make the output path and mirror content but only if not specifying that a network
+        # accessible support location already exists (this is --available-as on the command line)
+
+        if self.network_root is None:
+            # we need to mirror (copy) the files
+
+            utils.mkdir(self.path)
+
+            # prevent rsync from creating the directory name twice
+            # if we are copying via rsync
+
+            if not self.mirror.endswith("/"):
+                self.mirror = "%s/" % self.mirror
+
+            if self.mirror.startswith("http://") or self.mirror.startswith("ftp://") or self.mirror.startswith("nfs://"):
+
+                # http mirrors are kind of primative.  rsync is better.
+                # that's why this isn't documented in the manpage and we don't support them.
+                # TODO: how about adding recursive FTP as an option?
+
+                utils.die(self.logger,"unsupported protocol")
+
+            else:
+
+                # good, we're going to use rsync..
+                # we don't use SSH for public mirrors and local files.
+                # presence of user@host syntax means use SSH
+
+                spacer = ""
+                if not self.mirror.startswith("rsync://") and not self.mirror.startswith("/"):
+                    spacer = ' -e "ssh" '
+                rsync_cmd = RSYNC_CMD
+                if self.rsync_flags:
+                    rsync_cmd = rsync_cmd + " " + self.rsync_flags
+
+                # kick off the rsync now
+
+                utils.run_this(rsync_cmd, (spacer, self.mirror, self.path), self.logger)
+
+        else:
+
+            # rather than mirroring, we're going to assume the path is available
+            # over http, ftp, and nfs, perhaps on an external filer.  scanning still requires
+            # --mirror is a filesystem path, but --available-as marks the network path
+
+            if not os.path.exists(self.mirror):
+                utils.die(self.logger, "path does not exist: %s" % self.mirror)
+
+            # find the filesystem part of the path, after the server bits, as each distro
+            # URL needs to be calculated relative to this.
+
+            if not self.network_root.endswith("/"):
+                self.network_root = self.network_root + "/"
+            self.path = os.path.normpath( self.mirror )
+            valid_roots = [ "nfs://", "ftp://", "http://" ]
+            for valid_root in valid_roots:
+                if self.network_root.startswith(valid_root):
+                    break
+            else:
+                utils.die(self.logger, "Network root given to --available-as must be nfs://, ftp://, or http://")
+            if self.network_root.startswith("nfs://"):
+                try:
+                    (a,b,rest) = self.network_root.split(":",3)
+                except:
+                    utils.die(self.logger, "Network root given to --available-as is missing a colon, please see the manpage example.")
+
+        # now walk the filesystem looking for distributions that match certain patterns
+
+        self.logger.info("adding distros")
+        distros_added = []
+        # FIXME : search below self.path for isolinux configurations or known directories from TRY_LIST
+        os.path.walk(self.path, self.distro_adder, distros_added)
+
+        # find out if we can auto-create any repository records from the install tree
+
+        if self.network_root is None:
+            self.logger.info("associating repos")
+            # FIXME: this automagic is not possible (yet) without mirroring
+            self.repo_finder(distros_added)
+
+        # find the most appropriate answer files for each profile object
+
+        self.logger.info("associating kickstarts")
+        self.kickstart_finder(distros_added)
+
+        # ensure bootloaders are present
+        self.api.pxegen.copy_bootloaders()
+
+        return True
+
+    # required function for import modules
+    def get_valid_arches(self):
+        return ["i386", "ia64", "ppc", "ppc64", "s390", "s390x", "x86_64", "x86",]
+
+    # required function for import modules
+    def get_valid_breeds(self):
+        return ["suse",]
+
+    # required function for import modules
+    def get_valid_os_versions(self):
+        return []
+
+    def get_valid_repo_breeds(self):
+        return ["yast", "rsync", "yum"]
+
+    def get_release_files(self):
+        data = glob.glob(os.path.join(self.get_pkgdir(), "*release-*"))
+        data2 = []
+        for x in data:
+            b = os.path.basename(x)
+# FIXME
+#            if b.find("fedora") != -1 or \
+#               b.find("redhat") != -1 or \
+#               b.find("centos") != -1:
+#                data2.append(x)
+        return data2
+
+    def get_tree_location(self, distro):
+        """
+        Once a distribution is identified, find the part of the distribution
+        that has the URL in it that we want to use for kickstarting the
+        distribution, and create a ksmeta variable $tree that contains this.
+        """
+
+        base = self.get_rootdir()
+
+        if self.network_root is None:
+            dest_link = os.path.join(self.settings.webdir, "links", distro.name)
+            # create the links directory only if we are mirroring because with
+            # SELinux Apache can't symlink to NFS (without some doing)
+            if not os.path.exists(dest_link):
+                try:
+                    os.symlink(base + "-" + distro.arch, dest_link)
+                except:
+                    # this shouldn't happen but I've seen it ... debug ...
+                    self.logger.warning("symlink creation failed: %(base)s, %(dest)s") % { "base" : base, "dest" : dest_link }
+            # how we set the tree depends on whether an explicit network_root was specified
+            tree = "http://@@http_server@@/cblr/links/%s" % (distro.name)
+            self.set_install_tree(distro, tree)
+        else:
+            # where we assign the kickstart source is relative to our current directory
+            # and the input start directory in the crawl.  We find the path segments
+            # between and tack them on the network source path to find the explicit
+            # network path to the distro that Anaconda can digest.
+            tail = self.path_tail(self.path, base)
+            tree = self.network_root[:-1] + tail
+            self.set_install_tree(distro, tree)
+
+        return
+
+    def repo_finder(self, distros_added):
+        """
+        This routine looks through all distributions and tries to find
+        any applicable repositories in those distributions for post-install
+        usage.
+        """
+
+        for distro in distros_added:
+            self.logger.info("traversing distro %s" % distro.name)
+            # FIXME : Shouldn't decide this the value of self.network_root ?
+            if distro.kernel.find("ks_mirror") != -1:
+                basepath = os.path.dirname(distro.kernel)
+                top = self.get_rootdir()
+                self.logger.info("descent into %s" % top)
+                # FIXME : The location of repo definition is known from breed
+                os.path.walk(top, self.repo_scanner, distro)
+            else:
+                self.logger.info("this distro isn't mirrored")
+
+    def repo_scanner(self,distro,dirname,fnames):
+        """
+        This is an os.path.walk routine that looks for potential yum repositories
+        to be added to the configuration for post-install usage.
+        """
+
+        matches = {}
+        for x in fnames:
+            if x == "base" or x == "repodata":
+                self.logger.info("processing repo at : %s" % dirname)
+                # only run the repo scanner on directories that contain a comps.xml
+                gloob1 = glob.glob("%s/%s/*comps*.xml" % (dirname,x))
+                if len(gloob1) >= 1:
+                    if matches.has_key(dirname):
+                        self.logger.info("looks like we've already scanned here: %s" % dirname)
+                        continue
+                    self.logger.info("need to process repo/comps: %s" % dirname)
+                    matches[dirname] = 1
+                else:
+                    self.logger.info("directory %s is missing xml comps file, skipping" % dirname)
+                    continue
+
+    def distro_adder(self,distros_added,dirname,fnames):
+        """
+        This is an os.path.walk routine that finds distributions in the directory
+        to be scanned and then creates them.
+        """
+
+        # FIXME: If there are more than one kernel or initrd image on the same directory,
+        # results are unpredictable
+
+        initrd = None
+        kernel = None
+
+        # make sure we don't mismatch PAE and non-PAE types
+        pae_initrd = None
+        pae_kernel = None
+
+        for x in fnames:
+            adtls = []
+
+            fullname = os.path.join(dirname,x)
+            if os.path.islink(fullname) and os.path.isdir(fullname):
+                if fullname.startswith(self.path):
+                    # Prevent infinite loop with Sci Linux 5
+                    self.logger.warning("avoiding symlink loop")
+                    continue
+                self.logger.info("following symlink: %s" % fullname)
+                os.path.walk(fullname, self.distro_adder, distros_added)
+
+            if ( x.startswith("initrd") or x.startswith("ramdisk.image.gz") ) and x != "initrd.size":
+                if x.find("PAE") == -1:
+                    initrd = os.path.join(dirname,x)
+                else:
+                    pae_initrd = os.path.join(dirname, x)
+
+            if ( x.startswith("vmlinu") or x.startswith("kernel.img") or x.startswith("linux") ) and x.find("initrd") == -1:
+                if x.find("PAE") == -1:
+                    kernel = os.path.join(dirname,x)
+                else:
+                    pae_kernel = os.path.join(dirname, x)
+
+            # if we've collected a matching kernel and initrd pair, turn the in and add them to the list
+            if initrd is not None and kernel is not None and dirname.find("isolinux") == -1:
+                adtls.append(self.add_entry(dirname,kernel,initrd))
+                kernel = None
+                initrd = None
+            elif pae_initrd is not None and pae_kernel is not None and dirname.find("isolinux") == -1:
+                adtls.append(self.add_entry(dirname,pae_kernel,pae_initrd))
+                pae_kernel = None
+                pae_initrd = None
+
+            for adtl in adtls:
+                distros_added.extend(adtl)
+
+    def add_entry(self,dirname,kernel,initrd):
+        """
+        When we find a directory with a valid kernel/initrd in it, create the distribution objects
+        as appropriate and save them.  This includes creating xen and rescue distros/profiles
+        if possible.
+        """
+
+        proposed_name = self.get_proposed_name(dirname,kernel)
+        proposed_arch = self.get_proposed_arch(dirname)
+
+        if self.arch and proposed_arch and self.arch != proposed_arch:
+            utils.die(self.logger,"Arch from pathname (%s) does not match with supplied one %s"%(proposed_arch,self.arch))
+
+        archs = self.learn_arch_from_tree()
+        if not archs:
+            if self.arch:
+                archs.append( self.arch )
+        else:
+            if self.arch and self.arch not in archs:
+                utils.die(self.logger, "Given arch (%s) not found on imported tree %s"%(self.arch,self.get_pkgdir()))
+        if proposed_arch:
+            if archs and proposed_arch not in archs:
+                self.logger.warning("arch from pathname (%s) not found on imported tree %s" % (proposed_arch,self.get_pkgdir()))
+                return
+
+            archs = [ proposed_arch ]
+
+        if len(archs)>1:
+            if self.breed in [ "suse" ]:
+                self.logger.warning("directory %s holds multiple arches : %s" % (dirname, archs))
+                return
+            self.logger.warning("- Warning : Multiple archs found : %s" % (archs))
+
+        distros_added = []
+
+        for pxe_arch in archs:
+            name = proposed_name + "-" + pxe_arch
+            existing_distro = self.distros.find(name=name)
+
+            if existing_distro is not None:
+                self.logger.warning("skipping import, as distro name already exists: %s" % name)
+                continue
+
+            else:
+                self.logger.info("creating new distro: %s" % name)
+                distro = self.config.new_distro()
+
+            if name.find("-autoboot") != -1:
+                # this is an artifact of some EL-3 imports
+                continue
+
+            distro.set_name(name)
+            distro.set_kernel(kernel)
+            distro.set_initrd(initrd)
+            distro.set_arch(pxe_arch)
+            distro.set_breed(self.breed)
+            distro.set_kernel_options("install=http://@@http_server@@/cblr/links/%s" % (name))
+            # If a version was supplied on command line, we set it now
+            if self.os_version:
+                distro.set_os_version(self.os_version)
+
+            self.distros.add(distro,save=True)
+            distros_added.append(distro)
+
+            existing_profile = self.profiles.find(name=name)
+
+            # see if the profile name is already used, if so, skip it and
+            # do not modify the existing profile
+
+            if existing_profile is None:
+                self.logger.info("creating new profile: %s" % name)
+                #FIXME: The created profile holds a default kickstart, and should be breed specific
+                profile = self.config.new_profile()
+            else:
+                self.logger.info("skipping existing profile, name already exists: %s" % name)
+                continue
+
+            # save our minimal profile which just points to the distribution and a good
+            # default answer file
+
+            profile.set_name(name)
+            profile.set_distro(name)
+            profile.set_kickstart(self.kickstart_file)
+
+            # depending on the name of the profile we can define a good virt-type
+            # for usage with koan
+
+            if name.find("-xen") != -1:
+                profile.set_virt_type("xenpv")
+            elif name.find("vmware") != -1:
+                profile.set_virt_type("vmware")
+            else:
+                profile.set_virt_type("qemu")
+
+            # save our new profile to the collection
+
+            self.profiles.add(profile,save=True)
+
+            # Create a rescue image as well, if this is not a xen distro
+            # but only for red hat profiles
+
+            # this code disabled as it seems to be adding "-rescue" to
+            # distros that are /not/ rescue related, which is wrong.
+            # left as a FIXME for those who find this feature interesting.
+            #if name.find("-xen") == -1 and self.breed == "redhat":
+            #    rescue_name = 'rescue-' + name
+            #    existing_profile = self.profiles.find(name=rescue_name)
+            #
+            #    if existing_profile is None:
+            #        self.logger.info("creating new profile: %s" % rescue_name)
+            #        profile = self.config.new_profile()
+            #    else:
+            #        continue
+            #
+            #    profile.set_name(rescue_name)
+            #    profile.set_distro(name)
+            #    profile.set_virt_type("qemu")
+            #    profile.kernel_options['rescue'] = None
+            #    profile.kickstart = '/var/lib/cobbler/kickstarts/pxerescue.ks'
+            #
+            #    self.profiles.add(profile,save=True)
+
+        return distros_added
+
+    def get_proposed_name(self,dirname,kernel=None):
+        """
+        Given a directory name where we have a kernel/initrd pair, try to autoname
+        the distribution (and profile) object based on the contents of that path
+        """
+
+        if self.network_root is not None:
+            name = self.mirror_name + "-".join(self.path_tail(os.path.dirname(self.path),dirname).split("/"))
+        else:
+            # remove the part that says /var/www/cobbler/ks_mirror/name
+            name = "-".join(dirname.split("/")[5:])
+
+        if kernel is not None and kernel.find("PAE") != -1:
+            name = name + "-PAE"
+        if kernel is not None and kernel.find("xen") != -1:
+            name = name + "-xen"
+
+        # we have our kernel in ../boot/<arch>/vmlinuz-xen and
+        # .../boot/<arch>/loader/vmlinuz
+        #
+        name = name.replace("-loader","")
+        name = name.replace("-boot","")
+
+        # some paths above the media root may have extra path segments we want
+        # to clean up
+        name = name.replace("-os","")
+        name = name.replace("-tree","")
+        name = name.replace("srv-www-cobbler-", "")
+        name = name.replace("var-www-cobbler-", "")
+        name = name.replace("ks_mirror-","")
+        name = name.replace("--","-")
+
+        for separator in [ '-' , '_'  , '.' ] :
+            for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc32", "ppc", "x86" , "s390x", "s390" , "386" , "amd" ]:
+                name = name.replace("%s%s" % ( separator , arch ),"")
+
+        return name
+
+    def get_proposed_arch(self,dirname):
+        """
+        Given an directory name, can we infer an architecture from a path segment?
+        """
+        if dirname.find("x86_64") != -1 or dirname.find("amd") != -1:
+            return "x86_64"
+        if dirname.find("ia64") != -1:
+            return "ia64"
+        if dirname.find("i386") != -1 or dirname.find("386") != -1 or dirname.find("x86") != -1:
+            return "i386"
+        if dirname.find("s390x") != -1:
+            return "s390x"
+        if dirname.find("s390") != -1:
+            return "s390"
+        if dirname.find("ppc64") != -1 or dirname.find("chrp") != -1:
+            return "ppc64"
+        if dirname.find("ppc32") != -1:
+            return "ppc"
+        if dirname.find("ppc") != -1:
+            return "ppc"
+        return None
+
+    def arch_walker(self,foo,dirname,fnames):
+        """
+        See docs on learn_arch_from_tree.
+
+        The TRY_LIST is used to speed up search, and should be dropped for default importer
+        Searched kernel names are kernel-header, linux-headers-, kernel-largesmp, kernel-hugemem
+
+        This method is useful to get the archs, but also to package type and a raw guess of the breed
+        """
+
+        # try to find a kernel header RPM and then look at it's arch.
+        for x in fnames:
+            if self.match_kernelarch_file(x):
+                for arch in self.get_valid_arches():
+                    if x.find(arch) != -1:
+                        foo[arch] = 1
+                for arch in [ "i686" , "amd64" ]:
+                    if x.find(arch) != -1:
+                        foo[arch] = 1
+
+    def kickstart_finder(self,distros_added):
+        """
+        For all of the profiles in the config w/o a kickstart, use the
+        given kickstart file, or look at the kernel path, from that,
+        see if we can guess the distro, and if we can, assign a kickstart
+        if one is available for it.
+        """
+        for profile in self.profiles:
+            distro = self.distros.find(name=profile.get_conceptual_parent().name)
+            if distro is None or not (distro in distros_added):
+                continue
+
+            kdir = os.path.dirname(distro.kernel)
+            if self.kickstart_file == None:
+                for rpm in self.get_release_files():
+                    results = self.scan_pkg_filename(rpm)
+                    # FIXME : If os is not found on tree but set with CLI, no kickstart is searched
+                    if results is None:
+                        self.logger.warning("No version found on imported tree")
+                        continue
+                    (flavor, major, minor) = results
+                    version , ks = self.set_variance(flavor, major, minor, distro.arch)
+                    if self.os_version:
+                        if self.os_version != version:
+                            utils.die(self.logger,"CLI version differs from tree : %s vs. %s" % (self.os_version,version))
+                    ds = self.get_datestamp()
+                    distro.set_comment("%s.%s" % (version, int(minor)))
+                    distro.set_os_version(version)
+                    if ds is not None:
+                        distro.set_tree_build_time(ds)
+                    profile.set_kickstart(ks)
+                    self.profiles.add(profile,save=True)
+
+            self.configure_tree_location(distro)
+            self.distros.add(distro,save=True) # re-save
+            self.api.serialize()
+
+    def configure_tree_location(self, distro):
+        """
+        Once a distribution is identified, find the part of the distribution
+        that has the URL in it that we want to use for kickstarting the
+        distribution, and create a ksmeta variable $tree that contains this.
+        """
+
+        base = self.get_rootdir()
+
+        if self.network_root is None:
+            dest_link = os.path.join(self.settings.webdir, "links", distro.name)
+            # create the links directory only if we are mirroring because with
+            # SELinux Apache can't symlink to NFS (without some doing)
+            if not os.path.exists(dest_link):
+                try:
+                    os.symlink(base + "-" + distro.arch, dest_link)
+                except:
+                    # this shouldn't happen but I've seen it ... debug ...
+                    self.logger.warning("symlink creation failed: %(base)s, %(dest)s") % { "base" : base, "dest" : dest_link }
+            # how we set the tree depends on whether an explicit network_root was specified
+            tree = "http://@@http_server@@/cblr/links/%s" % (distro.name)
+            self.set_install_tree( distro, tree)
+        else:
+            # where we assign the kickstart source is relative to our current directory
+            # and the input start directory in the crawl.  We find the path segments
+            # between and tack them on the network source path to find the explicit
+            # network path to the distro that Anaconda can digest.
+            tail = utils.path_tail(self.path, base)
+            tree = self.network_root[:-1] + tail
+            self.set_install_tree( distro, tree)
+
+    def get_rootdir(self):
+        return self.rootdir
+
+    def get_pkgdir(self):
+        if not self.pkgdir:
+            return None
+        return os.path.join(self.get_rootdir(),self.pkgdir)
+
+    def set_install_tree(self, distro, url):
+        distro.ks_meta["tree"] = url
+
+    def learn_arch_from_tree(self):
+        """
+        If a distribution is imported from DVD, there is a good chance the path doesn't
+        contain the arch and we should add it back in so that it's part of the
+        meaningful name ... so this code helps figure out the arch name.  This is important
+        for producing predictable distro names (and profile names) from differing import sources
+        """
+        result = {}
+        # FIXME : this is called only once, should not be a walk
+        if self.get_pkgdir():
+            os.path.walk(self.get_pkgdir(), self.arch_walker, result)
+        if result.pop("amd64",False):
+            result["x86_64"] = 1
+        if result.pop("i686",False):
+            result["i386"] = 1
+        return result.keys()
+
+    def match_kernelarch_file(self, filename):
+        """
+        Is the given filename a kernel filename?
+        """
+
+        if not filename.endswith("rpm") and not filename.endswith("deb"):
+            return False
+        for match in ["kernel-header", "kernel-source", "kernel-smp", "kernel-default", "kernel-desktop", "linux-headers-", "kernel-devel", "kernel-"]:
+            if filename.find(match) != -1:
+                return True
+        return False
+
+    def scan_pkg_filename(self, rpm):
+        """
+        Determine what the distro is based on the release package filename.
+        """
+
+        rpm = os.path.basename(rpm)
+
+        return ("suse", 1, 1)
+
+    def get_datestamp(self):
+        """
+        Based on a RedHat tree find the creation timestamp
+        """
+        base = self.get_rootdir()
+        if os.path.exists("%s/.discinfo" % base):
+            discinfo = open("%s/.discinfo" % base, "r")
+            datestamp = discinfo.read().split("\n")[0]
+            discinfo.close()
+        else:
+            return 0
+        return float(datestamp)
+
+    def set_variance(self, flavor, major, minor, arch):
+        """
+        find the profile kickstart and set the distro breed/os-version based on what
+        we can find out from the rpm filenames and then return the kickstart
+        path to use.
+        """
+
+        os_version = "suse"
+
+        kickbase = "/var/lib/cobbler/kickstarts"
+        return os_version, "autoyast_sample.xml"
+
+# ==========================================================================
+
+def get_import_manager(config,logger):
+    return ImportSuseManager(config,logger)
diff --git a/kickstarts/autoyast_sample.xml b/kickstarts/autoyast_sample.xml
new file mode 100644
index 0000000..728aaa3
--- /dev/null
+++ b/kickstarts/autoyast_sample.xml
@@ -0,0 +1,45 @@
+<!--
+
+ Copyright (c) 2011 Novell
+ Uwe Gansert ug@suse.de
+
+ This software is licensed to you under the GNU General Public License,
+ version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ along with this software; if not, see
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+
+-->
+<?xml version="1.0"?>
+<!DOCTYPE profile>
+<profile xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns">
+  <networking>
+    <keep_install_network config:type="boolean">true</keep_install_network>
+  </networking>
+  <software>
+    <patterns config:type="list">
+      <pattern>base</pattern>
+    </patterns>
+  </software>
+  <users config:type="list">
+    <user>
+      <encrypted config:type="boolean">false</encrypted>
+      <fullname>root</fullname>
+      <gid>0</gid>
+      <home>/root</home>
+      <password_settings>
+        <expire></expire>
+        <flag></flag>
+        <inact></inact>
+        <max></max>
+        <min></min>
+        <warn></warn>
+      </password_settings>
+      <shell>/bin/bash</shell>
+      <uid>0</uid>
+      <user_password>s3cr3t</user_password>
+      <username>root</username>
+    </user>
+  </users>
+</profile>
-- 
1.7.3.4

