holo-plugin-interface(7)

NAME
holo-plugin-interface - API specification for Holo plugins

DESCRIPTION
Holo can leverage plugins to provision previously unknown entity types. For example, given a hypothetical "FooSQL" database, someone could implement a plugin for Holo that provisions FooSQL databases or database users. This document describes the interface that Holo uses to find, invoke, and communicate with its plugins.

This interface is deliberately designed around classic files and text streams, so that it can easily be implemented even by shell scripts without needing to resort to complex parser libraries.

This document describes version 3 of the Holo plugin interface.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 <https://tools.ietf.org/html/rfc2119>.

  Plugin discovery
Each plugin MUST have an ID following the format "[a-z0-9][a-z0-9-]*". When choosing the plugin ID, redundant verbs like "manage" or "provision" SHOULD be avoided. A good plugin ID is just the name of the software being configured by the plugin. For example, an appropriate plugin ID for the aforementioned FooSQL plugin would be "foosql". The things provisioned MAY be referenced in plural form if disambiguation is required. For example, if FooSQL is configured by multiple plugins, appropriate plugin IDs could include "foosql-databases" and "foosql-users".

Plugins are not discovered automatically. They MUST be referenced in /etc/holorc (see holorc(5)) by adding the line:

    plugin $PLUGIN_ID

It is RECOMMENDED that plugins install a holorc(5) snippet to achieve this:

    $ cat /etc/holorc.d/50-foosql
    # This file is part of the holo-foosql package.
    plugin foosql

Old versions of Holo (prior to 1.2) required that the /etc/holorc be modified via holo-files(8) through the use of post-install/upgrade/remove scripts. This is no longer required if holorc snippets are used.

  Runtime environment
Plugins MUST recognize the environment variable $HOLO_ROOT_DIR: If this variable exists, plugins SHALL assume that Holo is running in test mode. The variable holds the path to a directory which contains a test scenario resembling a normal root partition (at least the parts needed for the test scenario).

In test mode, plugins SHOULD NOT talk to system-level daemons or write files outside the $HOLO_ROOT_DIR. Appropriate mock implementations SHALL be used instead. Modifying files below $HOLO_ROOT_DIR is allowed.

HOLO_RESOURCE_DIR, HOLO_STATE_DIR, HOLO_CACHE_DIR

Plugins SHALL locate and store their data in the directories named by the following environment variables:

HOLO_RESOURCE_DIR (default: `/usr/share/holo/$PLUGIN_ID`)
    Where plugins can find their resources. Holo will refuse to operate if the resource directory does not exist, thus plugins SHOULD create the default path at installation time.

HOLO_STATE_DIR (default: `/var/lib/holo/$PLUGIN_ID`)
    Where plugins can store persistent state between runs of Holo. If the state directory is missing, Holo will create it before calling the plugin executable. However, plugins are encouraged to create the state directory at their installation time if they are going to need it.

HOLO_CACHE_DIR (default: below `/tmp`)
    Where plugins may store temporary data, such as results from an initial scan operation. Holo will create this directory when it starts up, and clean it up when it exits.

Future versions of Holo may start to choose these paths differently (or allow the user to do so), but the default values are stable and can safely be communicated to users in documentation. Because these paths can be chosen by Holo, plugins SHALL always use the paths in the environment variables rather than trying to compute them by themselves. The paths are always given as absolute paths, and are located below $HOLO_ROOT_DIR during test cases.

  The "info" operation
The plugin binary is executed one or multiple times when Holo is run. The first invocation is always with the single argument "info":

    PLUGIN_BINARY=/usr/lib/holo/holo-$PLUGIN_ID
    $PLUGIN_BINARY info

The plugin shall then report metadata about itself on stdout, as key-value pairs in the form "key=value", with a newline after each value. The following keys are recognized by Holo:

"MIN_API_VERSION", "MAX_API_VERSION" (required)
    These two keys describe an interval of versions of this plugin interface that the plugin is compatible with. Versions are single, positive integers. For example:

        MIN_API_VERSION=3
        MAX_API_VERSION=5

    Both values may be identical, of course. If they're not, then Holo will attempt to choose a plugin interface version that works both for it and the plugin, and announce that version to the plugin in subsequent operations through the $HOLO_API_VERSION environment variable. The plugin SHALL then conform to this version of the plugin interface.

All other keys are ignored.

  The "scan" operation
After "info" always comes another invocation with the single argument "scan":

    $PLUGIN_BINARY scan

The plugin shall then scan its $HOLO_RESOURCE_DIR for entities that it can provision. Any errors encountered shall be reported on stderr. If any fatal errors are encountered, the plugin shall exit with non-zero exit code.

At the end of scanning, the plugin shall provide on stdout a report for each of the entities found, in the following form (this example being from the "files" plugin from core Holo):

    ENTITY: file:/etc/locale.gen
    store at: /var/lib/holo/base/etc/locale.gen
    SOURCE: /usr/share/holo/files/00-base/etc/locale.gen
    apply: /usr/share/holo/files/00-base/etc/locale.gen

