Ticket #935: YUM24.py

File YUM24.py, 18.5 KB (added by jjneely, 12 years ago)

Yum driver for CentOS 4

Line 
1"""This provides bcfg2 support for yum."""
2__revision__ = '$Revision: $'
3
4import ConfigParser
5import copy
6import os.path
7import sys
8import yum
9import Bcfg2.Client.XML
10import Bcfg2.Client.Tools.RPMng
11
12# Fix for python2.3
13try:
14    set
15except NameError:
16    from sets import Set as set
17
18YAD = True
19CP = ConfigParser.ConfigParser()
20try:
21    if '-C' in sys.argv:
22        CP.read([sys.argv[sys.argv.index('-C') + 1]])
23    else:
24        CP.read(['/etc/bcfg2.conf'])
25    if CP.get('YUMng', 'autodep').lower() == 'false':
26        YAD = False
27except:
28    pass
29
30if not hasattr(Bcfg2.Client.Tools.RPMng, 'RPMng'):
31    raise ImportError
32
33def build_yname(pkgname, inst):
34    """Build yum appropriate package name."""
35    ypname = pkgname
36    if inst.get('version') != 'any':
37        ypname += '-'
38    if inst.get('epoch', False):
39        ypname += "%s:" % inst.get('epoch')
40    if inst.get('version', False) and inst.get('version') != 'any':
41        ypname += "%s" % (inst.get('version'))
42    if inst.get('release', False) and inst.get('release') != 'any':
43        ypname += "-%s" % (inst.get('release'))
44    if inst.get('arch', False) and inst.get('arch') != 'any':
45        ypname += ".%s" % (inst.get('arch'))
46    return ypname
47
48class YUM24(Bcfg2.Client.Tools.RPMng.RPMng):
49    """Support for Yum packages."""
50    pkgtype = 'yum'
51
52    name = 'YUM24'
53    __execs__ = ['/usr/bin/yum', '/var/lib/rpm']
54    __handles__ = [('Package', 'yum'),
55                   ('Package', 'rpm'),
56                   ('Path', 'ignore')]
57
58    __req__ = {'Package': ['name', 'version']}
59    __ireq__ = {'Package': ['name']}
60    #__ireq__ = {'Package': ['name', 'version']}
61
62    __new_req__ = {'Package': ['name'], 'Instance': ['version', 'release', 'arch']}
63    __new_ireq__ = {'Package': ['name'], \
64                    'Instance': []}
65    #__new_ireq__ = {'Package': ['name', 'uri'], \
66    #                'Instance': ['simplefile', 'version', 'release', 'arch']}
67
68    __gpg_req__ = {'Package': ['name', 'version']}
69    __gpg_ireq__ = {'Package': ['name', 'version']}
70
71    __new_gpg_req__ = {'Package': ['name'], 'Instance': ['version', 'release']}
72    __new_gpg_ireq__ = {'Package': ['name'], 'Instance': ['version', 'release']}
73
74    conflicts = ['YUMng', 'RPMng']
75
76    def __init__(self, logger, setup, config):
77        Bcfg2.Client.Tools.RPMng.RPMng.__init__(self, logger, setup, config)
78        self.__important__ = self.__important__ + \
79                             [entry.get('name') for struct in config \
80                              for entry in struct \
81                              if entry.tag in ['Path', 'ConfigFile'] and \
82                              (entry.get('name').startswith('/etc/yum.d') \
83                              or entry.get('name').startswith('/etc/yum.repos.d')) \
84                              or entry.get('name') == '/etc/yum.conf']
85        self.yum_avail = dict()
86        self.yum_installed = dict()
87        self.yb = yum.YumBase()
88        self.yb.doConfigSetup()
89        self.yb.doTsSetup()
90        self.yb.doRpmDBSetup()
91        yup = self.yb.doPackageLists(pkgnarrow='updates')
92        if hasattr(self.yb.rpmdb, 'pkglist'):
93            yinst = self.yb.rpmdb.pkglist
94        else:
95            yinst = self.yb.rpmdb.getPkgList()
96        for dest, source in [(self.yum_avail, yup.updates),
97                             (self.yum_installed, yinst)]:
98            for pkg in source:
99                if dest is self.yum_avail:
100                    pname = pkg.name
101                    data = {pkg.arch: (pkg.epoch, pkg.version, pkg.release)}
102                else:
103                    pname = pkg[0]
104                    if pkg[1] is None: a = 'noarch'
105                    else: a = pkg[1]
106                    if pkg[2] is None: e = '0'
107                    else: e = pkg[2]
108                    data = {a: (e, pkg[3], pkg[4])}
109                if pname in dest:
110                    dest[pname].update(data)
111                else:
112                    dest[pname] = dict(data)
113
114    def VerifyPackage(self, entry, modlist):
115        pinned_version = None
116        if entry.get('version', False) == 'auto':
117            # old style entry; synthesize Instances from current installed
118            if entry.get('name') not in self.yum_installed and \
119                   entry.get('name') not in self.yum_avail:
120                # new entry; fall back to default
121                entry.set('version', 'any')
122            else:
123                data = copy.copy(self.yum_installed[entry.get('name')])
124                if entry.get('name') in self.yum_avail:
125                    # installed but out of date
126                    data.update(self.yum_avail[entry.get('name')])
127                for (arch, (epoch, vers, rel)) in list(data.items()):
128                    x = Bcfg2.Client.XML.SubElement(entry, "Instance",
129                                                    name=entry.get('name'),
130                                                    version=vers, arch=arch,
131                                                    release=rel, epoch=epoch)
132                    if 'verify_flags' in entry.attrib:
133                        x.set('verify_flags', entry.get('verify_flags'))
134                    if 'verify' in entry.attrib:
135                        x.set('verify', entry.get('verify'))
136
137        if entry.get('type', False) == 'yum':
138            # Check for virtual provides or packages.  If we don't have
139            # this package use Yum to resolve it to a real package name
140            knownPkgs = self.yum_installed.keys() + self.yum_avail.keys()
141            if entry.get('name') not in knownPkgs:
142                # If the package name matches something installed
143                # or available the that's the correct package.
144                try:
145                    pkgDict = dict( [ (i.name, i) for i in \
146                        self.yb.returnPackagesByDep(entry.get('name')) ] )
147                except yum.Errors.YumBaseError, e:
148                    self.logger.error('Yum Error Depsolving for %s: %s' % \
149                                      (entry.get('name'), str(e)))
150                    pkgDict = {}
151
152                if len(pkgDict) > 1:
153                    # What do we do with multiple packages? 
154                    s = "YUMng: returnPackagesByDep(%s) returned many packages"
155                    self.logger.info(s % entry.get('name'))
156                    s = "YUMng: matching packages: %s"
157                    self.logger.info(s % str(pkgDict.keys()))
158                    pkgs = set(pkgDict.keys()) & set(self.yum_installed.keys())
159                    if len(pkgs) > 0:
160                        # Virtual packages matches an installed real package
161                        pkg = pkgDict[pkgs.pop()]
162                        s = "YUMng: chosing: %s" % pkg.name
163                        self.logger.info(s)
164                    else:
165                        # What's the right package?  This will fail verify
166                        # and Yum should Do The Right Thing on package install
167                        pkg = None
168                elif len(pkgDict) == 1:
169                    pkg = pkgDict.values()[0]
170                else:  # len(pkgDict) == 0
171                    s = "YUMng: returnPackagesByDep(%s) returned no results"
172                    self.logger.info(s % entry.get('name'))
173                    pkg = None
174
175                if pkg is not None:
176                    s = "YUMng: remapping virtual package %s to %s"
177                    self.logger.info(s % (entry.get('name'), pkg.name))
178                    entry.set('name', pkg.name)
179
180        return Bcfg2.Client.Tools.RPMng.RPMng.VerifyPackage(self, entry,
181                                                            modlist)
182
183    def Install(self, packages, states):
184        """
185           Try and fix everything that RPMng.VerifyPackages() found wrong for
186           each Package Entry.  This can result in individual RPMs being
187           installed (for the first time), deleted, downgraded
188           or upgraded.
189
190           NOTE: YUM can not reinstall a package that it thinks is already
191                 installed.
192
193           packages is a list of Package Elements that has
194               states[<Package Element>] == False
195
196           The following effects occur:
197           - states{} is conditionally updated for each package.
198           - self.installed{} is rebuilt, possibly multiple times.
199           - self.instance_status{} is conditionally updated for each instance
200             of a package.
201           - Each package will be added to self.modified[] if its states{}
202             entry is set to True.
203
204        """
205        self.logger.info('Running YUMng.Install()')
206
207        install_pkgs = []
208        gpg_keys = []
209        upgrade_pkgs = []
210
211        # Remove extra instances.
212        # Can not reverify because we don't have a package entry.
213        if len(self.extra_instances) > 0:
214            if (self.setup.get('remove') == 'all' or \
215                self.setup.get('remove') == 'packages'):
216                self.RemovePackages(self.extra_instances)
217            else:
218                self.logger.info("The following extra package instances will be removed by the '-r' option:")
219                for pkg in self.extra_instances:
220                    for inst in pkg:
221                        self.logger.info("    %s %s" % \
222                                         ((pkg.get('name'), self.str_evra(inst))))
223
224        # Figure out which instances of the packages actually need something
225        # doing to them and place in the appropriate work 'queue'.
226        for pkg in packages:
227            insts = [pinst for pinst in pkg \
228                     if pinst.tag in ['Instance', 'Package']]
229            if insts:
230                for inst in insts:
231                    if self.FixInstance(inst, self.instance_status[inst]):
232                        if self.instance_status[inst].get('installed', False) \
233                               == False:
234                            if pkg.get('name') == 'gpg-pubkey':
235                                gpg_keys.append(inst)
236                            else:
237                                install_pkgs.append(inst)
238                        elif self.instance_status[inst].get('version_fail', \
239                                                            False) == True:
240                            upgrade_pkgs.append(inst)
241            else:
242                install_pkgs.append(pkg)
243
244        # Install GPG keys.
245        # Alternatively specify the required keys using 'gpgkey' in the
246        # repository definition in yum.conf.  YUM will install the keys
247        # automatically.
248        if len(gpg_keys) > 0:
249            for inst in gpg_keys:
250                self.logger.info("Installing GPG keys.")
251                if inst.get('simplefile') is None:
252                    self.logger.error("GPG key has no simplefile attribute")
253                    continue
254                key_arg = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \
255                                                     inst.get('simplefile'))
256                cmdrc, output = self.cmd.run("rpm --import %s" % key_arg)
257                if cmdrc != 0:
258                    self.logger.debug("Unable to install %s-%s" % \
259                                              (self.instance_status[inst].get('pkg').get('name'), \
260                                               self.str_evra(inst)))
261                else:
262                    self.logger.debug("Installed %s-%s-%s" % \
263                                              (self.instance_status[inst].get('pkg').get('name'), \
264                                               inst.get('version'), inst.get('release')))
265            self.RefreshPackages()
266            self.gpg_keyids = self.getinstalledgpg()
267            pkg = self.instance_status[gpg_keys[0]].get('pkg')
268            states[pkg] = self.VerifyPackage(pkg, [])
269
270        # Install packages.
271        if len(install_pkgs) > 0:
272            self.logger.info("Attempting to install packages")
273
274            if YAD:
275                pkgtool = "/usr/bin/yum -d0 -y install %s"
276            else:
277                pkgtool = "/usr/bin/yum -d0 install %s"
278
279            install_args = []
280            for inst in install_pkgs:
281                pkg_arg = self.instance_status[inst].get('pkg').get('name')
282                install_args.append(build_yname(pkg_arg, inst))
283
284            cmdrc, output = self.cmd.run(pkgtool % " ".join(install_args))
285            if cmdrc == 0:
286                # The yum command succeeded.  All packages installed.
287                self.logger.info("Single Pass for Install Succeeded")
288                self.RefreshPackages()
289            else:
290                # The yum command failed.  No packages installed.
291                # Try installing instances individually.
292                self.logger.error("Single Pass Install of Packages Failed")
293                installed_instances = []
294                for inst in install_pkgs:
295                    pkg_arg = build_yname(self.instance_status[inst].get('pkg').get('name'), inst)
296
297                    cmdrc, output = self.cmd.run(pkgtool % pkg_arg)
298                    if cmdrc == 0:
299                        installed_instances.append(inst)
300                    else:
301                        self.logger.debug("%s %s would not install." % \
302                                              (self.instance_status[inst].get('pkg').get('name'), \
303                                               self.str_evra(inst)))
304                self.RefreshPackages()
305
306        # Fix upgradeable packages.
307        if len(upgrade_pkgs) > 0:
308            self.logger.info("Attempting to upgrade packages")
309
310            if YAD:
311                pkgtool = "/usr/bin/yum -d0 -y update %s"
312            else:
313                pkgtool = "/usr/bin/yum -d0 update %s"
314
315            upgrade_args = []
316            for inst in upgrade_pkgs:
317                pkg_arg = build_yname(self.instance_status[inst].get('pkg').get('name'), inst)
318                upgrade_args.append(pkg_arg)
319
320            cmdrc, output = self.cmd.run(pkgtool % " ".join(upgrade_args))
321            if cmdrc == 0:
322                # The yum command succeeded.  All packages installed.
323                self.logger.info("Single Pass for Install Succeeded")
324                self.RefreshPackages()
325            else:
326                # The yum command failed.  No packages installed.
327                # Try installing instances individually.
328                self.logger.error("Single Pass Install of Packages Failed")
329                installed_instances = []
330                for inst in upgrade_pkgs:
331                    pkg_arg = build_yname(self.instance_status[inst].get('pkg').get('name'), inst)
332                    cmdrc, output = self.cmd.run(pkgtool % pkg_arg)
333                    if cmdrc == 0:
334                        installed_instances.append(inst)
335                    else:
336                        self.logger.debug("%s %s would not install." % \
337                                              (self.instance_status[inst].get('pkg').get('name'), \
338                                               self.str_evra(inst)))
339
340                self.RefreshPackages()
341
342        if not self.setup['kevlar']:
343            for pkg_entry in [p for p in packages if self.canVerify(p)]:
344                self.logger.debug("Reverifying Failed Package %s" % (pkg_entry.get('name')))
345                states[pkg_entry] = self.VerifyPackage(pkg_entry, \
346                                                       self.modlists.get(pkg_entry, []))
347
348        for entry in [ent for ent in packages if states[ent]]:
349            self.modified.append(entry)
350
351    def RemovePackages(self, packages):
352        """
353           Remove specified entries.
354
355           packages is a list of Package Entries with Instances generated
356           by FindExtraPackages().
357        """
358        self.logger.debug('Running YUMng.RemovePackages()')
359
360        if YAD:
361            pkgtool = "/usr/bin/yum -d0 -y erase %s"
362        else:
363            pkgtool = "/usr/bin/yum -d0 erase %s"
364
365        erase_args = []
366        for pkg in packages:
367            for inst in pkg:
368                if pkg.get('name') != 'gpg-pubkey':
369                    pkg_arg = pkg.get('name') + '-'
370                    if inst.get('epoch', False):
371                        pkg_arg = pkg_arg + inst.get('epoch') + ':'
372                    pkg_arg = pkg_arg + inst.get('version') + '-' + inst.get('release')
373                    if inst.get('arch', False):
374                        pkg_arg = pkg_arg + '.' + inst.get('arch')
375                    erase_args.append(pkg_arg)
376                else:
377                    pkgspec = { 'name':pkg.get('name'),
378                            'version':inst.get('version'),
379                            'release':inst.get('release')}
380                    self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\
381                                                 % (pkgspec.get('name'), self.str_evra(pkgspec)))
382                    self.logger.info("         This package will be deleted in a future version of the RPMng driver.")
383
384        cmdrc, output = self.cmd.run(pkgtool % " ".join(erase_args))
385        if cmdrc == 0:
386            self.modified += packages
387            for pkg in erase_args:
388                self.logger.info("Deleted %s" % (pkg))
389        else:
390            self.logger.info("Bulk erase failed with errors:")
391            self.logger.debug("Erase results = %s" % output)
392            self.logger.info("Attempting individual erase for each package.")
393            for pkg in packages:
394                pkg_modified = False
395                for inst in pkg:
396                    if pkg.get('name') != 'gpg-pubkey':
397                        pkg_arg = pkg.get('name') + '-'
398                        if inst.attrib.has_key('epoch'):
399                            pkg_arg = pkg_arg + inst.get('epoch') + ':'
400                        pkg_arg = pkg_arg + inst.get('version') + '-' + inst.get('release')
401                        if 'arch' in inst.attrib:
402                            pkg_arg = pkg_arg + '.' + inst.get('arch')
403                    else:
404                        self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\
405                                                 % (pkg.get('name'), self.str_evra(pkg)))
406                        self.logger.info("         This package will be deleted in a future version of the RPMng driver.")
407                        continue
408
409                    cmdrc, output = self.cmd.run(self.pkgtool % pkg_arg)
410                    if cmdrc == 0:
411                        pkg_modified = True
412                        self.logger.info("Deleted %s" % pkg_arg)
413                    else:
414                        self.logger.error("unable to delete %s" % pkg_arg)
415                        self.logger.debug("Failure = %s" % output)
416                if pkg_modified == True:
417                    self.modified.append(pkg)
418
419
420        self.RefreshPackages()
421        self.extra = self.FindExtraPackages()