.. -*- mode: rst -*-

.. _server-plugins-connectors-properties:

==========
Properties
==========

The Properties plugin is a connector plugin that adds information from
XML, JSON, and YAML files into client metadata instances.

Enabling Properties
===================

First, ``mkdir /var/lib/bcfg2/Properties``. Each property file goes in
this directory. Each will automatically be cached by the server, and
reread/reparsed upon changes. Add **Properties** to your ``plugins``
line in ``/etc/bcfg2.conf``.

Data Structures
===============

Properties adds a new dictionary to client metadata instances that maps
property file names to PropertyFile instances.

A property file can be one of three types:

* If the filename ends with ``.xml``, it will be parsed as XML and
  handled by :class:`Bcfg2.Server.Plugins.Properties.XMLPropertyFile`.
  See `XML Property Files`_ below.
* If the filename ends with ``.json`` and JSON libraries are installed
  (either ``json`` or ``simplejson``, although ``json`` is highly
  recommended), it will be parsed as `JSON <http://www.json.org/>`_
  and handled by
  :class:`Bcfg2.Server.Plugins.Properties.JSONPropertyFile`.  See
  `JSON Property Files`_ below.
* If the filename ends with ``.yaml`` or ``.yml`` and PyYAML is
  installed, it will be parsed as `YAML <http://www.yaml.org/>`_ and
  handled by
  :class:`Bcfg2.Server.Plugins.Properties.YAMLPropertyFile`.  See
  `YAML Property Files`_ below.

The XML interface is undoubtably the most powerful, as it natively
supports schemas to check the data validity, client- and
group-specific data, and data encryption.

Usage
=====

Common Interface
----------------

Different data types have different interfaces, but there are some
usage patterns common to all properties files.

Specific property files can be referred to in templates as
``metadata.Properties[<filename>]``.

The data in property files is accessible via different attributes:

+-----------+----------------+
| Data Type | Data Attribute |
+===========+================+
| XML       | ``xdata``      |
+-----------+----------------+
| JSON      | ``json``       |
+-----------+----------------+
| YAML      | ``yaml``       |
+-----------+----------------+

For instance, in a :ref:`Genshi template
<server-plugins-generators-cfg-genshi>`, you might do::

    {% for item in metadata.Properties['foo.json'].json %}\
    ${item}
    {% end %}\

    {% for key, value in metadata.Properties['foo.yml'].yaml %}\
    ${key} = ${value}
    {% end %}\

    {% for el in metadata.Properties['foo.xml'].xdata.findall("Tag") %}\
    ${el.get("name")} = ${el.text}
    {% end %}\

The raw contents of a properties file as a string are available via
the ``data`` attribute, e.g., ``metadata.Properties['prop-file'].data``.

.. _server-plugins-connectors-properties-write-back:

Writing to Properties files
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 1.2.0

