3 Software package management library.
5 This is an abstraction layer on top of the existing distributions high level
6 package managers. It supports package operations useful for testing purposes,
7 and multiple high level package managers (here called backends). If you want
8 to make this lib to support your particular package manager/distro, please
9 implement the given backend class.
11 @author: Higor Vieira Alves (halves@br.ibm.com)
12 @author: Lucas Meneghel Rodrigues (lmr@redhat.com)
13 @author: Ramon de Carvalho Valle (rcvalle@br.ibm.com)
15 @copyright: IBM 2008-2009
16 @copyright: Red Hat 2009-2010
18 import os
, re
, logging
, ConfigParser
, optparse
, random
, string
24 from autotest_lib
.client
.bin
import os_dep
, utils
25 from autotest_lib
.client
.common_lib
import error
26 from autotest_lib
.client
.common_lib
import logging_config
, logging_manager
29 def generate_random_string(length
):
31 Return a random string using alphanumeric characters.
33 @length: Length of the string that will be generated.
35 r
= random
.SystemRandom()
37 chars
= string
.letters
+ string
.digits
39 str += r
.choice(chars
)
44 class SoftwareManagerLoggingConfig(logging_config
.LoggingConfig
):
46 Used with the sole purpose of providing convenient logging setup
47 for the KVM test auxiliary programs.
49 def configure_logging(self
, results_dir
=None, verbose
=False):
50 super(SoftwareManagerLoggingConfig
, self
).configure_logging(
55 class SystemInspector(object):
57 System inspector class.
59 This may grow up to include more complete reports of operating system and
64 Probe system, and save information for future reference.
66 self
.distro
= utils
.get_os_vendor()
67 self
.high_level_pms
= ['apt-get', 'yum', 'zypper']
70 def get_package_management(self
):
72 Determine the supported package management systems present on the
73 system. If more than one package management system installed, try
74 to find the best supported system.
77 for high_level_pm
in self
.high_level_pms
:
79 os_dep
.command(high_level_pm
)
80 list_supported
.append(high_level_pm
)
85 if len(list_supported
) == 0:
87 if len(list_supported
) == 1:
88 pm_supported
= list_supported
[0]
89 elif len(list_supported
) > 1:
90 if 'apt-get' in list_supported
and self
.distro
in ['Debian', 'Ubuntu']:
91 pm_supported
= 'apt-get'
92 elif 'yum' in list_supported
and self
.distro
== 'Fedora':
95 pm_supported
= list_supported
[0]
97 logging
.debug('Package Manager backend: %s' % pm_supported
)
101 class SoftwareManager(object):
103 Package management abstraction layer.
105 It supports a set of common package operations for testing purposes, and it
106 uses the concept of a backend, a helper class that implements the set of
107 operations of a given package management tool.
113 Determines the best supported package management system for the given
114 operating system running and initializes the appropriate backend.
116 inspector
= SystemInspector()
117 backend_type
= inspector
.get_package_management()
118 if backend_type
== 'yum':
119 self
.backend
= YumBackend()
120 elif backend_type
== 'zypper':
121 self
.backend
= ZypperBackend()
122 elif backend_type
== 'apt-get':
123 self
.backend
= AptBackend()
125 raise NotImplementedError('Unimplemented package management '
126 'system: %s.' % backend_type
)
129 def check_installed(self
, name
, version
=None, arch
=None):
131 Check whether a package is installed on this system.
133 @param name: Package name.
134 @param version: Package version.
135 @param arch: Package architecture.
137 return self
.backend
.check_installed(name
, version
, arch
)
142 List all installed packages.
144 return self
.backend
.list_all()
147 def list_files(self
, name
):
149 Get a list of all files installed by package [name].
151 @param name: Package name.
153 return self
.backend
.list_files(name
)
156 def install(self
, name
):
158 Install package [name].
160 @param name: Package name.
162 return self
.backend
.install(name
)
165 def remove(self
, name
):
167 Remove package [name].
169 @param name: Package name.
171 return self
.backend
.remove(name
)
174 def add_repo(self
, url
):
176 Add package repo described by [url].
178 @param name: URL of the package repo.
180 return self
.backend
.add_repo(url
)
183 def remove_repo(self
, url
):
185 Remove package repo described by [url].
187 @param url: URL of the package repo.
189 return self
.backend
.remove_repo(url
)
194 Upgrade all packages available.
196 return self
.backend
.upgrade()
199 def provides(self
, file):
201 Returns a list of packages that provides a given capability to the
202 system (be it a binary, a library).
204 @param file: Path to the file.
206 return self
.backend
.provides(file)
209 def install_what_provides(self
, file):
211 Installs package that provides [file].
213 @param file: Path to file.
215 provides
= self
.provides(file)
216 if provides
is not None:
217 self
.install(provides
)
219 logging
.warning('No package seems to provide %s', file)
222 class RpmBackend(object):
224 This class implements operations executed with the rpm package manager.
226 rpm is a lower level package manager, used by higher level managers such
230 self
.lowlevel_base_cmd
= os_dep
.command('rpm')
233 def _check_installed_version(self
, name
, version
):
235 Helper for the check_installed public method.
237 @param name: Package name.
238 @param version: Package version.
240 cmd
= (self
.lowlevel_base_cmd
+ ' -q --qf %{VERSION} ' + name
+
242 inst_version
= utils
.system_output(cmd
)
244 if inst_version
>= version
:
250 def check_installed(self
, name
, version
=None, arch
=None):
252 Check if package [name] is installed.
254 @param name: Package name.
255 @param version: Package version.
256 @param arch: Package architecture.
259 cmd
= (self
.lowlevel_base_cmd
+ ' -q --qf %{ARCH} ' + name
+
261 inst_archs
= utils
.system_output(cmd
)
262 inst_archs
= inst_archs
.split('\n')
264 for inst_arch
in inst_archs
:
265 if inst_arch
== arch
:
266 return self
._check
_installed
_version
(name
, version
)
270 return self
._check
_installed
_version
(name
, version
)
272 cmd
= 'rpm -q ' + name
+ ' 2> /dev/null'
273 return (os
.system(cmd
) == 0)
278 List all installed packages.
280 installed_packages
= utils
.system_output('rpm -qa').splitlines()
281 return installed_packages
284 def list_files(self
, name
):
286 List files installed on the system by package [name].
288 @param name: Package name.
290 path
= os
.path
.abspath(name
)
291 if os
.path
.isfile(path
):
297 l_cmd
= 'rpm' + ' ' + option
+ ' ' + name
+ ' 2> /dev/null'
300 result
= utils
.system_output(l_cmd
)
301 list_files
= result
.split('\n')
303 except error
.CmdError
:
307 class DpkgBackend(object):
309 This class implements operations executed with the dpkg package manager.
311 dpkg is a lower level package manager, used by higher level managers such
315 self
.lowlevel_base_cmd
= os_dep
.command('dpkg')
318 def check_installed(self
, name
):
319 if os
.path
.isfile(name
):
320 n_cmd
= (self
.lowlevel_base_cmd
+ ' -f ' + name
+
321 ' Package 2>/dev/null')
322 name
= utils
.system_output(n_cmd
)
323 i_cmd
= self
.lowlevel_base_cmd
+ ' -s ' + name
+ ' 2>/dev/null'
324 # Checking if package is installed
325 package_status
= utils
.system_output(i_cmd
, ignore_status
=True)
326 not_inst_pattern
= re
.compile('not-installed', re
.IGNORECASE
)
327 dpkg_not_installed
= re
.search(not_inst_pattern
, package_status
)
328 if dpkg_not_installed
:
335 List all packages available in the system.
337 installed_packages
= []
338 raw_list
= utils
.system_output('dpkg -l').splitlines()[5:]
339 for line
in raw_list
:
341 if parts
[0] == "ii": # only grab "installed" packages
342 installed_packages
.append("%s-%s" % (parts
[1], parts
[2]))
345 def list_files(self
, package
):
347 List files installed by package [package].
349 @param package: Package name.
350 @return: List of paths installed by package.
352 if os
.path
.isfile(package
):
353 l_cmd
= self
.lowlevel_base_cmd
+ ' -c ' + package
355 l_cmd
= self
.lowlevel_base_cmd
+ ' -l ' + package
356 return utils
.system_output(l_cmd
).split('\n')
359 class YumBackend(RpmBackend
):
361 Implements the yum backend for software manager.
363 Set of operations for the yum package manager, commonly found on Yellow Dog
364 Linux and Red Hat based distributions, such as Fedora and Red Hat
369 Initializes the base command and the yum package repository.
371 super(YumBackend
, self
).__init
__()
372 executable
= os_dep
.command('yum')
373 base_arguments
= '-y'
374 self
.base_command
= executable
+ ' ' + base_arguments
375 self
.repo_file_path
= '/etc/yum.repos.d/autotest.repo'
376 self
.cfgparser
= ConfigParser
.ConfigParser()
377 self
.cfgparser
.read(self
.repo_file_path
)
378 y_cmd
= executable
+ ' --version | head -1'
379 self
.yum_version
= utils
.system_output(y_cmd
, ignore_status
=True)
380 logging
.debug('Yum backend initialized')
381 logging
.debug('Yum version: %s' % self
.yum_version
)
382 self
.yum_base
= yum
.YumBase()
387 Clean up the yum cache so new package information can be downloaded.
389 utils
.system("yum clean all")
392 def install(self
, name
):
394 Installs package [name]. Handles local installs.
396 if os
.path
.isfile(name
):
397 name
= os
.path
.abspath(name
)
398 command
= 'localinstall'
402 i_cmd
= self
.base_command
+ ' ' + command
+ ' ' + name
411 def remove(self
, name
):
413 Removes package [name].
415 @param name: Package name (eg. 'ipython').
417 r_cmd
= self
.base_command
+ ' ' + 'erase' + ' ' + name
425 def add_repo(self
, url
):
427 Adds package repository located on [url].
429 @param url: Universal Resource Locator of the repository.
431 # Check if we URL is already set
432 for section
in self
.cfgparser
.sections():
433 for option
, value
in self
.cfgparser
.items(section
):
434 if option
== 'url' and value
== url
:
437 # Didn't find it, let's set it up
439 section_name
= 'software_manager' + '_' + generate_random_string(4)
440 if not self
.cfgparser
.has_section(section_name
):
442 self
.cfgparser
.add_section(section_name
)
443 self
.cfgparser
.set(section_name
, 'name',
444 'Repository added by the autotest software manager.')
445 self
.cfgparser
.set(section_name
, 'url', url
)
446 self
.cfgparser
.set(section_name
, 'enabled', 1)
447 self
.cfgparser
.set(section_name
, 'gpgcheck', 0)
448 self
.cfgparser
.write(self
.repo_file_path
)
451 def remove_repo(self
, url
):
453 Removes package repository located on [url].
455 @param url: Universal Resource Locator of the repository.
457 for section
in self
.cfgparser
.sections():
458 for option
, value
in self
.cfgparser
.items(section
):
459 if option
== 'url' and value
== url
:
460 self
.cfgparser
.remove_section(section
)
461 self
.cfgparser
.write(self
.repo_file_path
)
466 Upgrade all available packages.
468 r_cmd
= self
.base_command
+ ' ' + 'update'
476 def provides(self
, name
):
478 Returns a list of packages that provides a given capability.
480 @param name: Capability name (eg, 'foo').
482 d_provides
= self
.yum_base
.searchPackageProvides(args
=[name
])
483 provides_list
= [key
for key
in d_provides
]
485 logging
.info("Package %s provides %s", provides_list
[0], name
)
486 return str(provides_list
[0])
491 class ZypperBackend(RpmBackend
):
493 Implements the zypper backend for software manager.
495 Set of operations for the zypper package manager, found on SUSE Linux.
499 Initializes the base command and the yum package repository.
501 super(ZypperBackend
, self
).__init
__()
502 self
.base_command
= os_dep
.command('zypper') + ' -n'
503 z_cmd
= self
.base_command
+ ' --version'
504 self
.zypper_version
= utils
.system_output(z_cmd
, ignore_status
=True)
505 logging
.debug('Zypper backend initialized')
506 logging
.debug('Zypper version: %s' % self
.zypper_version
)
509 def install(self
, name
):
511 Installs package [name]. Handles local installs.
513 @param name: Package Name.
515 path
= os
.path
.abspath(name
)
516 i_cmd
= self
.base_command
+ ' install -l ' + name
524 def add_repo(self
, url
):
526 Adds repository [url].
528 @param url: URL for the package repository.
530 ar_cmd
= self
.base_command
+ ' addrepo ' + url
538 def remove_repo(self
, url
):
540 Removes repository [url].
542 @param url: URL for the package repository.
544 rr_cmd
= self
.base_command
+ ' removerepo ' + url
552 def remove(self
, name
):
554 Removes package [name].
556 r_cmd
= self
.base_command
+ ' ' + 'erase' + ' ' + name
567 Upgrades all packages of the system.
569 u_cmd
= self
.base_command
+ ' update -l'
578 def provides(self
, name
):
580 Searches for what provides a given file.
582 @param name: File path.
584 p_cmd
= self
.base_command
+ ' what-provides ' + name
587 p_output
= utils
.system_output(p_cmd
).split('\n')[4:]
588 for line
in p_output
:
589 line
= [a
.strip() for a
in line
.split('|')]
591 state
, pname
, type, version
, arch
, repository
= line
592 if pname
not in list_provides
:
593 list_provides
.append(pname
)
596 if len(list_provides
) > 1:
597 logging
.warning('More than one package found, '
598 'opting by the first queue result')
600 logging
.info("Package %s provides %s", list_provides
[0], name
)
601 return list_provides
[0]
607 class AptBackend(DpkgBackend
):
609 Implements the apt backend for software manager.
611 Set of operations for the apt package manager, commonly found on Debian and
612 Debian based distributions, such as Ubuntu Linux.
616 Initializes the base command and the debian package repository.
618 super(AptBackend
, self
).__init
__()
619 executable
= os_dep
.command('apt-get')
620 self
.base_command
= executable
+ ' -y'
621 self
.repo_file_path
= '/etc/apt/sources.list.d/autotest'
622 self
.apt_version
= utils
.system_output('apt-get -v | head -1',
624 logging
.debug('Apt backend initialized')
625 logging
.debug('apt version: %s' % self
.apt_version
)
628 def install(self
, name
):
630 Installs package [name].
632 @param name: Package name.
635 i_cmd
= self
.base_command
+ ' ' + command
+ ' ' + name
644 def remove(self
, name
):
646 Remove package [name].
648 @param name: Package name.
652 r_cmd
= self
.base_command
+ ' ' + command
+ ' ' + flag
+ ' ' + name
661 def add_repo(self
, repo
):
663 Add an apt repository.
665 @param repo: Repository string. Example:
666 'deb http://archive.ubuntu.com/ubuntu/ maverick universe'
668 repo_file
= open(self
.repo_file_path
, 'a')
669 repo_file_contents
= repo_file
.read()
670 if repo
not in repo_file_contents
:
671 repo_file
.write(repo
)
674 def remove_repo(self
, repo
):
676 Remove an apt repository.
678 @param repo: Repository string. Example:
679 'deb http://archive.ubuntu.com/ubuntu/ maverick universe'
681 repo_file
= open(self
.repo_file_path
, 'r')
682 new_file_contents
= []
683 for line
in repo_file
.readlines
:
685 new_file_contents
.append(line
)
687 new_file_contents
= "\n".join(new_file_contents
)
688 repo_file
.open(self
.repo_file_path
, 'w')
689 repo_file
.write(new_file_contents
)
695 Upgrade all packages of the system with eventual new versions.
697 ud_command
= 'update'
698 ud_cmd
= self
.base_command
+ ' ' + ud_command
702 logging
.error("Apt package update failed")
703 up_command
= 'upgrade'
704 up_cmd
= self
.base_command
+ ' ' + up_command
712 def provides(self
, file):
714 Return a list of packages that provide [file].
716 @param file: File path.
718 if not self
.check_installed('apt-file'):
719 self
.install('apt-file')
720 command
= os_dep
.command('apt-file')
721 cache_update_cmd
= command
+ ' update'
723 utils
.system(cache_update_cmd
, ignore_status
=True)
725 logging
.error("Apt file cache update failed")
726 fu_cmd
= command
+ ' search ' + file
728 provides
= utils
.system_output(fu_cmd
).split('\n')
730 for line
in provides
:
733 line
= line
.split(':')
734 package
= line
[0].strip()
735 path
= line
[1].strip()
736 if path
== file and package
not in list_provides
:
737 list_provides
.append(package
)
740 if len(list_provides
) > 1:
741 logging
.warning('More than one package found, '
742 'opting by the first queue result')
744 logging
.info("Package %s provides %s", list_provides
[0], file)
745 return list_provides
[0]
751 if __name__
== '__main__':
752 parser
= optparse
.OptionParser(
753 "usage: %prog [install|remove|list-all|list-files|add-repo|remove-repo|"
754 "upgrade|what-provides|install-what-provides] arguments")
755 parser
.add_option('--verbose', dest
="debug", action
='store_true',
756 help='include debug messages in console output')
758 options
, args
= parser
.parse_args()
759 debug
= options
.debug
760 logging_manager
.configure_logging(SoftwareManagerLoggingConfig(),
762 software_manager
= SoftwareManager()
765 args
= " ".join(args
[1:])
769 if action
== 'install':
770 software_manager
.install(args
)
771 elif action
== 'remove':
772 software_manager
.remove(args
)
773 if action
== 'list-all':
774 software_manager
.list_all()
775 elif action
== 'list-files':
776 software_manager
.list_files(args
)
777 elif action
== 'add-repo':
778 software_manager
.add_repo(args
)
779 elif action
== 'remove-repo':
780 software_manager
.remove_repo(args
)
781 elif action
== 'upgrade':
782 software_manager
.upgrade()
783 elif action
== 'what-provides':
784 software_manager
.provides(args
)
785 elif action
== 'install-what-provides':
786 software_manager
.install_what_provides(args
)
787 elif action
== 'show-help':