summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
blob: c47e1820175e7584fa4a893ff8fa7b2b37d33539 (plain)
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)