root/trunk/bcfg2/src/lib/Client/Tools/RPMng.py

Revision 5573, 52.0 KB (checked in by desai, 12 days ago)

RPMng: supress prelink runs in dryrun mode (Resolves Ticket #794) [bugfix]

  • Property svn:keywords set to Revision Id
Line 
1'''Bcfg2 Support for RPMS'''
2
3__revision__ = '$Revision$'
4
5import ConfigParser
6import os.path
7import rpm
8import rpmtools
9import Bcfg2.Client.Tools
10
11# Fix for python2.3
12try:
13    set
14except NameError:
15    from sets import Set as set
16
17class RPMng(Bcfg2.Client.Tools.PkgTool):
18    '''Support for RPM packages'''
19    name = 'RPMng'
20
21    __execs__ = ['/bin/rpm', '/var/lib/rpm']
22    __handles__ = [('Package', 'rpm')]
23
24    __req__ = {'Package': ['name', 'version']}
25    __ireq__ = {'Package': ['url']}
26
27    __new_req__ = {'Package': ['name'], 'Instance': ['version', 'release', 'arch']}
28    __new_ireq__ = {'Package': ['uri'], \
29                    'Instance': ['simplefile']}
30
31    __gpg_req__ = {'Package': ['name', 'version']}
32    __gpg_ireq__ = {'Package': ['name', 'version']}
33
34    __new_gpg_req__ = {'Package': ['name'], 'Instance': ['version', 'release']}
35    __new_gpg_ireq__ = {'Package': ['name'], 'Instance': ['version', 'release']}
36
37    conflicts = ['RPM']
38
39    pkgtype = 'rpm'
40    pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"]))
41
42    def __init__(self, logger, setup, config):
43        Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config)
44
45        self.instance_status = {}
46        self.extra_instances = []
47        self.modlists = {}
48        self.gpg_keyids = self.getinstalledgpg()
49
50        # Process thee RPMng section from the config file.
51        RPMng_CP = ConfigParser.ConfigParser()
52        RPMng_CP.read(self.setup.get('setup'))
53
54        # installonlypackages
55        self.installOnlyPkgs = []
56        if RPMng_CP.has_option(self.name, 'installonlypackages'):
57            for i in RPMng_CP.get(self.name, 'installonlypackages').split(','):
58                self.installOnlyPkgs.append(i.strip())
59        if self.installOnlyPkgs == []:
60            self.installOnlyPkgs = ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp',
61                               'kernel-modules', 'kernel-debug', 'kernel-unsupported',
62                               'kernel-source', 'kernel-devel', 'kernel-default',
63                               'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen',
64                               'gpg-pubkey']
65        if 'gpg-pubkey' not in self.installOnlyPkgs:
66            self.installOnlyPkgs.append('gpg-pubkey')
67        self.logger.debug('installOnlyPackages = %s' % self.installOnlyPkgs)
68
69        # erase_flags
70        self.erase_flags = []
71        if RPMng_CP.has_option(self.name, 'erase_flags'):
72            for i in RPMng_CP.get(self.name, 'erase_flags').split(','):
73                self.erase_flags.append(i.strip())
74        if self.erase_flags == []:
75            self.erase_flags = ['allmatches']
76        self.logger.debug('erase_flags = %s' % self.erase_flags)
77
78        # pkg_checks
79        if RPMng_CP.has_option(self.name, 'pkg_checks'):
80            self.pkg_checks = RPMng_CP.get(self.name, 'pkg_checks').lower()
81        else:
82            self.pkg_checks = 'true'
83        self.logger.debug('pkg_checks = %s' % self.pkg_checks)
84
85        # pkg_verify
86        if RPMng_CP.has_option(self.name, 'pkg_verify'):
87            self.pkg_verify = RPMng_CP.get(self.name, 'pkg_verify').lower()
88        else:
89            self.pkg_verify = 'true'
90        self.logger.debug('pkg_verify = %s' % self.pkg_verify)
91
92        # installed_action
93        if RPMng_CP.has_option(self.name, 'installed_action'):
94            self.installed_action = RPMng_CP.get(self.name, 'installed_action').lower()
95        else:
96            self.installed_action = 'install'
97        self.logger.debug('installed_action = %s' % self.installed_action)
98
99        # version_fail_action
100        if RPMng_CP.has_option(self.name, 'version_fail_action'):
101            self.version_fail_action = RPMng_CP.get(self.name, 'version_fail_action').lower()
102        else:
103            self.version_fail_action = 'upgrade'
104        self.logger.debug('version_fail_action = %s' % self.version_fail_action)
105
106        # verify_fail_action
107        if self.name == "RPMng":
108            if RPMng_CP.has_option(self.name, 'verify_fail_action'):
109                self.verify_fail_action = RPMng_CP.get(self.name, 'verify_fail_action').lower()
110            else:
111                self.verify_fail_action = 'reinstall'
112        else: # yum can't reinstall packages.
113            self.verify_fail_action = 'none'
114        self.logger.debug('verify_fail_action = %s' % self.verify_fail_action)
115
116        # version_fail_action
117        if RPMng_CP.has_option(self.name, 'verify_flags'):
118            self.verify_flags = RPMng_CP.get(self.name, 'verify_flags').lower().split(',')
119        else:
120            self.verify_flags = []
121        if '' in self.verify_flags:
122            self.verify_flags.remove('')
123        self.logger.debug('version_fail_action = %s' % self.version_fail_action)
124        # Force a re- prelink of all packages if prelink exists.
125        # Many, if not most package verifies can be caused by out of date prelinking.
126        if os.path.isfile('/usr/sbin/prelink') and not self.setup['dryrun']:
127            cmdrc, output = self.cmd.run('/usr/sbin/prelink -a -mR')
128            if cmdrc == 0:
129                self.logger.debug('Pre-emptive prelink succeeded')
130            else:
131                # FIXME : this is dumb - what if the output is huge?
132                self.logger.error('Pre-emptive prelink failed: %s' % output)
133
134
135    def RefreshPackages(self):
136        '''
137            Creates self.installed{} which is a dict of installed packages.
138
139            The dict items are lists of nevra dicts.  This loosely matches the
140            config from the server and what rpmtools uses to specify pacakges.
141
142            e.g.
143
144            self.installed['foo'] = [ {'name':'foo', 'epoch':None,
145                                       'version':'1', 'release':2,
146                                       'arch':'i386'},
147                                      {'name':'foo', 'epoch':None,
148                                       'version':'1', 'release':2,
149                                       'arch':'x86_64'} ]
150        '''
151        self.installed = {}
152        refresh_ts = rpmtools.rpmtransactionset()
153        # Don't bother with signature checks at this stage. The GPG keys might
154        # not be installed.
155        refresh_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES)
156        for nevra in rpmtools.rpmpackagelist(refresh_ts):
157            self.installed.setdefault(nevra['name'], []).append(nevra)
158        if self.setup['debug']:
159            print("The following package instances are installed:")
160            for name, instances in list(self.installed.items()):
161                self.logger.debug("    " + name)
162                for inst in instances:
163                    self.logger.debug("        %s" %self.str_evra(inst))
164        refresh_ts.closeDB()
165        del refresh_ts
166
167    def VerifyPackage(self, entry, modlist, pinned_version=None):
168        '''
169            Verify Package status for entry.
170            Performs the following:
171                - Checks for the presence of required Package Instances.
172                - Compares the evra 'version' info against self.installed{}.
173                - RPM level package verify (rpm --verify).
174                - Checks for the presence of unrequired package instances.
175
176            Produces the following dict and list for RPMng.Install() to use:
177              For installs/upgrades/fixes of required instances:
178                instance_status = { <Instance Element Object>:
179                                       { 'installed': True|False,
180                                         'version_fail': True|False,
181                                         'verify_fail': True|False,
182                                         'pkg': <Package Element Object>,
183                                         'modlist': [ <filename>, ... ],
184                                         'verify' : [ <rpm --verify results> ]
185                                       }, ......
186                                  }
187
188              For deletions of unrequired instances:
189                extra_instances = [ <Package Element Object>, ..... ]
190
191              Constructs the text prompts for interactive mode.
192        '''
193        instances = [inst for inst in entry if inst.tag == 'Instance' or inst.tag == 'Package']
194        if instances == []:
195            # We have an old style no Instance entry. Convert it to new style.
196            instance = Bcfg2.Client.XML.SubElement(entry, 'Package')
197            for attrib in list(entry.attrib.keys()):
198                instance.attrib[attrib] = entry.attrib[attrib]
199            if self.pkg_checks == 'true' and entry.get('pkg_checks', 'true') == 'true':
200                if 'any' in [entry.get('version'), pinned_version]:
201                    version, release = 'any', 'any'
202                elif entry.get('version') == 'auto':
203                    if pinned_version != None:
204                        version, release = pinned_version.split('-')
205                    else:
206                        return False
207                else:
208                    version, release = entry.get('version').split('-')
209                instance.set('version', version)
210                instance.set('release', release)
211                if entry.get('verify', 'true') == 'false':
212                    instance.set('verify', 'false')
213            instances = [ instance ]
214
215        self.logger.debug("Verifying package instances for %s" % entry.get('name'))
216        package_fail = False
217        qtext_versions = ''
218
219        if entry.get('name') in self.installed:
220            # There is at least one instance installed.
221            if self.pkg_checks == 'true' and entry.get('pkg_checks', 'true') == 'true':
222                if entry.get('name') in self.installOnlyPkgs:
223                    # Packages that should only be installed or removed.
224                    # e.g. kernels.
225                    self.logger.debug("        Install only package.")
226                    for inst in instances:
227                        self.instance_status.setdefault(inst, {})['installed'] = False
228                        self.instance_status[inst]['version_fail'] = False
229                        if inst.tag == 'Package' and len(self.installed[entry.get('name')]) > 1:
230                            self.logger.error("WARNING: Multiple instances of package %s are installed." % \
231                                              (entry.get('name')))
232                        for pkg in self.installed[entry.get('name')]:
233                            if inst.get('version') == 'any' or self.pkg_vr_equal(inst, pkg) \
234                               or self.inst_evra_equal(inst, pkg):
235                                if inst.get('version') == 'any':
236                                    self.logger.error("got any version")
237                                self.logger.debug("        %s" % self.str_evra(inst))
238                                self.instance_status[inst]['installed'] = True
239
240                                if self.pkg_verify == 'true' and \
241                                   inst.get('pkg_verify', 'true') == 'true':
242                                    flags = inst.get('verify_flags', '').split(',') + self.verify_flags
243                                    if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \
244                                       entry.get('name') != 'gpg-pubkey':
245                                        flags += ['nosignature', 'nodigest']
246                                        self.logger.debug('WARNING: Package %s %s requires GPG Public key with ID %s'\
247                                                           % (pkg.get('name'), self.str_evra(pkg), \
248                                                              pkg.get('gpgkeyid', '')))
249                                        self.logger.debug('         Disabling signature check.')
250
251                                    if self.setup.get('quick', False):
252                                        if rpmtools.prelink_exists:
253                                            flags += ['nomd5', 'nosize']
254                                        else:
255                                            flags += ['nomd5']
256                                    self.logger.debug("        verify_flags = %s" % flags)
257
258                                    if inst.get('verify', 'true') == 'false':
259                                        self.instance_status[inst]['verify'] = None
260                                    else:
261                                        vp_ts = rpmtools.rpmtransactionset()
262                                        self.instance_status[inst]['verify'] = \
263                                                                             rpmtools.rpm_verify( vp_ts, pkg, flags)
264                                        vp_ts.closeDB()
265                                        del vp_ts
266
267                        if self.instance_status[inst]['installed'] == False:
268                            self.logger.info("        Package %s %s not installed." % \
269                                         (entry.get('name'), self.str_evra(inst)))
270
271                            qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst)
272                            entry.set('current_exists', 'false')
273                else:
274                    # Normal Packages that can be upgraded.
275                    for inst in instances:
276                        self.instance_status.setdefault(inst, {})['installed'] = False
277                        self.instance_status[inst]['version_fail'] = False
278
279                        # Only installed packages with the same architecture are
280                        # relevant.
281                        if inst.get('arch', None) == None:
282                            arch_match = self.installed[entry.get('name')]
283                        else:
284                            arch_match = [pkg for pkg in self.installed[entry.get('name')] \
285                                              if pkg.get('arch', None) == inst.get('arch', None)]
286
287                        if len(arch_match) > 1:
288                            self.logger.error("Multiple instances of package %s installed with the same achitecture." % \
289                                                  (entry.get('name')))
290                        elif len(arch_match) == 1:
291                            # There is only one installed like there should be.
292                            # Check that it is the right version.
293                            for pkg in arch_match:
294                                if inst.get('version') == 'any' or self.pkg_vr_equal(inst, pkg) or \
295                                       self.inst_evra_equal(inst, pkg):
296                                    self.logger.debug("        %s" % self.str_evra(inst))
297                                    self.instance_status[inst]['installed'] = True
298
299                                    if self.pkg_verify == 'true' and \
300                                       inst.get('pkg_verify', 'true') == 'true':
301                                        flags = inst.get('verify_flags', '').split(',') + self.verify_flags
302                                        if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \
303                                           'nosignature' not in flags:
304                                            flags += ['nosignature', 'nodigest']
305                                            self.logger.info('WARNING: Package %s %s requires GPG Public key with ID %s'\
306                                                         % (pkg.get('name'), self.str_evra(pkg), \
307                                                            pkg.get('gpgkeyid', '')))
308                                            self.logger.info('         Disabling signature check.')
309
310                                        if self.setup.get('quick', False):
311                                            if rpmtools.prelink_exists:
312                                                flags += ['nomd5', 'nosize']
313                                            else:
314                                                flags += ['nomd5']
315                                        self.logger.debug("        verify_flags = %s" % flags)
316
317                                        if inst.get('verify', 'true') == 'false':
318                                            self.instance_status[inst]['verify'] = None
319                                        else:
320                                            vp_ts = rpmtools.rpmtransactionset()
321                                            self.instance_status[inst]['verify'] = \
322                                                                                 rpmtools.rpm_verify( vp_ts, pkg, flags )
323                                            vp_ts.closeDB()
324                                            del vp_ts
325
326                                else:
327                                    # Wrong version installed.
328                                    self.instance_status[inst]['version_fail'] = True
329                                    self.logger.info("        Wrong version installed.  Want %s, but have %s"\
330                                                    % (self.str_evra(inst), self.str_evra(pkg)))
331
332                                    qtext_versions = qtext_versions + 'U(%s -> %s) ' % \
333                                                          (self.str_evra(pkg), self.str_evra(inst))
334                        elif len(arch_match) == 0:
335                            # This instance is not installed.
336                            self.instance_status[inst]['installed'] = False
337                            self.logger.info("        %s is not installed." % self.str_evra(inst))
338                            qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst)
339
340                # Check the rpm verify results.
341                for inst in instances:
342                    instance_fail = False
343                    # Dump the rpm verify results.
344                    #****Write something to format this nicely.*****
345                    if self.setup['debug'] and self.instance_status[inst].get('verify', None):
346                        self.logger.debug(self.instance_status[inst]['verify'])
347
348                    self.instance_status[inst]['verify_fail'] = False
349                    if self.instance_status[inst].get('verify', None):
350                        if len(self.instance_status[inst].get('verify')) > 1:
351                            self.logger.info("WARNING: Verification of more than one package instance.")
352
353                        for result in self.instance_status[inst]['verify']:
354
355                            # Check header results
356                            if result.get('hdr', None):
357                                instance_fail = True
358                                self.instance_status[inst]['verify_fail'] = True
359
360                            # Check dependency results
361                            if result.get('deps', None):
362                                instance_fail = True
363                                self.instance_status[inst]['verify_fail'] = True
364
365                            # Check the rpm verify file results against the modlist
366                            # and per Instance Ignores.
367                            for file_result in result.get('files', []):
368                                if file_result[-1] not in modlist + \
369                                       [ignore.get('name') for ignore \
370                                        in inst.findall('Ignore')]:
371                                    instance_fail = True
372                                    self.instance_status[inst]['verify_fail'] = True
373                                else:
374                                    self.logger.debug("        Modlist/Ignore match: %s" % \
375                                                                                 (file_result[-1]))
376
377                        if instance_fail == True:
378                            self.logger.debug("*** Instance %s failed RPM verification ***" % \
379                                              self.str_evra(inst))
380                            qtext_versions = qtext_versions + 'R(%s) ' % self.str_evra(inst)
381                            self.modlists[entry] = modlist
382
383                            # Attach status structure for return to server for reporting.
384                            inst.set('verify_status', str(self.instance_status[inst]))
385
386                    if self.instance_status[inst]['installed'] == False or \
387                       self.instance_status[inst].get('version_fail', False)== True or \
388                       self.instance_status[inst].get('verify_fail', False) == True:
389                        package_fail = True
390                        self.instance_status[inst]['pkg'] = entry
391                        self.modlists[entry] = modlist
392
393                # Find Installed Instances that are not in the Config.
394                extra_installed = self.FindExtraInstances(entry, self.installed[entry.get('name')])
395                if extra_installed != None:
396                    package_fail = True
397                    self.extra_instances.append(extra_installed)
398                    for inst in extra_installed.findall('Instance'):
399                        qtext_versions = qtext_versions + 'D(%s) ' % self.str_evra(inst)
400                    self.logger.debug("Found Extra Instances %s" % qtext_versions)
401
402                if package_fail == True:
403                    self.logger.info("        Package %s failed verification." % \
404                                                              (entry.get('name')))
405                    qtext = 'Install/Upgrade/delete Package %s instance(s) - %s (y/N) ' % \
406                                                  (entry.get('name'), qtext_versions)
407                    entry.set('qtext', qtext)
408
409                    bcfg2_versions = ''
410                    for bcfg2_inst in [inst for inst in instances if inst.tag == 'Instance']:
411                        bcfg2_versions = bcfg2_versions + '(%s) ' % self.str_evra(bcfg2_inst)
412                    if bcfg2_versions != '':
413                        entry.set('version', bcfg2_versions)
414                    installed_versions = ''
415
416                    for installed_inst in self.installed[entry.get('name')]:
417                        installed_versions = installed_versions + '(%s) ' % \
418                                                                      self.str_evra(installed_inst)
419
420                    entry.set('current_version', installed_versions)
421                    return False
422
423        else:
424            # There are no Instances of this package installed.
425            self.logger.debug("Package %s has no instances installed" % (entry.get('name')))
426            entry.set('current_exists', 'false')
427            bcfg2_versions = ''
428            for inst in instances:
429                qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst)
430                self.instance_status.setdefault(inst, {})['installed'] = False
431                self.modlists[entry] = modlist
432                self.instance_status[inst]['pkg'] = entry
433                if inst.tag == 'Instance':
434                    bcfg2_versions = bcfg2_versions + '(%s) ' % self.str_evra(inst)
435            if bcfg2_versions != '':
436                entry.set('version', bcfg2_versions)
437            entry.set('qtext', "Install Package %s Instance(s) %s? (y/N) " % \
438                      (entry.get('name'), qtext_versions))
439
440            return False
441        return True
442
443    def RemovePackages(self, packages):
444        '''
445           Remove specified entries.
446
447           packages is a list of Package Entries with Instances generated
448           by FindExtraPackages().
449        '''
450        self.logger.debug('Running RPMng.RemovePackages()')
451
452        pkgspec_list = []
453        for pkg in packages:
454            for inst in pkg:
455                if pkg.get('name') != 'gpg-pubkey':
456                    pkgspec = { 'name':pkg.get('name'),
457                            'epoch':inst.get('epoch', None),
458                            'version':inst.get('version'),
459                            'release':inst.get('release'),
460                            'arch':inst.get('arch') }
461                    pkgspec_list.append(pkgspec)
462                else:
463                    pkgspec = { 'name':pkg.get('name'),
464                            'version':inst.get('version'),
465                            'release':inst.get('release')}
466                    self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\
467                                                 % (pkgspec.get('name'), self.str_evra(pkgspec)))
468                    self.logger.info("         This package will be deleted in a future version of the RPMng driver.")
469                #pkgspec_list.append(pkg_spec)
470
471        erase_results = rpmtools.rpm_erase(pkgspec_list, self.erase_flags)
472        if erase_results == []:
473            self.modified += packages
474            for pkg in pkgspec_list:
475                self.logger.info("Deleted %s %s" % (pkg.get('name'), self.str_evra(pkg)))
476        else:
477            self.logger.info("Bulk erase failed with errors:")
478            self.logger.debug("Erase results = %s" % erase_results)
479            self.logger.info("Attempting individual erase for each package.")
480            pkgspec_list = []
481            for pkg in packages:
482                pkg_modified = False
483                for inst in pkg:
484                    if pkg.get('name') != 'gpg-pubkey':
485                        pkgspec = { 'name':pkg.get('name'),
486                                'epoch':inst.get('epoch', None),
487                                'version':inst.get('version'),
488                                'release':inst.get('release'),
489                                'arch':inst.get('arch') }
490                        pkgspec_list.append(pkgspec)
491                    else:
492                        pkgspec = { 'name':pkg.get('name'),
493                                'version':inst.get('version'),
494                                'release':inst.get('release')}
495                        self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\
496                                                   % (pkgspec.get('name'), self.str_evra(pkgspec)))
497                        self.logger.info("         This package will be deleted in a future version of the RPMng driver.")
498                        continue # Don't delete the gpg-pubkey packages for now.
499                    erase_results = rpmtools.rpm_erase([pkgspec], self.erase_flags)
500                    if erase_results == []:
501                        pkg_modified = True
502                        self.logger.info("Deleted %s %s" % \
503                                                   (pkgspec.get('name'), self.str_evra(pkgspec)))
504                    else:
505                        self.logger.error("unable to delete %s %s" % \
506                                                   (pkgspec.get('name'), self.str_evra(pkgspec)))
507                        self.logger.debug("Failure = %s" % erase_results)
508                if pkg_modified == True:
509                    self.modified.append(pkg)
510
511        self.RefreshPackages()
512        self.extra = self.FindExtraPackages()
513
514    def FixInstance(self, instance, inst_status):
515        '''
516           Control if a reinstall of a package happens or not based on the
517           results from RPMng.VerifyPackage().
518
519           Return True to reinstall, False to not reintstall.
520        '''
521        fix = False
522
523        if inst_status.get('installed', False) == False:
524            if instance.get('installed_action', 'install') == "install" and \
525               self.installed_action == "install":
526                fix = True
527            else:
528                self.logger.debug('Installed Action for %s %s is to not install' % \
529                                  (inst_status.get('pkg').get('name'),
530                                   self.str_evra(instance)))
531
532        elif inst_status.get('version_fail', False) == True:
533            if instance.get('version_fail_action', 'upgrade') == "upgrade" and \
534                self.version_fail_action == "upgrade":
535                fix = True
536            else:
537                self.logger.debug('Version Fail Action for %s %s is to not upgrade' % \
538                                  (inst_status.get('pkg').get('name'),
539                                   self.str_evra(instance)))
540
541        elif inst_status.get('verify_fail', False) == True and self.name == "RPMng":
542            # yum can't reinstall packages so only do this for rpm.
543            if instance.get('verify_fail_action', 'reinstall') == "reinstall" and \
544               self.verify_fail_action == "reinstall":
545                for inst in inst_status.get('verify'):
546                    # This needs to be a for loop rather than a straight get()
547                    # because the underlying routines handle multiple packages
548                    # and return a list of results.
549                    self.logger.debug('reinstall_check: %s %s:%s-%s.%s' % inst.get('nevra'))
550
551                    if inst.get("hdr", False):
552                        fix = True
553
554                    elif inst.get('files', False):
555                        # Parse rpm verify file results
556                        for file_result in inst.get('files', []):
557                            self.logger.debug('reinstall_check: file: %s' % file_result)
558                            if file_result[-2] != 'c':
559                                fix = True
560                                break
561
562                    # Shouldn't really need this, but included for clarity.
563                    elif inst.get("deps", False):
564                        fix = False
565            else:
566                self.logger.debug('Verify Fail Action for %s %s is to not reinstall' % \
567                                                     (inst_status.get('pkg').get('name'),
568                                                      self.str_evra(instance)))
569
570        return fix
571
572    def Install(self, packages, states):
573        '''
574           Try and fix everything that RPMng.VerifyPackages() found wrong for
575           each Package Entry.  This can result in individual RPMs being
576           installed (for the first time), reinstalled, deleted, downgraded
577           or upgraded.
578
579           packages is a list of Package Elements that has
580               states[<Package Element>] == False
581
582           The following effects occur:
583           - states{} is conditionally updated for each package.
584           - self.installed{} is rebuilt, possibly multiple times.
585           - self.instance_statusi{} is conditionally updated for each instance
586             of a package.
587           - Each package will be added to self.modified[] if its states{}
588             entry is set to True.
589        '''
590        self.logger.info('Runing RPMng.Install()')
591
592        install_only_pkgs = []
593        gpg_keys = []
594        upgrade_pkgs = []
595
596        # Remove extra instances.
597        # Can not reverify because we don't have a package entry.
598        if len(self.extra_instances) > 0:
599            if (self.setup.get('remove') == 'all' or \
600                self.setup.get('remove') == 'packages') and\
601                not self.setup.get('dryrun'):
602                self.RemovePackages(self.extra_instances)
603            else:
604                self.logger.info("The following extra package instances will be removed by the '-r' option:")
605                for pkg in self.extra_instances:
606                    for inst in pkg:
607                        self.logger.info("    %s %s" % (pkg.get('name'), self.str_evra(inst)))
608
609        # Figure out which instances of the packages actually need something
610        # doing to them and place in the appropriate work 'queue'.
611        for pkg in packages:
612            for inst in [instn for instn in pkg if instn.tag \
613                         in ['Instance', 'Package']]:
614                if self.FixInstance(inst, self.instance_status[inst]):
615                    if pkg.get('name') == 'gpg-pubkey':
616                        gpg_keys.append(inst)
617                    elif pkg.get('name') in self.installOnlyPkgs:
618                        install_only_pkgs.append(inst)
619                    else:
620                        upgrade_pkgs.append(inst)
621
622        # Fix installOnlyPackages
623        if len(install_only_pkgs) > 0:
624            self.logger.info("Attempting to install 'install only packages'")
625            install_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \
626                                                  inst.get('simplefile')) \
627                                           for inst in install_only_pkgs])
628            self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args)
629            cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \
630                                                                                     install_args)
631            if cmdrc == 0:
632                # The rpm command succeeded.  All packages installed.
633                self.logger.info("Single Pass for InstallOnlyPkgs Succeded")
634                self.RefreshPackages()
635
636            else:
637                # The rpm command failed.  No packages installed.
638                # Try installing instances individually.
639                self.logger.error("Single Pass for InstallOnlyPackages Failed")
640                installed_instances = []
641                for inst in install_only_pkgs:
642                    install_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \
643                                                     inst.get('simplefile'))
644                    self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args)
645                    cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \
646                                                                                      install_args)
647                    if cmdrc == 0:
648                        installed_instances.append(inst)
649                    else:
650                        self.logger.debug("InstallOnlyPackage %s %s would not install." % \
651                                              (self.instance_status[inst].get('pkg').get('name'), \
652                                               self.str_evra(inst)))
653
654                install_pkg_set = set([self.instance_status[inst].get('pkg') \
655                                                      for inst in install_only_pkgs])
656                self.RefreshPackages()
657
658        # Install GPG keys.
659        if len(gpg_keys) > 0:
660            for inst in gpg_keys:
661                self.logger.info("Installing GPG keys.")
662                key_arg = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \
663                                                     inst.get('simplefile'))
664                cmdrc, output = self.cmd.run("rpm --import %s" % key_arg)
665                if cmdrc != 0:
666                    self.logger.debug("Unable to install %s-%s" % \
667                                              (self.instance_status[inst].get('pkg').get('name'), \
668                                               self.str_evra(inst)))
669                else:
670                    self.logger.debug("Installed %s-%s-%s" % \
671                                              (self.instance_status[inst].get('pkg').get('name'), \
672                                               inst.get('version'), inst.get('release')))
673            self.RefreshPackages()
674            self.gpg_keyids = self.getinstalledgpg()
675            pkg = self.instance_status[gpg_keys[0]].get('pkg')
676            states[pkg] = self.VerifyPackage(pkg, [])
677
678        # Fix upgradeable packages.
679        if len(upgrade_pkgs) > 0:
680            self.logger.info("Attempting to upgrade packages")
681            upgrade_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \
682                                                  inst.get('simplefile')) \
683                                           for inst in upgrade_pkgs])
684            cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \
685                                                       upgrade_args)
686            if cmdrc == 0:
687                # The rpm command succeeded.  All packages upgraded.
688                self.logger.info("Single Pass for Upgraded Packages Succeded")
689                upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \
690                                                      for inst in upgrade_pkgs])
691                self.RefreshPackages()
692            else:
693                # The rpm command failed.  No packages upgraded.
694                # Try upgrading instances individually.
695                self.logger.error("Single Pass for Upgrading Packages Failed")
696                upgraded_instances = []
697                for inst in upgrade_pkgs:
698                    upgrade_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \
699                                                     inst.get('simplefile'))
700                    #self.logger.debug("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \
701                    #                                                      upgrade_args)
702                    cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % upgrade_args)
703                    if cmdrc == 0:
704                        upgraded_instances.append(inst)
705                    else:
706                        self.logger.debug("Package %s %s would not upgrade." % \
707                                              (self.instance_status[inst].get('pkg').get('name'), \
708                                               self.str_evra(inst)))
709
710                upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \
711                                                      for inst in upgrade_pkgs])
712                self.RefreshPackages()
713
714        if not self.setup['kevlar']:
715            for pkg_entry in packages:
716                self.logger.debug("Reverifying Failed Package %s" % (pkg_entry.get('name')))
717                states[pkg_entry] = self.VerifyPackage(pkg_entry, \
718                                                       self.modlists.get(pkg_entry, []))
719
720        for entry in [ent for ent in packages if states[ent]]:
721            self.modified.append(entry)
722
723    def canInstall(self, entry):
724        '''
725            test if entry has enough information to be installed
726        '''
727        if not self.handlesEntry(entry):
728            return False
729
730        if 'failure' in entry.attrib:
731            self.logger.error("Cannot install entry %s:%s with bind failure" % \
732                              (entry.tag, entry.get('name')))
733            return False
734
735
736        instances = entry.findall('Instance')
737
738        # If the entry wasn't verifiable, then we really don't want to try and fix something
739        # that we don't know is broken.
740        if not self.canVerify(entry):
741            self.logger.debug("WARNING: Package %s was not verifiable, not passing to Install()" \
742                                           % entry.get('name'))
743            return False
744
745        if not instances:
746            # Old non Instance format, unmodified.
747            if entry.get('name') == 'gpg-pubkey':
748                # gpg-pubkey packages aren't really pacakges, so we have to do
749                # something a little different.
750                # Check that the Package Level has what we need for verification.
751                if [attr for attr in self.__gpg_ireq__[entry.tag] if attr not in entry.attrib]:
752                    self.logger.error("Incomplete information for entry %s:%s; cannot install" \
753                                      % (entry.tag, entry.get('name')))
754                    return False
755            else:
756                if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]:
757                    self.logger.error("Incomplete information for entry %s:%s; cannot install" \
758                                      % (entry.tag, entry.get('name')))
759                    return False
760        else:
761            if entry.get('name') == 'gpg-pubkey':
762                # gpg-pubkey packages aren't really pacakges, so we have to do
763                # something a little different.
764                # Check that the Package Level has what we need for verification.
765                if [attr for attr in self.__new_gpg_ireq__[entry.tag] if attr not in entry.attrib]:
766                    self.logger.error("Incomplete information for entry %s:%s; cannot install" \
767                                      % (entry.tag, entry.get('name')))
768                    return False
769                # Check that the Instance Level has what we need for verification.
770                for inst in instances:
771                    if [attr for attr in self.__new_gpg_ireq__[inst.tag] \
772                                 if attr not in inst.attrib]:
773                        self.logger.error("Incomplete information for entry %s:%s; cannot install"\
774                                          % (inst.tag, entry.get('name')))
775                        return False
776            else:
777                # New format with Instances.
778                # Check that the Package Level has what we need for verification.
779                if [attr for attr in self.__new_ireq__[entry.tag] if attr not in entry.attrib]:
780                    self.logger.error("Incomplete information for entry %s:%s; cannot install" \
781                                      % (entry.tag, entry.get('name')))
782                    self.logger.error("             Required attributes that may not be present are %s" \
783                                      % (self.__new_ireq__[entry.tag]))
784                    return False
785                # Check that the Instance Level has what we need for verification.
786                for inst in instances:
787                    if inst.tag == 'Instance':
788                        if [attr for attr in self.__new_ireq__[inst.tag] \
789                                     if attr not in inst.attrib]:
790                            self.logger.error("Incomplete information for %s of package %s; cannot install" \
791                                              % (inst.tag, entry.get('name')))
792                            self.logger.error("         Required attributes that may not be present are %s" \
793                                              % (self.__new_ireq__[inst.tag]))
794                            return False
795        return True
796
797    def canVerify(self, entry):
798        '''
799            Test if entry has enough information to be verified.
800
801            Three types of entries are checked.
802               Old style Package
803               New style Package with Instances
804               pgp-pubkey packages
805
806           Also the old style entries get modified after the first
807           VerifyPackage() run, so there needs to be a second test.
808        '''
809        if not self.handlesEntry(entry):
810            return False
811
812        if 'failure' in entry.attrib:
813            self.logger.error("Entry %s:%s reports bind failure: %s" % \
814                              (entry.tag, entry.get('name'), entry.get('failure')))
815            return False
816
817        # We don't want to do any checks so we don't care what the entry has in it.
818        if self.pkg_checks == 'false' or \
819               entry.get('pkg_checks', 'true').lower() == 'false':
820            return True
821
822        instances = entry.findall('Instance')
823
824        if not instances:
825            # Old non Instance format, unmodified.
826            if entry.get('name') == 'gpg-pubkey':
827                # gpg-pubkey packages aren't really pacakges, so we have to do
828                # something a little different.
829                # Check that the Package Level has what we need for verification.
830                if [attr for attr in self.__gpg_req__[entry.tag] if attr not in entry.attrib]:
831                    self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
832                                      % (entry.tag, entry.get('name')))
833                    return False
834            else:
835                if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]:
836                    self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
837                                      % (entry.tag, entry.get('name')))
838                    return False
839        else:
840            if entry.get('name') == 'gpg-pubkey':
841                # gpg-pubkey packages aren't really pacakges, so we have to do
842                # something a little different.
843                # Check that the Package Level has what we need for verification.
844                if [attr for attr in self.__new_gpg_req__[entry.tag] if attr not in entry.attrib]:
845                    self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
846                                      % (entry.tag, entry.get('name')))
847                    return False
848                # Check that the Instance Level has what we need for verification.
849                for inst in instances:
850                    if [attr for attr in self.__new_gpg_req__[inst.tag] \
851                                 if attr not in inst.attrib]:
852                        self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
853                                          % (inst.tag, inst.get('name')))
854                        return False
855            else:
856                # New format with Instances, or old style modified.
857                # Check that the Package Level has what we need for verification.
858                if [attr for attr in self.__new_req__[entry.tag] if attr not in entry.attrib]:
859                    self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
860                                      % (entry.tag, entry.get('name')))
861                    return False
862                # Check that the Instance Level has what we need for verification.
863                for inst in instances:
864                    if inst.tag == 'Instance':
865                        if [attr for attr in self.__new_req__[inst.tag] \
866                                     if attr not in inst.attrib]:
867                            self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
868                                              % (inst.tag, inst.get('name')))
869                            return False
870        return True
871
872    def FindExtraPackages(self):
873        '''
874           Find extra packages
875        '''
876        packages = [entry.get('name') for entry in self.getSupportedEntries()]
877        extras = []
878
879        for (name, instances) in list(self.installed.items()):
880            if name not in packages:
881                extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype)
882                for installed_inst in instances:
883                    if self.setup['extra']:
884                        self.logger.info("Extra Package %s %s." % \
885                                         (name, self.str_evra(installed_inst)))
886                    tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \
887                                     version = installed_inst.get('version'), \
888                                     release = installed_inst.get('release'))
889                    if installed_inst.get('epoch', None) != None:
890                        tmp_entry.set('epoch', str(installed_inst.get('epoch')))
891                    if installed_inst.get('arch', None) != None:
892                        tmp_entry.set('arch', installed_inst.get('arch'))
893                extras.append(extra_entry)
894        return extras
895
896
897    def FindExtraInstances(self, pkg_entry, installed_entry):
898        '''
899            Check for installed instances that are not in the config.
900            Return a Package Entry with Instances to remove, or None if there
901            are no Instances to remove.
902        '''
903        name = pkg_entry.get('name')
904        extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype)
905        instances = [inst for inst in pkg_entry if inst.tag == 'Instance' or inst.tag == 'Package']
906        if name in self.installOnlyPkgs:
907            for installed_inst in installed_entry:
908                not_found = True
909                for inst in instances:
910                    if self.pkg_vr_equal(inst, installed_inst) or \
911                       self.inst_evra_equal(inst, installed_inst):
912                        not_found = False
913                        break
914                if not_found == True:
915                    # Extra package.
916                    self.logger.info("Extra InstallOnlyPackage %s %s." % \
917                                     (name, self.str_evra(installed_inst)))
918                    tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \
919                                     version = installed_inst.get('version'), \
920                                     release = installed_inst.get('release'))
921                    if installed_inst.get('epoch', None) != None:
922                        tmp_entry.set('epoch', str(installed_inst.get('epoch')))
923                    if installed_inst.get('arch', None) != None:
924                        tmp_entry.set('arch', installed_inst.get('arch'))
925        else:
926            # Normal package, only check arch.
927            for installed_inst in installed_entry:
928                not_found = True
929                for inst in instances:
930                    if installed_inst.get('arch', None) == inst.get('arch', None) or\
931                       inst.tag == 'Package':
932                        not_found = False
933                        break
934                if not_found:
935                    self.logger.info("Extra Normal Package Instance %s %s" % \
936                                     (name, self.str_evra(installed_inst)))
937                    tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \
938                                     version = installed_inst.get('version'), \
939                                     release = installed_inst.get('release'))
940                    if installed_inst.get('epoch', None) != None:
941                        tmp_entry.set('epoch', str(installed_inst.get('epoch')))
942                    if installed_inst.get('arch', None) != None:
943                        tmp_entry.set('arch', installed_inst.get('arch'))
944
945        if len(extra_entry) == 0:
946            extra_entry = None
947
948        return extra_entry
949
950    def str_evra(self, instance):
951        '''
952            Convert evra dict entries to a string.
953        '''
954        if instance.get('epoch', '*') in ['*', None]:
955            return '%s-%s.%s' % (instance.get('version', '*'),
956                                 instance.get('release', '*'),
957                                 instance.get('arch', '*'))
958        else:
959            return '%s:%s-%s.%s' % (instance.get('epoch', '*'),
960                                    instance.get('version', '*'),
961                                    instance.get('release', '*'),
962                                    instance.get('arch', '*'))
963
964    def pkg_vr_equal(self, config_entry, installed_entry):
965        '''
966            Compare old style entry to installed entry.  Which means ignore
967            the epoch and arch.
968        '''
969        if (config_entry.tag == 'Package' and \
970            config_entry.get('version') == installed_entry.get('version') and \
971            config_entry.get('release') == installed_entry.get('release')):
972            return True
973        else:
974            return False
975
976    def inst_evra_equal(self, config_entry, installed_entry):
977        '''
978            Compare new style instance to installed entry.
979        '''
980
981        if config_entry.get('epoch', None) != None:
982            epoch = int(config_entry.get('epoch'))
983        else:
984            epoch = None
985
986        if (config_entry.tag == 'Instance' and \
987           (epoch == installed_entry.get('epoch', 0) or \
988               (epoch == 0 and installed_entry.get('epoch', 0) == None) or \
989               (epoch == None and installed_entry.get('epoch', 0) == 0)) and \
990           config_entry.get('version') == installed_entry.get('version') and \
991           config_entry.get('release') == installed_entry.get('release') and \
992           config_entry.get('arch', None) == installed_entry.get('arch', None)):
993            return True
994        else:
995            return False
996
997    def getinstalledgpg(self):
998        '''
999           Create a list of installed GPG key IDs.
1000
1001           The pgp-pubkey package version is the least significant 4 bytes
1002           (big-endian) of the key ID which is good enough for our purposes.
1003        '''
1004        init_ts = rpmtools.rpmtransactionset()
1005        init_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES)
1006        gpg_hdrs = rpmtools.getheadersbykeyword(init_ts, **{'name':'gpg-pubkey'})
1007        keyids = [ header[rpm.RPMTAG_VERSION] for header in gpg_hdrs]
1008        keyids.append('None')
1009        init_ts.closeDB()
1010        del init_ts
1011        return keyids
Note: See TracBrowser for help on using the browser.