Component.configure()
=====================

The first step in the component lifecycle is the configuration. It determines
the target state of a component by computing attributes and configuring sub-
components.

.. code-block:: python

    from batou.component import Component

    class Frontend(Component):

        def configure(self):
            self += File('hostname',
                content='{{self.host.fqdn}}')


Top-level components versus sub-components
------------------------------------------

Top-level components
~~~~~~~~~~~~~~~~~~~~

Component classes that are defined in your project's
:file:`components/*/component.py` files are called "top-level" or "root"
components and they are special in the following sense:

* they can be assigned to hosts in your environments' configuration file
* they do not need a ``namevar`` attribute
* their class attributes can be overriden by environment and with secrets
* they provide the granularity for automatic ordering of components during
* they get assigned to the ``batou.c`` namespace to make them referencable from the other   
  :file:`component.py` files.
* they are instantiated once per host that they are assigned to in an environment

A component class that is defined outside of a :file:`component.py` file can be
used as a top-level component by simply importing it into a :file:`component.py`
namespace:

.. code-block:: python

    from batou.component.supervisor import Supervisor


Sub-components
~~~~~~~~~~~~~~

Components can also be assigned as sub-components by using the composition
operator ``+=``. This works recursively: sub-components can configure further
sub-components.

They do not differ on the API level and any component class can be used both as
a top-level or sub-component.

In comparison to top-level components, sub-level components have the following
properties:

* their order is determined by the order that the "+=" operator is used in their parent component
* they receive arguments from the class' constructor and have a special ``namevar`` attribute
  which corresponds to the first positional (non-keyword) argument
* they can not be assigned or customized on a per-environment level directly
* they can be defined in any Python-importable module or package and can thus be distributed as   
  separate packages for independent re-use.

Component.configure()
---------------------

The :func:`configure` method is used to determine target system state by
computing attributes and building a tree of components. It is called after all
overrides (environment and secrets) have been applied.

:func:`configure` may be called multiple times during the configuration
phase while batou tries to work out a correct dependency graph between top-level components.

Whenever it is called a new instance of the component will have been generated
and any previous computation will be lost. It is advisable that your
:func:`configure` code is fast.

Also, :func:`configure` will be run in isolation: it is not allowed to access or
modify state of the system as it might (and will) not even be running on the
target.

.. note:: To avoid unnecessary updates you need to ensure that data does
   not randomly change between runs: 

   * avoid unseeded function calls to the :mod:`random` module
   * make sure that any :class:`dict` or :class:`set` is used with a
     stable order

The composition operator ``+=``
-------------------------------

Using sub-components happens in two steps:

1. instantiating a Component
2. assigning an instance using the ``+=`` operator.

A typical example using the :class:`File` component:

.. code-block:: python

   def configure(self):
       file = File('foo/bar', content='asdf')
       self += file

The sub-component's :func:`configure` method will be called when the
composition happens, not when the component is instantiated.

This also gives you flexibility to initialize components and then pass them around or stick them on another component.

A somewhat silly example:

.. code-block:: python

   def configure(self):
       file = File('foo/bar', content='asdf')
       file2 = File('foo', ensure='directory')
       file += file2
       self += file

Built-in attributes
-------------------

In addition to the attributes defined by your component, the base class provides
the following attributes:

..
    XXX The following should be extracted into a structured API reference.

.. py:class:: Component

   .. py:attribute:: host

       The host this component is being deployed on.

   .. py:attribute:: environment

       The environment this component is being deployed in.

   .. py:attribute:: root

       The root component this component belongs to.

   .. py:attribute:: workdir

       The current work directory for this component. 

       This can be changed for sub-components to allow isolation complex subcomponents' workin directories.

Those attributes provide the following data:

.. py:class:: Host

    Represents the host that a component is being deployed to.

    .. py:attribute:: name

        The short name of the host, e.g. ``prod00``.

    .. py:attribute:: fqdn

        The fully qualified domain name of the host, e.g. ``prod00.gocept.net``.

.. py:class:: Environment

    Represents the environment that a component is being deployed in.

    .. py:attribute:: name

        The name of the environment, derived from the configuration filename.
        E.g. ``production`` if your config file is named :file:`production.cfg`.

    .. py:attribute:: service_user

        The name of the user that is being deployed to.

    .. py:attribute:: host_domain

        The domain suffix appended to the host names in the environment.

        E.g. ``gocept.net``.

    .. py:attribute:: platform

        The platform identifier that we're deploying to. Defaults to the
        :attr:`host_domain`.

    .. py:attribute:: timeout

        The timeout applied to various operations when deploying to this
        environment.

        This is used by batou internally (e.g. for SSH connections, buildout,
        etc.) and should be used as an indicator for any long-running actions in
        custom code.

    .. py:attribute:: base_dir

        The directory that the batou project lives in, i.e. where the
        :file:`batou` script is.

    .. py:attribute:: workdir_base

        The directory where component-specific work directories will be created.

        Defaults to :file:`$base_dir/work`.


.. py:class:: RootComponent

    The :class:`RootComponent` is a thin wrapper around the root component
    being deployed.

    .. py:attribute:: name

        The name of the top-level component as used in the environment configuration.

    .. py:attribute:: features

        The features activated for this top-level component on this host in this environment.

    .. py:attribute:: component

        The actual component object that is the current top-level component.

    .. py:attribute:: defdir

        The definition directory of the current top-level component.

        This is the directory where the corresponding :file:`component.py`` lives and used as the working directory during the configuration phase.

    .. py:attribute:: workdir

        The working directory of the current top-level component.


Example:

.. code-block:: python

    def configure(self):
        self.bind_address = self.host.fqdn
        self.tags = open(self.root.defdir+'/tags').readlines()
        # Same as:
        self.tags = open('tags').readlines()

In this example it's OK to just open a file and interact with the system: we're
explicitly working in the definition directory.
