summaryrefslogtreecommitdiffstats
path: root/doc/development/plugins.txt
blob: 96469602d9b99caa530bc12e89d4eafc56f296a4 (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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
.. -*- mode: rst -*-

.. _development-plugins:

Bcfg2 Plugin development
========================

While the Bcfg2 server provides a good interface for representing
general system configurations, its plugin interface offers the ability
to implement configuration interfaces and representation tailored to
problems encountered by a particular site. This chapter describes what
plugins are good for, what they can do, and how to implement them.

Bcfg2 Plugins
-------------

Bcfg2 plugins are loadable python modules that the Bcfg2 server loads at
initialization time. These plugins can contribute to the functions already
offered by the Bcfg2 server or can extend its functionality. In general,
plugins will provide some portion of the configuration for clients, with a
data representation that is tuned for a set of common tasks. Much of the
core functionality of Bcfg2 is implemented by several plugins, however,
they are not special in any way; new plugins could easily supplant one
or all of them.

Server Plugin Types
-------------------

A plugin must implement at least one of the interfaces described
below.  Each interface is available as a class in
``Bcfg2.Server.Plugin``.  In most cases, a plugin must also inherit
from ``Bcfg2.Server.Plugin.Plugin``, which is the base Plugin object
(described below).  Some of the interfaces listed below are themselves
Plugin objects, so your custom plugin would only need to inherit from
the plugin type.

Generator
^^^^^^^^^

Generator plugins contribute to literal client configurations.  That
is, they generate entry contents.  Examples are
:ref:`server-plugins-generators-cfg` and
:ref:`server-plugins-generators-sshbase`.

An entry is generated in one of two ways:

#.  First, the Bcfg2 core looks in the ``Entries`` dict attribute of
 the plugin object.  ``Entries`` is expected to be a dict whose keys
 are entry tags (e.g., ``"Path"``, ``"Service"``, etc.) and whose
 values are dicts; those dicts should map the ``name`` attribute of an
 entry to a callable that will be called to generate the content.  The
 callable will receive two arguments: the abstract entry (as an
 lxml.etree._Element object), and the client metadata object the entry
 is being generated for.
#.  Second, if the entry is not listed in ``Entries``, the Bcfg2 core
 calls ``HandlesEntry(entry, metadata)``; if that returns True, then
 it calls ``HandleEntry(entry, metadata)``.

The Generator plugin should provide one or both methods to bind
entries, but does not have to provide both.

Both ``HandleEntry()`` and the callable objects in the ``Entries``
dict should return an lxml.etree._Element object representing the
fully-bound entry.  They should raise
``Bcfg2.Server.Plugin.PluginExecutionError`` with a sensible error
message on failure.

Structure
^^^^^^^^^

Structure Plugins contribute to abstract client configurations.  That
is, they produce lists of entries that will be generated for a client.
:ref:`server-plugins-structure-bundler-index` is a Structure plugin.

Structure plugins must implement one method: ``BuildStructures()``,
which is called with a single argument, a client metadata object
structures should be built for.  It should return a list of
lxml.etree._Element objects that will be added to the top-level
``<Configuration>`` tag of the client configuration.  Consequently,
each object in the list must consist of a container tag (e.g.,
``<Bundle>`` or ``<Independent>``) which contains the entry tags.  It
must not return a list of entry tags.

Metadata
^^^^^^^^

Metadata plugins provide client metadata.
:ref:`server-plugins-grouping-metadata` is a Metadata plugin.

Metadata plugins **must** implement the following methods:

* ``AuthenticateConnection(cert, user, password, addresspair)``:
  Authenticate the given client.  Arguments are:
  * ``cert``: an x509 certificate dict;
  * ``user``: The username of the user trying to authenticate;
  * ``password``: The password supplied by the client;
  * ``addresspair``: A tuple of ``(<ip address>, <hostname>)``
  ``AuthenticateConnection()`` should return True if the authenticate
  succeeds, False otherwise.  Failures should be logged at the error
  level.
* ``get_initial_metadata(client_name)``: Return a ClientMetadata
  object that fully describes everything the Metadata plugin knows
  about the named client.  See :file:``Metadata.py`` for a reference
  implementation of the ClientMetadata object.
* ``merge_additional_data(metadata, source, data)``: Add data from the
  Connector plugin named by ``<source>`` (a string giving the name of
  the Connector plugin) to the given metadata object.
  ``merge_additional_data()`` should modify the ``metadata`` object in
  place; it doesn't need to return anything.
* ``merge_additional_groups(metadata, groups)``: Add groups from an
  anonymous Connector plugin to the given metadata object.
  ``merge_additional_groups()`` should modify the ``metadata`` object in
  place; it doesn't need to return anything.

Metadata plugins **may** implement the following methods:

* ``viz(hosts, bundles, key, only_client, colors)``: Return a string
  containing a graphviz document that maps out the Metadata.  The
  first three options are boolean, and describe whether or not the
  named item(s) should be included in the graphviz document.
  ``only_client`` is the name of a client which, if included, will be
  the only client whose metadata will be included on the map.
  ``colors`` is a list of graphviz color names to use.  If
  unimplemented, the empty string will be returned.
* ``set_version(client, version)``: Set the version for the named
  client to the specified version string.  If unimplemented, setting
  the version of a client will fail silently.
* ``set_profile(client, profile, addresspair)``: Set the profile for
  the named client to the named profile group.  If unimplemented,
  setting a client profile will fail silently.
* ``resolve_client(addresspair, cleanup_cache=False)``: Given a tuple
  of ``(<ip address>, <hostname>)``, resolve the canonical name of
  this client.  If this method is not implemented, the hostname
  claimed by the client is used.  (This may be a security risk; it's
  highly recommended that you implement ``resolve_client`` if you are
  writing a Metadata plugin.)

Connector
^^^^^^^^^

Connector plugins augment client metadata instances with additional
data, additional groups, or both.  Connector plugins include
:ref:`server-plugins-grouping-grouppatterns`,
:ref:`server-plugins-connectors-properties`, and
:ref:`server-plugins-probes-index`.

Connector plugins should implement one or all of the following
methods:

* ``get_additional_groups(metadata)``: Return a list of additional
  groups for the given ClientMetadata object.  If unimplemented, the
  empty list is returned.
* ``get_additional_data(metadata)``: Return arbitrary additional data
  for the given ClientMetadata object.  By convention this is usually
  a dict object, but doesn't need to be.  If unimplemented, the empty
  dict is returned.

Probing
^^^^^^^

Probing plugins can collect data from clients and process it.
Examples include :ref:`server-plugins-probes-index` and
:ref:`server-plugins-probes-fileprobes`.

Probing plugins must implement the following methods:

* ``GetProbes(metadata)``: Return a list of probes for the given
  ClientMetadata object.  Each probe should be an XML document,
  described below.
* ``ReceiveData(metadata, datalist)``: Process data returned from the
  probes for the given ClientMetadata object.  ``datalist`` is a list
  of lxml.etree._Element objects, each of which is a single tag; the
  ``name`` attribute holds the unique name of the probe that was run,
  and the text contents of the tag hold the results of the probe.

``GetProbes()`` returns a list of probes, each of which is an
``lxml.etree._Element`` object that adheres to the following
specification.  Each probe must the following attributes:

* ``name``: The unique name of the probe.
* ``source``: The origin of the probe; probably the name of the plugin
  that supplies the probe.
* ``interpreter``: The command that will be run on the client to
  interpret the probe script.  Compiled (i.e., non-interpreted) probes
  are not supported.

The text of the XML tag should be the contents of the probe, i.e., the
code that will be run on the client.

Statistics
^^^^^^^^^^

Signal statistics handling capability

Decision
^^^^^^^^

Signal decision handling capability

Version
^^^^^^^

Interact with various version control systems

Writing Bcfg2 Server Plugins
----------------------------

Bcfg2 plugins are python classes that subclass from
Bcfg2.Server.Plugin.Plugin. Several plugin-specific values must be set
in the new plugin. These values dictate how the new plugin will behave
with respect to the above four functions.  The following table describes
all important member fields.

+-----------------+-----------------------------------+--------------------------+
| Name            | Description                       | Format                   |
+=================+===================================+==========================+
| __name__        | The name of the plugin            | string                   |
+-----------------+-----------------------------------+--------------------------+
| __version__     | The plugin version (generally     | string                   |
|                 | tied to revctl keyword expansion) |                          |
+-----------------+-----------------------------------+--------------------------+
| __author__      | The plugin author                 | string                   |
+-----------------+-----------------------------------+--------------------------+
| __rmi__         | Set of functions to be exposed as | List of function names   |
|                 | XML-RPC functions                 | (strings)                |
+-----------------+-----------------------------------+--------------------------+
| Entries         | Multidimentional dictionary of    | Dictionary of            |
|                 | keys that point to the function   | ConfigurationEntityType, |
|                 | used to bind literal contents for | Name keys, and function  |
|                 | a given configuration entity      | reference values         |
+-----------------+-----------------------------------+--------------------------+
| BuildStructures | Function that returns a list of   | Member function          |
|                 | the structures for a given client |                          |
+-----------------+-----------------------------------+--------------------------+
| GetProbes       | Function that returns a list of   | Member function          |
|                 | probes that a given client should |                          |
|                 | execute                           |                          |
+-----------------+-----------------------------------+--------------------------+
| ReceiveData     | Function that accepts the probe   | Member function          |
|                 | results for a given client        |                          |
+-----------------+-----------------------------------+--------------------------+

Example Plugin
^^^^^^^^^^^^^^

.. code-block:: python

    import Bcfg2.Server.Plugin
    class MyPlugin(Bcfg2.Server.Plugin.Plugin):
       '''An example plugin'''
       # All plugins need to subclass Bcfg2.Server.Plugin.Plugin
       __name__ = 'MyPlugin'
       __version__ = '1'
       __author__ = 'me@me.com'
       __rmi__ = ['myfunction']
       # myfunction is now available remotely as MyPlugin.myfunction

       def __init__(self, core, datastore):
           Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
           self.Entries = {'Path':{'/etc/foo.conf': self.buildFoo}}

       def myfunction(self):
           '''function for xmlrpc rmi call'''
           #do something
           return True

       def buildFoo(self, entry, metadata):
           '''Bind per-client information into entry based on metadata'''
           entry.attrib.update({'type':'file', 'owner':'root', 'group':'root', 'perms':'644'})
           entry.text = '''contents of foo.conf'''

Example Connector
^^^^^^^^^^^^^^^^^

.. code-block:: python

    import Bcfg2.Server.Plugin

    class Foo(Bcfg2.Server.Plugin.Plugin,
             Bcfg2.Server.Plugin.Connector):
        '''The Foo plugin is here to illustrate a barebones connector'''
        name = 'Foo'
        experimental = True

        def __init__(self, core, datastore):
            Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
            Bcfg2.Server.Plugin.Connector.__init__(self)
            self.store = XMLFileBacked(self.data, core.fam)

        def get_additional_data(self, metadata):

            mydata = {}
            for data in self.store.entries['foo.xml'].xdata.get("foo", []):

                mydata[data] = "bar"

            return  dict([('mydata', mydata)])

        def get_additional_groups(self, meta):
            return self.cgroups.get(meta.hostname, list())

Example Metadata plugin
^^^^^^^^^^^^^^^^^^^^^^^

If you would like to define your own Metadata plugin (to extend/change
functionality of the existing Metadata plugin), here are the steps to
do so. We will call our new plugin `MyMetadata`.

#. Add MyMetadata.py

   .. code-block:: python

       import Bcfg2.Server.Plugins.Metadata

       class MyMetadata(Bcfg2.Server.Plugins.Metadata.Metadata):
           '''This class contains data for bcfg2 server metadata'''
           __author__ = 'bcfg-dev@mcs.anl.gov'

           def __init__(self, core, datastore, watch_clients=True):
               Bcfg2.Server.Plugins.Metadata.Metadata.__init__(self, core, datastore, watch_clients)

#. Add MyMetadata to ``src/lib/Server/Plugins/__init__.py``
#. Replace Metadata with MyMetadata in the plugins line of bcfg2.conf