Each line has the form "key: value". Most lines are informational content that is not processed further by Holo (except for pretty-printing), and can be used to convey any sort of useful information about the entity to the user. However, keys with all capital letters are reserved for special semantics. Currently, the following special keys are known:

"ENTITY"
    The "ENTITY" key starts a new entity. The value after the colon is the entity ID as chosen by the plugin. The recommended format for entity IDs is "type:identifier", with the type in singular form. For example, the hypothetical "foosql" plugin could report entities like "foosql-db:production" or "foosql-user:sarah".

    Entity IDs MUST NOT look like filesystem paths, since Holo's interface uses entity IDs and paths to resource files in the same place.

"SOURCE"
    The "SOURCE" key names a resource file (below $HOLO_RESOURCE_DIR) from which the entity in question has been read. If multiple such files exist, multiple "SOURCE" lines can be printed.

    This link is only used internally to resolve resource file arguments into the entities defined by them. It is therefore considered good practice to list the source files a second time as informational text, with appropriate lower-case keys. The "users-groups" plugin demonstrates this practice:

        ENTITY: group:sudo
        SOURCE: /usr/share/holo/users-groups/00-base.toml
        found in: /usr/share/holo/users-groups/00-base.toml
        with: type: system

"ACTION"
    Lines of the form "ACTION: verb (reason)" are used when applying the entity will do something else than provisioning it. The line shall contain a verb describing the action taken, and a reason for doing so. For example, the "files" plugin uses the action verb "Scrubbing" to signal that a deleted configuration file is being cleaned up after.

        ENTITY: file:/etc/targetfile-deleted.conf
        ACTION: Scrubbing (target was deleted)
        delete: /var/lib/holo/files/base/etc/targetfile-deleted.conf

    When not given, Holo will display a generic action verb like "Working on" or "Applying".

The report for an entity ends at the next "ENTITY: ID" line, or when EOF is encountered.

If scanning for entities is expensive, plugins should cache results of their scanning in $HOLO_CACHE_DIR (as described above).

  The "apply" operation
If the user requests that one or multiple entities be provisioned (with the "holo apply" command), then for each of the selected entities, the corresponding plugin will be called like this:

    $PLUGIN_BINARY apply $ENTITY_ID

During this operation, the plugin can reuse results from the previously conducted scanning operation if they have been cached in $HOLO_CACHE_DIR. Informational output shall be printed on stdout, errors and warnings shall be printed on stderr. This output will be passed on to the user directly. If an error occurred during provisioning, the plugin shall exit with non-zero exit code.

During this operation, the plugin process's file descriptor no. 3 is opened by Holo, and the plugin can write the following messages into this FD to invoke special behavior in Holo:

"not changed\n"
    The entity is already in the desired state, so no changes have been made. Holo will format its output accordingly (at the time of this writing, by omitting the entity from the output).

"requires --force to overwrite\n"
    The entity was provisioned by this plugin, but has been changed by a user or external application since then. Holo will output an error message indicating that "--force" is needed to overwrite these manual changes.

"requires --force to restore\n"
    Same as above, but indicates that the entity was not just changed, but deleted by a user or external application.

  The "force-apply" operation
During the "apply" operation, plugins shall refuse to provision entities that appear to have been edited or deleted by the user or an external application. However, when the plugin is called like this:

    $PLUGIN_BINARY force-apply $ENTITY_ID

Then the plugin shall overwrite any external changes to the selected entity and bring it into the desired target state with all means possible. Otherwise, the "force-apply" operation works just like "apply".

  The "diff" operation
If the user requests that a diff be printed for one or multiple entities (with the "holo diff" command), then for each of the selected entities, the corresponding plugin will be called like this:

    $PLUGIN_BINARY diff $ENTITY_ID

If the entity does not have a meaningful diff (e.g. for the "run-scripts" plugin), the plugin shall exit with zero exit code without doing anything.

Otherwise, two NUL-terminated filesystem paths must be printed on file descriptor 3. The first file represents the state of the entity as it was last applied by the plugin (i.e., the state the plugin expects it to currently be in), the second file represents the actual current state of the entity.

The plugin is allowed to return paths that do not exist in the file system, in which case Holo will diff against `/dev/null` instead. `/dev/null` can also be given explicitly instead of a file that is missing. (The first file will be missing when the entity is orphaned, and the second file will be missing when the entity was deleted by the user or an external program.)

For entities that are not backed by a file, the plugin is allowed to make up a useful textual representation of the entity, and write appropriate files to the "HOLO_CACHE_DIR". An example of this is the "holo-users-groups" plugin.

SEE ALSO
holo(8), holo-test(7) (test runner for Holo plugins)

AUTHOR
Stefan Majewsky

Further documentation is available at the project homepage: http://holocm.org

Please report any issues and feature requests at Github: http://github.com/holocm/holo/issues