1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import os
17 import glob
18 import imp
19 import warnings
20 import atexit
21 import gettext
22 import logging
23 import logginglevels
24 from constants import *
25 import config
26 from config import ParsingError, ConfigParser
27 import Errors
28 from parser import ConfigPreProcessor
29
30 from textwrap import fill
31 import fnmatch
32
33 from weakref import proxy as weakref
34
35 from yum import _
36
37 from yum.i18n import utf8_width
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 API_VERSION = '2.6'
68 '''
69 A simple int subclass that used to check when a deprecated constant is used.
70 '''
71
72
73 TYPE_CORE = 0
74 TYPE_INTERACTIVE = 1
75 TYPE_INTERFACE = DeprecatedInt(1)
76 ALL_TYPES = (TYPE_CORE, TYPE_INTERACTIVE)
77
78
79 SLOT_TO_CONDUIT = {
80 'config': 'ConfigPluginConduit',
81 'postconfig': 'PostConfigPluginConduit',
82 'init': 'InitPluginConduit',
83 'args': 'ArgsPluginConduit',
84 'predownload': 'DownloadPluginConduit',
85 'postdownload': 'DownloadPluginConduit',
86 'prereposetup': 'PreRepoSetupPluginConduit',
87 'postreposetup': 'PostRepoSetupPluginConduit',
88 'close': 'PluginConduit',
89 'clean': 'PluginConduit',
90 'pretrans': 'MainPluginConduit',
91 'posttrans': 'MainPluginConduit',
92 'exclude': 'MainPluginConduit',
93 'preresolve': 'DepsolvePluginConduit',
94 'postresolve': 'DepsolvePluginConduit',
95 }
96
97
98 SLOTS = SLOT_TO_CONDUIT.keys()
101 '''Used by plugins to signal that yum should stop
102 '''
103 - def __init__(self, value="", translation_domain=""):
104 self.value = value
105 self.translation_domain = translation_domain
107 if self.translation_domain:
108 return gettext.dgettext(self.translation_domain, self.value)
109 else:
110 return self.value
111
113 '''
114 Manager class for Yum plugins.
115 '''
116
117 - def __init__(self, base, searchpath, optparser=None, types=None,
118 pluginconfpath=None,disabled=None,enabled=None):
119 '''Initialise the instance.
120
121 @param base: The
122 @param searchpath: A list of paths to look for plugin modules.
123 @param optparser: The OptionParser instance for this run (optional).
124 Use to allow plugins to extend command line options.
125 @param types: A sequence specifying the types of plugins to load.
126 This should be sequnce containing one or more of the TYPE_...
127 constants. If None (the default), all plugins will be loaded.
128 @param pluginconfpath: A list of paths to look for plugin configuration
129 files. Defaults to "/etc/yum/pluginconf.d".
130 '''
131 if not pluginconfpath:
132 pluginconfpath = ['/etc/yum/pluginconf.d']
133
134 self.searchpath = searchpath
135 self.pluginconfpath = pluginconfpath
136 self.base = weakref(base)
137 self.optparser = optparser
138 self.cmdline = (None, None)
139 self.verbose_logger = logging.getLogger("yum.verbose.YumPlugins")
140 self.disabledPlugins = disabled
141 self.enabledPlugins = enabled
142 if types is None:
143 types = ALL_TYPES
144 if not isinstance(types, (list, tuple)):
145 types = (types,)
146
147 if id(TYPE_INTERFACE) in [id(t) for t in types]:
148 self.verbose_logger.log(logginglevels.INFO_2,
149 'Deprecated constant TYPE_INTERFACE during plugin '
150 'initialization.\nPlease use TYPE_INTERACTIVE instead.')
151
152 self._importplugins(types)
153
154 self.cmdlines = {}
155
156
157 atexit.register(self.run, 'close')
158
159
160 self.run('config')
161
162 - def run(self, slotname, **kwargs):
163 '''Run all plugin functions for the given slot.
164 '''
165
166 conduitcls = SLOT_TO_CONDUIT.get(slotname, None)
167 if conduitcls is None:
168 raise ValueError('unknown slot name "%s"' % slotname)
169 conduitcls = eval(conduitcls)
170
171 for modname, func in self._pluginfuncs[slotname]:
172 self.verbose_logger.log(logginglevels.DEBUG_4,
173 'Running "%s" handler for "%s" plugin',
174 slotname, modname)
175
176 _, conf = self._plugins[modname]
177 func(conduitcls(self, self.base, conf, **kwargs))
178
180 '''Load plugins matching the given types.
181 '''
182
183
184 self._plugins = {}
185 self._pluginfuncs = {}
186 for slot in SLOTS:
187 self._pluginfuncs[slot] = []
188
189
190 self._used_disable_plugin = set()
191 self._used_enable_plugin = set()
192 for dir in self.searchpath:
193 if not os.path.isdir(dir):
194 continue
195 for modulefile in sorted(glob.glob('%s/*.py' % dir)):
196 self._loadplugin(modulefile, types)
197
198
199 if (self._plugins and
200 not self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)):
201
202 key = _("Loaded plugins: ")
203 val = ", ".join(sorted(self._plugins))
204 nxt = ' ' * (utf8_width(key) - 2) + ': '
205 width = 80
206 if hasattr(self.base, 'term'):
207 width = self.base.term.columns
208 self.verbose_logger.log(logginglevels.INFO_2,
209 fill(val, width=width, initial_indent=key,
210 subsequent_indent=nxt))
211
212 if self.disabledPlugins:
213 for wc in self.disabledPlugins:
214 if wc not in self._used_disable_plugin:
215 self.verbose_logger.log(logginglevels.INFO_2,
216 _("No plugin match for: %s") % wc)
217 del self._used_disable_plugin
218 if self.enabledPlugins:
219 for wc in self.enabledPlugins:
220 if wc not in self._used_enable_plugin:
221 self.verbose_logger.log(logginglevels.INFO_2,
222 _("No plugin match for: %s") % wc)
223 del self._used_enable_plugin
224
225 @staticmethod
227 """ Check if this plugin has been temporary enabled/disabled. """
228 if plugins is None:
229 return False
230
231 for wc in plugins:
232 if fnmatch.fnmatch(modname, wc):
233 used.add(wc)
234 return True
235
236 return False
237
238
240 '''Attempt to import a plugin module and register the hook methods it
241 uses.
242 '''
243 dir, modname = os.path.split(modulefile)
244 modname = modname.split('.py')[0]
245
246 conf = self._getpluginconf(modname)
247 if (not conf or
248 (not config.getOption(conf, 'main', 'enabled',
249 config.BoolOption(False)) and
250 not self._plugin_cmdline_match(modname, self.enabledPlugins,
251 self._used_enable_plugin))):
252 self.verbose_logger.debug(_('Not loading "%s" plugin, as it is disabled'), modname)
253 return
254
255 try:
256 fp, pathname, description = imp.find_module(modname, [dir])
257 try:
258 module = imp.load_module(modname, fp, pathname, description)
259 finally:
260 fp.close()
261 except:
262 if self.verbose_logger.isEnabledFor(logginglevels.DEBUG_4):
263 raise
264 self.verbose_logger.error(_('Plugin "%s" can\'t be imported') %
265 modname)
266 return
267
268
269 if not hasattr(module, 'requires_api_version'):
270 self.verbose_logger.error(
271 _('Plugin "%s" doesn\'t specify required API version') %
272 modname)
273 return
274 if not apiverok(API_VERSION, module.requires_api_version):
275 self.verbose_logger.error(
276 _('Plugin "%s" requires API %s. Supported API is %s.') % (
277 modname,
278 module.requires_api_version,
279 API_VERSION,
280 ))
281 return
282
283
284 plugintypes = getattr(module, 'plugin_type', ALL_TYPES)
285 if not isinstance(plugintypes, (list, tuple)):
286 plugintypes = (plugintypes,)
287
288 if len(plugintypes) < 1:
289 return
290 for plugintype in plugintypes:
291 if id(plugintype) == id(TYPE_INTERFACE):
292 self.verbose_logger.log(logginglevels.INFO_2,
293 'Plugin "%s" uses deprecated constant '
294 'TYPE_INTERFACE.\nPlease use TYPE_INTERACTIVE '
295 'instead.', modname)
296
297 if plugintype not in types:
298 return
299
300
301
302
303 if (self._plugin_cmdline_match(modname, self.disabledPlugins,
304 self._used_disable_plugin) and
305 not self._plugin_cmdline_match(modname, self.enabledPlugins,
306 self._used_enable_plugin)):
307 return
308
309 self.verbose_logger.log(logginglevels.DEBUG_3, _('Loading "%s" plugin'),
310 modname)
311
312
313 if modname not in self._plugins:
314 self._plugins[modname] = (module, conf)
315 else:
316 raise Errors.ConfigError(_('Two or more plugins with the name "%s" ' \
317 'exist in the plugin search path') % modname)
318
319 for slot in SLOTS:
320 funcname = slot+'_hook'
321 if hasattr(module, funcname):
322 self._pluginfuncs[slot].append(
323 (modname, getattr(module, funcname))
324 )
325
327 '''Parse the plugin specific configuration file and return a
328 IncludingConfigParser instance representing it. Returns None if there
329 was an error reading or parsing the configuration file.
330 '''
331 for dir in self.pluginconfpath:
332 conffilename = os.path.join(dir, modname + ".conf")
333 if os.access(conffilename, os.R_OK):
334
335 break
336 self.verbose_logger.log(logginglevels.INFO_2, _("Configuration file %s not found") % conffilename)
337 else:
338
339 self.verbose_logger.log(logginglevels.INFO_2, _("Unable to find configuration file for plugin %s")
340 % modname)
341 return None
342 parser = ConfigParser()
343 confpp_obj = ConfigPreProcessor(conffilename)
344 try:
345 parser.readfp(confpp_obj)
346 except ParsingError, e:
347 raise Errors.ConfigError("Couldn't parse %s: %s" % (conffilename,
348 str(e)))
349 return parser
350
352 '''Set the parsed command line options so that plugins can access them
353 '''
354 self.cmdline = (opts, commands)
355
358 '''
359 This class provides basic emulation of the YumPlugins class. It exists so
360 that calls to plugins.run() don't fail if plugins aren't in use.
361 '''
362 - def run(self, *args, **kwargs):
364
367
369 - def __init__(self, parent, base, conf):
370 self._parent = parent
371 self._base = base
372 self._conf = conf
373
374 self.logger = logging.getLogger("yum.plugin")
375 self.verbose_logger = logging.getLogger("yum.verbose.plugin")
376
377 - def info(self, level, msg):
380
381 - def error(self, level, msg):
384
391
393 import yum
394 return yum.__version__
395
397 '''Return the optparse.OptionParser instance for this execution of Yum
398
399 In the "config" and "init" slots a plugin may add extra options to this
400 instance to extend the command line options that Yum exposes.
401
402 In all other slots a plugin may only read the OptionParser instance.
403 Any modification of the instance at this point will have no effect.
404
405 See the getCmdLine() method for details on how to retrieve the parsed
406 values of command line options.
407
408 @return: the global optparse.OptionParser instance used by Yum. May be
409 None if an OptionParser isn't in use.
410 '''
411
412
413
414 return self._parent.optparser
415
416 - def confString(self, section, opt, default=None):
417 '''Read a string value from the plugin's own configuration file
418
419 @param section: Configuration file section to read.
420 @param opt: Option name to read.
421 @param default: Value to read if option is missing.
422 @return: String option value read, or default if option was missing.
423 '''
424
425 return config.getOption(self._conf, section, opt, config.Option(default))
426
427 - def confInt(self, section, opt, default=None):
428 '''Read an integer value from the plugin's own configuration file
429
430 @param section: Configuration file section to read.
431 @param opt: Option name to read.
432 @param default: Value to read if option is missing.
433 @return: Integer option value read, or default if option was missing or
434 could not be parsed.
435 '''
436 return config.getOption(self._conf, section, opt, config.IntOption(default))
437
438 - def confFloat(self, section, opt, default=None):
439 '''Read a float value from the plugin's own configuration file
440
441 @param section: Configuration file section to read.
442 @param opt: Option name to read.
443 @param default: Value to read if option is missing.
444 @return: Float option value read, or default if option was missing or
445 could not be parsed.
446 '''
447 return config.getOption(self._conf, section, opt, config.FloatOption(default))
448
449 - def confBool(self, section, opt, default=None):
450 '''Read a boolean value from the plugin's own configuration file
451
452 @param section: Configuration file section to read.
453 @param opt: Option name to read.
454 @param default: Value to read if option is missing.
455 @return: Boolean option value read, or default if option was missing or
456 could not be parsed.
457 '''
458 return config.getOption(self._conf, section, opt, config.BoolOption(default))
459
461 self._base.run_with_package_names.add(name)
462
465
466 - def registerOpt(self, name, valuetype, where, default):
467 '''Register a yum configuration file option.
468
469 @param name: Name of the new option.
470 @param valuetype: Option type (PLUG_OPT_BOOL, PLUG_OPT_STRING ...)
471 @param where: Where the option should be available in the config file.
472 (PLUG_OPT_WHERE_MAIN, PLUG_OPT_WHERE_REPO, ...)
473 @param default: Default value for the option if not set by the user.
474 '''
475 warnings.warn('registerOpt() will go away in a future version of Yum.\n'
476 'Please manipulate config.YumConf and config.RepoConf directly.',
477 DeprecationWarning)
478
479 type2opt = {
480 PLUG_OPT_STRING: config.Option,
481 PLUG_OPT_INT: config.IntOption,
482 PLUG_OPT_BOOL: config.BoolOption,
483 PLUG_OPT_FLOAT: config.FloatOption,
484 }
485
486 if where == PLUG_OPT_WHERE_MAIN:
487 setattr(config.YumConf, name, type2opt[valuetype](default))
488
489 elif where == PLUG_OPT_WHERE_REPO:
490 setattr(config.RepoConf, name, type2opt[valuetype](default))
491
492 elif where == PLUG_OPT_WHERE_ALL:
493 option = type2opt[valuetype](default)
494 setattr(config.YumConf, name, option)
495 setattr(config.RepoConf, name, config.Inherit(option))
496
498 if hasattr(self._base, 'registerCommand'):
499 self._base.registerCommand(command)
500 else:
501 raise Errors.ConfigError(_('registration of commands not supported'))
502
503 -class PostConfigPluginConduit(ConfigPluginConduit):
504
506 return self._base.conf
507
509
511 return self._base.conf
512
514 '''Return Yum's container object for all configured repositories.
515
516 @return: Yum's RepoStorage instance
517 '''
518 return self._base.repos
519
521
522 - def __init__(self, parent, base, conf, args):
525
528
530
532 '''Return parsed command line options.
533
534 @return: (options, commands) as returned by OptionParser.parse_args()
535 '''
536 return self._parent.cmdline
537
539 '''Return a representation of local RPM database. This allows querying
540 of installed packages.
541
542 @return: rpmUtils.RpmDBHolder instance
543 '''
544 return self._base.rpmdb
545
546 -class PostRepoSetupPluginConduit(PreRepoSetupPluginConduit):
547
548 - def getGroups(self):
549 '''Return group information.
550
551 @return: yum.comps.Comps instance
552 '''
553 return self._base.comps
554
556
557 - def __init__(self, parent, base, conf, pkglist, errors=None):
561
563 '''Return a list of package objects representing packages to be
564 downloaded.
565 '''
566 return self._pkglist
567
569 '''Return a dictionary of download errors.
570
571 The returned dictionary is indexed by package object. Each element is a
572 list of strings describing the error.
573 '''
574 if not self._errors:
575 return {}
576 return self._errors
577
578 -class MainPluginConduit(PostRepoSetupPluginConduit):
579
580 - def getPackages(self, repo=None):
581 if repo:
582 arg = repo.id
583 else:
584 arg = None
585 return self._base.pkgSack.returnPackages(arg)
586
587 - def getPackageByNevra(self, nevra):
588 '''Retrieve a package object from the packages loaded by Yum using
589 nevra information
590
591 @param nevra: A tuple holding (name, epoch, version, release, arch)
592 for a package
593 @return: A PackageObject instance (or subclass)
594 '''
595 return self._base.getPackageObject(nevra)
596
597 - def delPackage(self, po):
598 po.repo.sack.delPackage(po)
599
600 - def getTsInfo(self):
601 return self._base.tsInfo
602
604 - def __init__(self, parent, base, conf, rescode=None, restring=[]):
608
611 maj, min = apiver.split('.')
612 return int(maj), int(min)
613
615 '''Return true if API version "a" supports API version "b"
616 '''
617 a = parsever(a)
618 b = parsever(b)
619
620 if a[0] != b[0]:
621 return 0
622
623 if a[1] >= b[1]:
624 return 1
625
626 return 0
627