1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
""" PackagesSources handles the
:ref:`server-plugins-generators-packages` ``sources.xml`` file"""
import os
import sys
import Bcfg2.Server.Plugin
from Bcfg2.Server.Plugins.Packages.Source import SourceInitError
# pylint: disable=E0012,R0924
class PackagesSources(Bcfg2.Server.Plugin.StructFile,
Bcfg2.Server.Plugin.Debuggable):
""" PackagesSources handles parsing of the
:mod:`Bcfg2.Server.Plugins.Packages` ``sources.xml`` file, and the
creation of the appropriate
:class:`Bcfg2.Server.Plugins.Packages.Source.Source` object for
each ``Source`` tag. """
__identifier__ = None
create = "Sources"
def __init__(self, filename, cachepath, fam, packages, setup):
"""
:param filename: The full path to ``sources.xml``
:type filename: string
:param cachepath: The full path to the directory where
:class:`Bcfg2.Server.Plugins.Packages.Source.Source`
data will be cached
:type cachepath: string
:param fam: The file access monitor to use to create watches
on ``sources.xml`` and any XIncluded files.
:type fam: Bcfg2.Server.FileMonitor.FileMonitor
:param packages: The Packages plugin object ``sources.xml`` is
being parsed on behalf of (i.e., the calling
object)
:type packages: Bcfg2.Server.Plugins.Packages.Packages
:param setup: A Bcfg2 options dict
:type setup: dict
:raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginInitError` -
If ``sources.xml`` cannot be read
"""
Bcfg2.Server.Plugin.Debuggable.__init__(self)
Bcfg2.Server.Plugin.StructFile.__init__(self, filename, fam=fam,
should_monitor=True)
#: The full path to the directory where
#: :class:`Bcfg2.Server.Plugins.Packages.Source.Source` data
#: will be cached
self.cachepath = cachepath
if not os.path.exists(self.cachepath):
# create cache directory if needed
try:
os.makedirs(self.cachepath)
except OSError:
err = sys.exc_info()[1]
self.logger.error("Could not create Packages cache at %s: %s" %
(self.cachepath, err))
#: The Bcfg2 options dict
self.setup = setup
#: The :class:`Bcfg2.Server.Plugins.Packages.Packages` that
#: instantiated this ``PackagesSources`` object
self.pkg_obj = packages
#: The set of all XML files that have been successfully
#: parsed. This is used by :attr:`loaded` to determine if the
#: sources have been fully parsed and the
#: :class:`Bcfg2.Server.Plugins.Packages.Packages` plugin
#: should be told to reload its data.
self.parsed = set()
def set_debug(self, debug):
Bcfg2.Server.Plugin.Debuggable.set_debug(self, debug)
for source in self.entries:
source.set_debug(debug)
set_debug.__doc__ = Bcfg2.Server.Plugin.Plugin.set_debug.__doc__
def HandleEvent(self, event=None):
""" HandleEvent is called whenever the FAM registers an event.
When :attr:`loaded` becomes True,
:func:`Bcfg2.Server.Plugins.Packages.Packages.Reload` is
called to reload all plugin data from the configured sources.
:param event: The event object
:type event: Bcfg2.Server.FileMonitor.Event
:returns: None
"""
if event and event.filename != self.name:
for fpath in self.extras:
if fpath == os.path.abspath(event.filename):
self.parsed.add(fpath)
break
Bcfg2.Server.Plugin.StructFile.HandleEvent(self, event=event)
if self.loaded:
self.logger.info("Reloading Packages plugin")
self.pkg_obj.Reload()
@property
def loaded(self):
""" Whether or not all XML files (``sources.xml`` and
everything XIncluded in it) have been parsed. This flag is
used to determine if the Packages plugin should be told to
load its data. """
return sorted(list(self.parsed)) == sorted(self.extras)
@Bcfg2.Server.Plugin.track_statistics()
def Index(self):
Bcfg2.Server.Plugin.StructFile.Index(self)
self.entries = []
if self.loaded:
for xsource in self.xdata.findall('.//Source'):
source = self.source_from_xml(xsource)
if source is not None:
self.entries.append(source)
Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__ + """
``Index`` is responsible for calling :func:`source_from_xml`
for each ``Source`` tag in each file. """
@Bcfg2.Server.Plugin.track_statistics()
def source_from_xml(self, xsource):
""" Create a
:class:`Bcfg2.Server.Plugins.Packages.Source.Source` subclass
object from XML representation of a source in ``sources.xml``.
``source_from_xml`` determines the appropriate subclass of
``Source`` to instantiate according to the ``type`` attribute
of the ``Source`` tag.
:param xsource: The XML tag representing the source
:type xsource: lxml.etree._Element
:returns: :class:`Bcfg2.Server.Plugins.Packages.Source.Source`
subclass, or None on error
"""
stype = xsource.get("type")
if stype is None:
self.logger.error("Packages: No type specified for source at %s, "
"skipping" % (xsource.get("rawurl",
xsource.get("url"))))
return None
try:
module = getattr(__import__("Bcfg2.Server.Plugins.Packages.%s" %
stype.title()).Server.Plugins.Packages,
stype.title())
cls = getattr(module, "%sSource" % stype.title())
except (ImportError, AttributeError):
err = sys.exc_info()[1]
self.logger.error("Packages: Unknown source type %s (%s)" % (stype,
err))
return None
try:
source = cls(self.cachepath, xsource, self.setup)
except SourceInitError:
err = sys.exc_info()[1]
self.logger.error("Packages: %s" % err)
source = None
return source
def __getitem__(self, key):
return self.entries[key]
def __repr__(self):
return "PackagesSources: %s" % repr(self.entries)
def __str__(self):
return "PackagesSources: %s sources" % len(self.entries)
def __len__(self):
return len(self.entries)
|