If you need to make persistent changes to properties data, you can use
the ``write`` method of the
:class:`Bcfg2.Server.Plugins.Properties.PropertyFile` class::

    {% python
    import lxml.etree
    from genshi.template import TemplateError
    lxml.etree.SubElement(metadata.Properties['foo.xml'],
                          "Client",
                          name=metadata.hostname)
    if not metadata.Properties['foo.xml'].write():
        raise TemplateError("Failed to write changes back to foo.xml")

The interface is the same for YAML or JSON data.

If writing XML data, the ``write`` method checks the data in the
object against its schema before writing it; see `Data Structures`_
for details.

Note that use of the ``write`` method can cause race conditions if you
run more than one Bcfg2 server.  If you run more than one Bcfg2
server, you can disable Properties write-back by setting the following
in ``bcfg2.conf``::

    [properties]
    writes_enabled = false

.. _server-plugins-connectors-properties-xml:

XML Property Files
------------------

The data in an XML property file can be accessed with the ``xdata``
attribute, an :class:`lxml.etree._Element` object documented `here
<http://codespeak.net/lxml/tutorial.html#the-element-class>`_.

In addition to the ``xdata`` attribute that can be used to access the
raw data, the following access methods are defined:

* ``Match()`` parses the Group and Client tags in the file and returns
  a list of elements that apply to the client described by a set of
  metadata.  For instance::

    {% python
    ntp_servers = [el.text
                   for el in metadata.Properties['ntp.xml'].Match(metadata)
                   if el.tag == "Server"]
    %}
* ``XMLMatch()`` parses the Group and Client tags in the file and
  returns an XML document containing only the data that applies to the
  client described by a set of metadata.  (The Group and Client tags
  themselves are also removed, leaving only the tags and data
  contained in them.)  For instance::

    {% python
    ntp_servers = [el.text
                   for el in metadata.Properties['ntp.xml'].XMLMatch(metadata).findall("//Server")]
    %}

  ``XMLMatch()`` can be run automatically on properties files by using
  the :ref:`server-plugins-connectors-properties-automatch` feature.

You can also access the XML data that comprises a property file
directly in one of several ways:

* ``metadata.Properties['prop-file'].xdata`` is an lxml.etree._Element
  object representing the top-level element in the file.
* ``metadata.Properties['prop-file'].data`` is the raw contents of the
  property file as a string.
* ``metadata.Properties['prop-file'].entries`` is a list of
  lxml.etree._Element objects representing the direct children of the
  top-level element.  (I.e., everything directly under the
  ``<Properties>`` tag.)

The XML data in a property file is arbitrary, but a matching ``.xsd``
file can be created to assign a schema to a property file, which will
be checked when running ``bcfg2-lint``.  For instance, given::

    Properties/dns-config.xml
    Properties/dns-config.xsd

``dns-config.xml`` will be validated against ``dns-config.xsd``.

Although Properties files are technically freeform XML, the top-level
XML tag should be ``<Properties>``.


JSON Property Files
-------------------

.. versionadded:: 1.3.0

The data in a JSON property file can be accessed with the ``json``
attribute, which is the loaded JSON data.  The JSON properties
interface does not provide any additional functionality beyond the
`Common Interface`_.

YAML Property Files
-------------------

.. versionadded:: 1.3.0

The data in a YAML property file can be accessed with the ``yaml``
attribute, which is the loaded YAML data.  Only a single YAML document
may be included in a file.

The YAML properties interface does not provide any additional
functionality beyond the `Common Interface`_.

.. _server-plugins-connectors-properties-automatch:

Automatch
=========

.. versionadded:: 1.3.0

You can enable
:func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch()` for all XML
Property files by setting ``automatch`` to ``true`` in the
``[properties]`` section of ``bcfg2.conf``.  This makes
``metadata.Properties`` values :class:`lxml.etree._Element` objects
that contain only matching data.  (This makes it impossible to do
:ref:`server-plugins-connectors-properties-write-back` as a
side-effect.)

In Python terms, setting ``automatch=true`` is the same as doing the
following at the top of each template::

    {% python
    for prop in metadata.Properties.values():
        prop = prop.XMLMatch(metadata)
    %}

The example above that describes ``XMLMatch()`` would then become
simply::

    {% python
    ntp_servers = [el.text
                   for el in metadata.Properties['ntp.xml'].findall("//Server")]
    %}

You can also enable automatch for individual Property files by setting
the attribute ``automatch="true"`` on the top-level ``<Properties>``
tag.  Conversely, if automatch is enabled by default in
``bcfg2.conf``, you can disable it for an individual Property file by
setting ``automatch="false"`` on the top-level ``<Properties>`` tag.

If you want to see what ``XMLMatch()``/automatch would produce for a
given client on a given Properties file, you can use :ref:`bcfg2-info
<server-bcfg2-info>`::

    bcfg2-info automatch props.xml foo.example.com

If automatch is not enabled, you can force ``bcfg2-info`` to perform
it anyway with ``-f``::

    bcfg2-info automatch -f props.xml foo.example.com

.. note::

    Be sure to notice that enabling automatch changes the type of the
    data in ``metadata.Properties``; with automatch disabled, the
    values of the ``metadata.Properties`` dict are
    :class:`Bcfg2.Server.Plugins.Properties.PropertyFile` objects.
    With automatch enabled, they are :class:`lxml.etree._Element`
    objects.

.. _server-plugins-connectors-properties-encryption:

Encrypted Properties data
=========================

.. versionadded:: 1.3.0

You can encrypt selected data in XML Properties files to protect that
data from other people who need access to the repository.  The
data is decrypted transparently on-the-fly by the server; you never
need to decrypt the data in your templates.  Encryption is only
supported on XML properties files.

See :ref:`server-encryption` for details on encryption in general, and
:ref:`xml-encryption` for details on encryption in XML files.

Accessing Properties contents from Genshi Templates
===================================================

Access contents of ``Properties/auth.xml``::

    ${metadata.Properties['auth.xml'].xdata.find('file').find('bcfg2.key').text}

Configuration
=============

``bcfg2.conf`` contains several miscellaneous configuration options
for the Properties plugin, which can be set in the ``[properties]``
section. Any booleans in the config file accept the values "1", "yes",
"true", and "on" for True, and "0", "no", "false", and "off" for
False.

It understands the following directives:

* ``automatch``: Enable
  :ref:`server-plugins-connectors-properties-automatch`.  Default is
  false.
* ``writes_enabled``: Enable
  :ref:`server-plugins-connectors-properties-write-back`.  Default is
  true.

Module Documentation
====================

.. automodule:: Bcfg2.Server.Plugins.Properties
