Ticket #540: APT.py

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