#!/usr/bin/python
"""
Software package management library.

This is an abstraction layer on top of the existing distributions high level
package managers. It supports package operations useful for testing purposes,
and multiple high level package managers (here called backends). If you want
to make this lib to support your particular package manager/distro, please
implement the given backend class.

@author: Higor Vieira Alves (halves@br.ibm.com)
@author: Lucas Meneghel Rodrigues (lmr@redhat.com)
@author: Ramon de Carvalho Valle (rcvalle@br.ibm.com)

@copyright: IBM 2008-2009
@copyright: Red Hat 2009-2010
"""
import os, re, logging, ConfigParser, optparse, random, string
try:
    import yum
except Exception:
    pass
try:
    import autotest.common as common
except ImportError:
    import common
from autotest_lib.client.bin import os_dep, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import logging_config, logging_manager


def generate_random_string(length):
    """
    Return a random string using alphanumeric characters.

    @length: Length of the string that will be generated.
    """
    r = random.SystemRandom()
    str = ""
    chars = string.letters + string.digits
    while length > 0:
        str += r.choice(chars)
        length -= 1
    return str


class SoftwareManagerLoggingConfig(logging_config.LoggingConfig):
    """
    Used with the sole purpose of providing convenient logging setup
    for the KVM test auxiliary programs.
    """
    def configure_logging(self, results_dir=None, verbose=False):
        super(SoftwareManagerLoggingConfig, self).configure_logging(
                                                            use_console=True,
                                                            verbose=verbose)


class SystemInspector(object):
    """
    System inspector class.

    This may grow up to include more complete reports of operating system and
    machine properties.
    """
    def __init__(self):
        """
        Probe system, and save information for future reference.
        """
        self.distro = utils.get_os_vendor()
        self.high_level_pms = ['apt-get', 'yum', 'zypper']


    def get_package_management(self):
        """
        Determine the supported package management systems present on the
        system. If more than one package management system installed, try
        to find the best supported system.
        """
        list_supported = []
        for high_level_pm in self.high_level_pms:
            try:
                os_dep.command(high_level_pm)
                list_supported.append(high_level_pm)
            except Exception:
                pass

        pm_supported = None
        if len(list_supported) == 0:
            pm_supported = None
        if len(list_supported) == 1:
            pm_supported = list_supported[0]
        elif len(list_supported) > 1:
            if 'apt-get' in list_supported and self.distro in ['Debian', 'Ubuntu']:
                pm_supported = 'apt-get'
            elif 'yum' in list_supported and self.distro == 'Fedora':
                pm_supported = 'yum'
            else:
                pm_supported = list_supported[0]

        logging.debug('Package Manager backend: %s' % pm_supported)
        return pm_supported


class SoftwareManager(object):
    """
    Package management abstraction layer.

    It supports a set of common package operations for testing purposes, and it
    uses the concept of a backend, a helper class that implements the set of
    operations of a given package management tool.
    """
    def __init__(self):
        """
        Class constructor.

        Determines the best supported package management system for the given
        operating system running and initializes the appropriate backend.
        """
        inspector = SystemInspector()
        backend_type = inspector.get_package_management()
        if backend_type == 'yum':
            self.backend = YumBackend()
        elif backend_type == 'zypper':
            self.backend = ZypperBackend()
        elif backend_type == 'apt-get':
            self.backend = AptBackend()
        else:
            raise NotImplementedError('Unimplemented package management '
                                      'system: %s.' % backend_type)


    def check_installed(self, name, version=None, arch=None):
        """
        Check whether a package is installed on this system.

        @param name: Package name.
        @param version: Package version.
        @param arch: Package architecture.
        """
        return self.backend.check_installed(name, version, arch)


    def list_all(self):
        """
        List all installed packages.
        """
        return self.backend.list_all()


    def list_files(self, name):
        """
        Get a list of all files installed by package [name].

        @param name: Package name.
        """
        return self.backend.list_files(name)


    def install(self, name):
        """
        Install package [name].

        @param name: Package name.
        """
        return self.backend.install(name)


    def remove(self, name):
        """
        Remove package [name].

        @param name: Package name.
        """
        return self.backend.remove(name)


    def add_repo(self, url):
        """
        Add package repo described by [url].

        @param name: URL of the package repo.
        """
        return self.backend.add_repo(url)


    def remove_repo(self, url):
        """
        Remove package repo described by [url].

        @param url: URL of the package repo.
        """
        return self.backend.remove_repo(url)


    def upgrade(self):
        """
        Upgrade all packages available.
        """
        return self.backend.upgrade()


    def provides(self, file):
        """
        Returns a list of packages that provides a given capability to the
        system (be it a binary, a library).

        @param file: Path to the file.
        """
        return self.backend.provides(file)


    def install_what_provides(self, file):
        """
        Installs package that provides [file].

        @param file: Path to file.
        """
        provides = self.provides(file)
        if provides is not None:
            self.install(provides)
        else:
            logging.warning('No package seems to provide %s', file)


class RpmBackend(object):
    """
    This class implements operations executed with the rpm package manager.

    rpm is a lower level package manager, used by higher level managers such
    as yum and zypper.
    """
    def __init__(self):
        self.lowlevel_base_cmd = os_dep.command('rpm')


    def _check_installed_version(self, name, version):
        """
        Helper for the check_installed public method.

        @param name: Package name.
        @param version: Package version.
        """
        cmd = (self.lowlevel_base_cmd + ' -q --qf %{VERSION} ' + name +
               ' 2> /dev/null')
        inst_version = utils.system_output(cmd)

        if inst_version >= version:
            return True
        else:
            return False


    def check_installed(self, name, version=None, arch=None):
        """
        Check if package [name] is installed.

        @param name: Package name.
        @param version: Package version.
        @param arch: P
ackage architecture.
        """
        if arch:
            cmd = (self.lowlevel_base_cmd + ' -q --qf %{ARCH} ' + name +
                   ' 2> /dev/null')
            inst_archs = utils.s
ystem_output(cmd)
            inst_archs = inst_archs.split('\n')

            for inst_arch in inst_archs:
                if inst_arch == arch:
                    return self._check_installed_version(name, version)
            return False

        elif version:
            return self._check_installed_version(name, version)
        else:
            cmd = 'rpm -q ' + name + ' 2> /dev/null'
            return (os.system(cmd) == 0)


    def list_all(self):
        """
        List all installed packages.
        """
        installed_packages = utils.system_ou