You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
12 KiB
317 lines
12 KiB
# Lint as: python2, python3
|
|
"""
|
|
Functions to handle software packages. The functions covered here aim to be
|
|
generic, with implementations that deal with different package managers, such
|
|
as dpkg and rpm.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
__author__ = 'lucasmr@br.ibm.com (Lucas Meneghel Rodrigues)'
|
|
|
|
import os, re
|
|
from autotest_lib.client.bin import os_dep, utils
|
|
from autotest_lib.client.common_lib import error
|
|
|
|
# As more package methods are implemented, this list grows up
|
|
KNOWN_PACKAGE_MANAGERS = ['rpm', 'dpkg']
|
|
|
|
|
|
def _rpm_info(rpm_package):
|
|
"""\
|
|
Private function that returns a dictionary with information about an
|
|
RPM package file
|
|
- type: Package management program that handles the file
|
|
- system_support: If the package management program is installed on the
|
|
system or not
|
|
- source: If it is a source (True) our binary (False) package
|
|
- version: The package version (or name), that is used to check against the
|
|
package manager if the package is installed
|
|
- arch: The architecture for which a binary package was built
|
|
- installed: Whether the package is installed (True) on the system or not
|
|
(False)
|
|
"""
|
|
# We will make good use of what the file command has to tell us about the
|
|
# package :)
|
|
file_result = utils.system_output('file ' + rpm_package)
|
|
package_info = {}
|
|
package_info['type'] = 'rpm'
|
|
try:
|
|
os_dep.command('rpm')
|
|
# Build the command strings that will be used to get package info
|
|
# s_cmd - Command to determine if package is a source package
|
|
# a_cmd - Command to determine package architecture
|
|
# v_cmd - Command to determine package version
|
|
# i_cmd - Command to determiine if package is installed
|
|
s_cmd = 'rpm -qp --qf %{SOURCE} ' + rpm_package + ' 2>/dev/null'
|
|
a_cmd = 'rpm -qp --qf %{ARCH} ' + rpm_package + ' 2>/dev/null'
|
|
v_cmd = 'rpm -qp ' + rpm_package + ' 2>/dev/null'
|
|
i_cmd = 'rpm -q ' + utils.system_output(v_cmd) + ' 2>&1 >/dev/null'
|
|
|
|
package_info['system_support'] = True
|
|
# Checking whether this is a source or src package
|
|
source = utils.system_output(s_cmd)
|
|
if source == '(none)':
|
|
package_info['source'] = False
|
|
else:
|
|
package_info['source'] = True
|
|
package_info['version'] = utils.system_output(v_cmd)
|
|
package_info['arch'] = utils.system_output(a_cmd)
|
|
# Checking if package is installed
|
|
try:
|
|
utils.system(i_cmd)
|
|
package_info['installed'] = True
|
|
except:
|
|
package_info['installed'] = False
|
|
|
|
except:
|
|
package_info['system_support'] = False
|
|
package_info['installed'] = False
|
|
# File gives a wealth of information about rpm packages.
|
|
# However, we can't trust all this info, as incorrectly
|
|
# packaged rpms can report some wrong values.
|
|
# It's better than nothing though :)
|
|
if len(file_result.split(' ')) == 6:
|
|
# Figure if package is a source package
|
|
if file_result.split(' ')[3] == 'src':
|
|
package_info['source'] = True
|
|
elif file_result.split(' ')[3] == 'bin':
|
|
package_info['source'] = False
|
|
else:
|
|
package_info['source'] = False
|
|
# Get architecture
|
|
package_info['arch'] = file_result.split(' ')[4]
|
|
# Get version
|
|
package_info['version'] = file_result.split(' ')[5]
|
|
elif len(file_result.split(' ')) == 5:
|
|
# Figure if package is a source package
|
|
if file_result.split(' ')[3] == 'src':
|
|
package_info['source'] = True
|
|
elif file_result.split(' ')[3] == 'bin':
|
|
package_info['source'] = False
|
|
else:
|
|
package_info['source'] = False
|
|
# When the arch param is missing on file, we assume noarch
|
|
package_info['arch'] = 'noarch'
|
|
# Get version
|
|
package_info['version'] = file_result.split(' ')[4]
|
|
else:
|
|
# If everything else fails...
|
|
package_info['source'] = False
|
|
package_info['arch'] = 'Not Available'
|
|
package_info['version'] = 'Not Available'
|
|
return package_info
|
|
|
|
|
|
def _dpkg_info(dpkg_package):
|
|
"""\
|
|
Private function that returns a dictionary with information about a
|
|
dpkg package file
|
|
- type: Package management program that handles the file
|
|
- system_support: If the package management program is installed on the
|
|
system or not
|
|
- source: If it is a source (True) our binary (False) package
|
|
- version: The package version (or name), that is used to check against the
|
|
package manager if the package is installed
|
|
- arch: The architecture for which a binary package was built
|
|
- installed: Whether the package is installed (True) on the system or not
|
|
(False)
|
|
"""
|
|
# We will make good use of what the file command has to tell us about the
|
|
# package :)
|
|
file_result = utils.system_output('file ' + dpkg_package)
|
|
package_info = {}
|
|
package_info['type'] = 'dpkg'
|
|
# There's no single debian source package as is the case
|
|
# with RPM
|
|
package_info['source'] = False
|
|
try:
|
|
os_dep.command('dpkg')
|
|
# Build the command strings that will be used to get package info
|
|
# a_cmd - Command to determine package architecture
|
|
# v_cmd - Command to determine package version
|
|
# i_cmd - Command to determiine if package is installed
|
|
a_cmd = 'dpkg -f ' + dpkg_package + ' Architecture 2>/dev/null'
|
|
v_cmd = 'dpkg -f ' + dpkg_package + ' Package 2>/dev/null'
|
|
i_cmd = 'dpkg -s ' + utils.system_output(v_cmd) + ' 2>/dev/null'
|
|
|
|
package_info['system_support'] = True
|
|
package_info['version'] = utils.system_output(v_cmd)
|
|
package_info['arch'] = utils.system_output(a_cmd)
|
|
# Checking if package is installed
|
|
package_status = utils.system_output(i_cmd, ignore_status=True)
|
|
not_inst_pattern = re.compile('not-installed', re.IGNORECASE)
|
|
dpkg_not_installed = re.search(not_inst_pattern, package_status)
|
|
if dpkg_not_installed:
|
|
package_info['installed'] = False
|
|
else:
|
|
package_info['installed'] = True
|
|
|
|
except:
|
|
package_info['system_support'] = False
|
|
package_info['installed'] = False
|
|
# The output of file is not as generous for dpkg files as
|
|
# it is with rpm files
|
|
package_info['arch'] = 'Not Available'
|
|
package_info['version'] = 'Not Available'
|
|
|
|
return package_info
|
|
|
|
|
|
def list_all():
|
|
"""Returns a list with the names of all currently installed packages."""
|
|
support_info = os_support()
|
|
installed_packages = []
|
|
|
|
if support_info['rpm']:
|
|
installed_packages += utils.system_output('rpm -qa').splitlines()
|
|
|
|
if support_info['dpkg']:
|
|
raw_list = utils.system_output('dpkg -l').splitlines()[5:]
|
|
for line in raw_list:
|
|
parts = line.split()
|
|
if parts[0] == "ii": # only grab "installed" packages
|
|
installed_packages.append("%s-%s" % (parts[1], parts[2]))
|
|
|
|
return installed_packages
|
|
|
|
|
|
def info(package):
|
|
"""\
|
|
Returns a dictionary with package information about a given package file:
|
|
- type: Package management program that handles the file
|
|
- system_support: If the package management program is installed on the
|
|
system or not
|
|
- source: If it is a source (True) our binary (False) package
|
|
- version: The package version (or name), that is used to check against the
|
|
package manager if the package is installed
|
|
- arch: The architecture for which a binary package was built
|
|
- installed: Whether the package is installed (True) on the system or not
|
|
(False)
|
|
|
|
Implemented package types:
|
|
- 'dpkg' - dpkg (debian, ubuntu) package files
|
|
- 'rpm' - rpm (red hat, suse) package files
|
|
Raises an exception if the package type is not one of the implemented
|
|
package types.
|
|
"""
|
|
if not os.path.isfile(package):
|
|
raise ValueError('invalid file %s to verify' % package)
|
|
# Use file and libmagic to determine the actual package file type.
|
|
file_result = utils.system_output('file ' + package)
|
|
for package_manager in KNOWN_PACKAGE_MANAGERS:
|
|
if package_manager == 'rpm':
|
|
package_pattern = re.compile('RPM', re.IGNORECASE)
|
|
elif package_manager == 'dpkg':
|
|
package_pattern = re.compile('Debian', re.IGNORECASE)
|
|
|
|
result = re.search(package_pattern, file_result)
|
|
|
|
if result and package_manager == 'rpm':
|
|
return _rpm_info(package)
|
|
elif result and package_manager == 'dpkg':
|
|
return _dpkg_info(package)
|
|
|
|
# If it's not one of the implemented package manager methods, there's
|
|
# not much that can be done, hence we throw an exception.
|
|
raise error.PackageError('Unknown package type %s' % file_result)
|
|
|
|
|
|
def install(package, nodeps = False):
|
|
"""\
|
|
Tries to install a package file. If the package is already installed,
|
|
it prints a message to the user and ends gracefully. If nodeps is set to
|
|
true, it will ignore package dependencies.
|
|
"""
|
|
my_package_info = info(package)
|
|
type = my_package_info['type']
|
|
system_support = my_package_info['system_support']
|
|
source = my_package_info['source']
|
|
installed = my_package_info['installed']
|
|
|
|
if not system_support:
|
|
e_msg = ('Client does not have package manager %s to handle %s install'
|
|
% (type, package))
|
|
raise error.PackageError(e_msg)
|
|
|
|
opt_args = ''
|
|
if type == 'rpm':
|
|
if nodeps:
|
|
opt_args = opt_args + '--nodeps'
|
|
install_command = 'rpm %s -U %s' % (opt_args, package)
|
|
if type == 'dpkg':
|
|
if nodeps:
|
|
opt_args = opt_args + '--force-depends'
|
|
install_command = 'dpkg %s -i %s' % (opt_args, package)
|
|
|
|
# RPM source packages can be installed along with the binary versions
|
|
# with this check
|
|
if installed and not source:
|
|
return 'Package %s is already installed' % package
|
|
|
|
# At this point, the most likely thing to go wrong is that there are
|
|
# unmet dependencies for the package. We won't cover this case, at
|
|
# least for now.
|
|
utils.system(install_command)
|
|
return 'Package %s was installed successfuly' % package
|
|
|
|
|
|
def convert(package, destination_format):
|
|
"""\
|
|
Convert packages with the 'alien' utility. If alien is not installed, it
|
|
throws a NotImplementedError exception.
|
|
returns: filename of the package generated.
|
|
"""
|
|
try:
|
|
os_dep.command('alien')
|
|
except:
|
|
e_msg = 'Cannot convert to %s, alien not installed' % destination_format
|
|
raise error.TestError(e_msg)
|
|
|
|
# alien supports converting to many formats, but its interesting to map
|
|
# convertions only for the implemented package types.
|
|
if destination_format == 'dpkg':
|
|
deb_pattern = re.compile('[A-Za-z0-9_.-]*[.][d][e][b]')
|
|
conv_output = utils.system_output('alien --to-deb %s 2>/dev/null'
|
|
% package)
|
|
converted_package = re.findall(deb_pattern, conv_output)[0]
|
|
elif destination_format == 'rpm':
|
|
rpm_pattern = re.compile('[A-Za-z0-9_.-]*[.][r][p][m]')
|
|
conv_output = utils.system_output('alien --to-rpm %s 2>/dev/null'
|
|
% package)
|
|
converted_package = re.findall(rpm_pattern, conv_output)[0]
|
|
else:
|
|
e_msg = 'Convertion to format %s not implemented' % destination_format
|
|
raise NotImplementedError(e_msg)
|
|
|
|
print('Package %s successfuly converted to %s' % \
|
|
(os.path.basename(package), os.path.basename(converted_package)))
|
|
return os.path.abspath(converted_package)
|
|
|
|
|
|
def os_support():
|
|
"""\
|
|
Returns a dictionary with host os package support info:
|
|
- rpm: True if system supports rpm packages, False otherwise
|
|
- dpkg: True if system supports dpkg packages, False otherwise
|
|
- conversion: True if the system can convert packages (alien installed),
|
|
or False otherwise
|
|
"""
|
|
support_info = {}
|
|
for package_manager in KNOWN_PACKAGE_MANAGERS:
|
|
try:
|
|
os_dep.command(package_manager)
|
|
support_info[package_manager] = True
|
|
except:
|
|
support_info[package_manager] = False
|
|
|
|
try:
|
|
os_dep.command('alien')
|
|
support_info['conversion'] = True
|
|
except:
|
|
support_info['conversion'] = False
|
|
|
|
return support_info
|