| 1 | '''This is the bcfg2 support for apt-get''' |
|---|
| 2 | __revision__ = '$Revision$' |
|---|
| 3 | |
|---|
| 4 | # suppress apt API warnings |
|---|
| 5 | import warnings |
|---|
| 6 | warnings.filterwarnings("ignore", "apt API not stable yet", |
|---|
| 7 | FutureWarning) |
|---|
| 8 | warnings.filterwarnings("ignore", "Accessed deprecated property Package.installedVersion, please see the Version class for alternatives.", DeprecationWarning) |
|---|
| 9 | warnings.filterwarnings("ignore", "Accessed deprecated property Package.candidateVersion, please see the Version class for alternatives.", DeprecationWarning) |
|---|
| 10 | import apt.cache |
|---|
| 11 | import os |
|---|
| 12 | import Bcfg2.Client.Tools |
|---|
| 13 | |
|---|
| 14 | class APT(Bcfg2.Client.Tools.Tool): |
|---|
| 15 | '''The Debian toolset implements package and service operations and inherits |
|---|
| 16 | the rest from Toolset.Toolset''' |
|---|
| 17 | name = 'APT' |
|---|
| 18 | __execs__ = ['/usr/bin/debsums', '/usr/bin/apt-get', '/usr/bin/dpkg'] |
|---|
| 19 | __important__ = ["/etc/apt/sources.list", |
|---|
| 20 | "/var/cache/debconf/config.dat", |
|---|
| 21 | "/var/cache/debconf/templates.dat", |
|---|
| 22 | '/etc/passwd', '/etc/group', |
|---|
| 23 | '/etc/apt/apt.conf', '/etc/dpkg/dpkg.cfg'] |
|---|
| 24 | __handles__ = [('Package', 'deb')] |
|---|
| 25 | __req__ = {'Package': ['name', 'version']} |
|---|
| 26 | pkgcmd = 'apt-get ' + \ |
|---|
| 27 | '-o DPkg::Options::=--force-overwrite ' + \ |
|---|
| 28 | '-o DPkg::Options::=--force-confold ' + \ |
|---|
| 29 | '--reinstall ' + \ |
|---|
| 30 | '-q=2 ' + \ |
|---|
| 31 | '--force-yes ' + \ |
|---|
| 32 | '-y install %s' |
|---|
| 33 | |
|---|
| 34 | def __init__(self, logger, cfg, setup): |
|---|
| 35 | Bcfg2.Client.Tools.Tool.__init__(self, logger, cfg, setup) |
|---|
| 36 | self.cfg = cfg |
|---|
| 37 | os.environ["DEBIAN_FRONTEND"] = 'noninteractive' |
|---|
| 38 | self.actions = {} |
|---|
| 39 | if self.setup['kevlar'] and not self.setup['dryrun']: |
|---|
| 40 | self.cmd.run("dpkg --force-confold --configure --pending") |
|---|
| 41 | self.cmd.run("apt-get clean") |
|---|
| 42 | self.pkg_cache = apt.cache.Cache() |
|---|
| 43 | self.pkg_cache.update() |
|---|
| 44 | self.pkg_cache = apt.cache.Cache() |
|---|
| 45 | |
|---|
| 46 | def FindExtra(self): |
|---|
| 47 | '''Find extra packages''' |
|---|
| 48 | packages = [entry.get('name') for entry in self.getSupportedEntries()] |
|---|
| 49 | extras = [(p.name, p.installedVersion) for p in self.pkg_cache |
|---|
| 50 | if p.isInstalled and p.name not in packages] |
|---|
| 51 | return [Bcfg2.Client.XML.Element('Package', name=name, \ |
|---|
| 52 | type='deb', version=version) \ |
|---|
| 53 | for (name, version) in extras] |
|---|
| 54 | |
|---|
| 55 | def VerifyDebsums(self, entry, modlist): |
|---|
| 56 | output = self.cmd.run("/usr/bin/debsums -as %s" % entry.get('name'))[1] |
|---|
| 57 | if len(output) == 1 and "no md5sums for" in output[0]: |
|---|
| 58 | self.logger.info("Package %s has no md5sums. Cannot verify" % \ |
|---|
| 59 | entry.get('name')) |
|---|
| 60 | entry.set('qtext', "Reinstall Package %s-%s to setup md5sums? (y/N) " \ |
|---|
| 61 | % (entry.get('name'), entry.get('version'))) |
|---|
| 62 | return False |
|---|
| 63 | files = [] |
|---|
| 64 | for item in output: |
|---|
| 65 | if "checksum mismatch" in item: |
|---|
| 66 | files.append(item.split()[-1]) |
|---|
| 67 | elif "can't open" in item: |
|---|
| 68 | files.append(item.split()[5]) |
|---|
| 69 | elif "is not installed" in item: |
|---|
| 70 | self.logger.error("Package %s is not fully installed" \ |
|---|
| 71 | % entry.get('name')) |
|---|
| 72 | else: |
|---|
| 73 | self.logger.error("Got Unsupported pattern %s from debsums" \ |
|---|
| 74 | % item) |
|---|
| 75 | files.append(item) |
|---|
| 76 | # We check if there is file in the checksum to do |
|---|
| 77 | if files: |
|---|
| 78 | # if files are found there we try to be sure our modlist is sane |
|---|
| 79 | # with erroneous symlinks |
|---|
| 80 | modlist = [os.path.realpath(filename) for filename in modlist] |
|---|
| 81 | bad = [filename for filename in files if filename not in modlist] |
|---|
| 82 | if bad: |
|---|
| 83 | self.logger.info("Package %s failed validation. Bad files are:" % \ |
|---|
| 84 | entry.get('name')) |
|---|
| 85 | self.logger.info(bad) |
|---|
| 86 | entry.set('qtext', |
|---|
| 87 | "Reinstall Package %s-%s to fix failing files? (y/N) " % \ |
|---|
| 88 | (entry.get('name'), entry.get('version'))) |
|---|
| 89 | return False |
|---|
| 90 | return True |
|---|
| 91 | |
|---|
| 92 | def VerifyPackage(self, entry, modlist, checksums=True): |
|---|
| 93 | '''Verify package for entry''' |
|---|
| 94 | if not 'version' in entry.attrib: |
|---|
| 95 | self.logger.info("Cannot verify unversioned package %s" % |
|---|
| 96 | (entry.attrib['name'])) |
|---|
| 97 | return False |
|---|
| 98 | pkgname = entry.get('name') |
|---|
| 99 | if not self.pkg_cache.has_key(pkgname) \ |
|---|
| 100 | or not self.pkg_cache[pkgname].isInstalled: |
|---|
| 101 | self.logger.info("Package %s not installed" % (entry.get('name'))) |
|---|
| 102 | entry.set('current_exists', 'false') |
|---|
| 103 | return False |
|---|
| 104 | |
|---|
| 105 | pkg = self.pkg_cache[pkgname] |
|---|
| 106 | if entry.get('version') == 'auto': |
|---|
| 107 | if self.pkg_cache._depcache.IsUpgradable(pkg._pkg): |
|---|
| 108 | desiredVersion = pkg.candidateVersion |
|---|
| 109 | else: |
|---|
| 110 | desiredVersion = pkg.installedVersion |
|---|
| 111 | elif entry.get('version') == 'any': |
|---|
| 112 | desiredVersion = pkg.installedVersion |
|---|
| 113 | else: |
|---|
| 114 | desiredVersion = entry.get('version') |
|---|
| 115 | if desiredVersion != pkg.installedVersion: |
|---|
| 116 | entry.set('current_version', pkg.installedVersion) |
|---|
| 117 | entry.set('qtext', "Modify Package %s (%s -> %s)? (y/N) " % \ |
|---|
| 118 | (entry.get('name'), entry.get('current_version'), |
|---|
| 119 | entry.get('version'))) |
|---|
| 120 | return False |
|---|
| 121 | else: |
|---|
| 122 | # version matches |
|---|
| 123 | if not self.setup['quick'] and entry.get('verify', 'true') == 'true' \ |
|---|
| 124 | and checksums: |
|---|
| 125 | pkgsums = self.VerifyDebsums(entry, modlist) |
|---|
| 126 | return pkgsums |
|---|
| 127 | return True |
|---|
| 128 | |
|---|
| 129 | def Remove(self, packages): |
|---|
| 130 | '''Deal with extra configuration detected''' |
|---|
| 131 | pkgnames = " ".join([pkg.get('name') for pkg in packages]) |
|---|
| 132 | self.pkg_cache = apt.cache.Cache() |
|---|
| 133 | if len(packages) > 0: |
|---|
| 134 | self.logger.info('Removing packages:') |
|---|
| 135 | self.logger.info(pkgnames) |
|---|
| 136 | for pkg in pkgnames.split(" "): |
|---|
| 137 | try: |
|---|
| 138 | self.pkg_cache[pkg].markDelete(purge=True) |
|---|
| 139 | except: |
|---|
| 140 | self.pkg_cache[pkg].markDelete() |
|---|
| 141 | try: |
|---|
| 142 | self.pkg_cache.commit() |
|---|
| 143 | except SystemExit: |
|---|
| 144 | # thank you python-apt 0.6 |
|---|
| 145 | pass |
|---|
| 146 | self.pkg_cache = apt.cache.Cache() |
|---|
| 147 | self.modified += packages |
|---|
| 148 | self.extra = self.FindExtra() |
|---|
| 149 | |
|---|
| 150 | def Install(self, packages, states): |
|---|
| 151 | # it looks like you can't install arbitrary versions of software |
|---|
| 152 | # out of the pkg cache, we will still need to call apt-get |
|---|
| 153 | ipkgs = [] |
|---|
| 154 | bad_pkgs = [] |
|---|
| 155 | for pkg in packages: |
|---|
| 156 | if not self.pkg_cache.has_key(pkg.get('name')): |
|---|
| 157 | self.logger.error("APT has no information about package %s" % (pkg.get('name'))) |
|---|
| 158 | continue |
|---|
| 159 | if pkg.get('version') in ['auto', 'any']: |
|---|
| 160 | ipkgs.append("%s=%s" % (pkg.get('name'), |
|---|
| 161 | self.pkg_cache[pkg.get('name')].candidateVersion)) |
|---|
| 162 | continue |
|---|
| 163 | avail_vers = [x.VerStr for x in \ |
|---|
| 164 | self.pkg_cache[pkg.get('name')]._pkg.VersionList] |
|---|
| 165 | if pkg.get('version') in avail_vers: |
|---|
| 166 | ipkgs.append("%s=%s" % (pkg.get('name'), pkg.get('version'))) |
|---|
| 167 | continue |
|---|
| 168 | else: |
|---|
| 169 | self.logger.error("Package %s: desired version %s not in %s" \ |
|---|
| 170 | % (pkg.get('name'), pkg.get('version'), |
|---|
| 171 | avail_vers)) |
|---|
| 172 | bad_pkgs.append(pkg.get('name')) |
|---|
| 173 | if bad_pkgs: |
|---|
| 174 | self.logger.error("Cannot find correct versions of packages:") |
|---|
| 175 | self.logger.error(bad_pkgs) |
|---|
| 176 | if not ipkgs: |
|---|
| 177 | return |
|---|
| 178 | rc = self.cmd.run(self.pkgcmd % (" ".join(ipkgs)))[0] |
|---|
| 179 | if rc: |
|---|
| 180 | self.logger.error("APT command failed") |
|---|
| 181 | self.pkg_cache = apt.cache.Cache() |
|---|
| 182 | self.extra = self.FindExtra() |
|---|
| 183 | for package in packages: |
|---|
| 184 | states[package] = self.VerifyPackage(package, [], checksums=False) |
|---|
| 185 | if states[package]: |
|---|
| 186 | self.modified.append(package) |
|---|