Plugin Conventions

Configuration

Validation

It is up to the plugin writer to determine what configuration values are necessary for the plugin to function.

Pulp performs no validation on the configuration for a plugin. The validate_config method in each plugin subclass is used to verify the user-entered values for a repository. This is called when the plugin is first added to the repository and on all subsequent configuration changes. The configuration is sent to the Pulp server as a JSON document through its REST APIs and will be deserialized before being passed to the plugin.

This call must ensure the configuration to be used when running the plugin will be valid for the repository. If this call indicates an invalid configuration, the plugin will not be added to the repository (for the add call) or the configuration changes will not be saved to the database (for the update configuration call).

The docstring for the method describes the format of the returned value.

Format

Each call into a plugin passes the configuration the call should use for its execution. The configuration is contained in a pulp.plugins.config.PluginCallConfiguration instance. This object is a wrapper on top of the three different locations a configuration value can come from:

  • Overrides - Most calls allow the user to specify configuration values as a parameter when they are invoked. These values are made available to the plugin for the operation’s execution, however they are not saved in the server.

  • Repository-level - When an importer or distributor is attached to a repository, the Pulp server saves the configuration for that plugin with the repository. These values are only used for operations on that repository. For example, if an importer is configured to synchronize from an external feed, the URL of that feed would be stored on a per repository basis.

  • Plugin-level - Each importer and distributor may be paired with a static configuration file on disk. These are JSON files that are loaded by the Pulp server when the plugins are initialized. Configuration values in this location are available to all instances of the importer/distributor.

The PluginCallConfiguration defines a method called get(str) that will retrieve the value for the given key. This call will check the three configuration locations in the order listed above. The first value found for the key is returned, removing the need for the plugin writer to apply this prioritization on their own.

Life Cycle Methods

Both types of plugins define a number of methods related to the lifecycle of the plugin on a particular repository. These methods are called when the importer/distributor is added to or removed from a repository. Examples include importer_added(repo, config and distributor_removed(repo, config).

In many cases, these methods can be ignored. The default implementation will not raise an error. Their usage is typically to perform any initialization in the plugin’s working directory that is necessary before the first plugin operation is invoked.

Metadata Method

Both types of plugins require a metadata method to be overridden from the base class. The metadata() method is responsible for providing Pulp with information on how the plugin works. The following information must be returned from the metadata call. The docstring for the method describes the format of the returned value.

  • ID - Unique ID that is used to refer to this type of plugin. This must be unique for all plugins installed in the Pulp server.

  • Display Name - User-friendly description of what the plugin does.

  • Supported Types - List of IDs for all content types that may be handled by the plugin. If there is no type definition found for any of the IDs referenced here, the server will fail to start.

Conduits

A conduit is an object passed to a plugin when an method is executed. The conduit is used to access functionality in the Pulp server itself. Each method is given a custom conduit type depending on the needs of the method being invoked. Consult the docstrings for each method in the plugin base class for more information on the conduit class that will be used.

Warning

Plugins should not retain any state between calls. Conduits are typically scoped to the repository being used; reusing old conduit instances can lead to data corruption.

Scratchpads

A scratchpad is used to store information across multiple operations run by the plugin. Each importer and distributor on a repository is given its own scratchpad. A plugin may only edit its own scratchpad for the repository being acted on.

The scratchpad is retrieved through the conduit’s get_scratchpad() method and updated with set_scratchpad(object). The scratchpad is stored in the database, therefore its value must be able to be pickled. It is recommended to use either a single string or a dictionary of string pairs.

Additionally, there exists a scratchpad at the repository level, accessible to all importers and distributors on the repository. This can be used to share information between different plugins. It is highly recommended to avoid using this wherever possible so as to not tightly couple plugins together. The repository scratchpad can be accessed using get_repo_scratchpad() and set_repo_scratchpad(object) and carries the same pickle restriction as described above.

Working Directories

Each plugin on a repository is given a unique location on disk. This directory should be used for storing any temporary files that need to be created when the plugin is used. These directories are automatically deleted when the repository is deleted. The location of the working directory can be found in the repository instance (pulp.plugins.model.Repository) passed into each plugin call.

Installation

There are two ways to install a plugin.

Entry Points

The plugin may define a method that will serve as its entry point. The method must accept zero arguments and return a tuple of the following:

  • Class of the plugin itself. This must be a subclass of either pulp.plugins.importer.Importer or pulp.plugins.distributor.Distributor.

  • Plugin-level configuration to use for that plugin. See Configuration for more information on the scope of these configuration values.

A sample is as follows:

def entry_point():
    return DemoImporter, {}

class DemoImporter(Importer):
    ...

Python entry points are advertised within the package’s setup.py file. Multiple entry points may be advertised by the same setup file. A sample from the Puppet plugins is below:

from setuptools import setup, find_packages

setup(
    name='pulp_puppet_plugins',
    version='2.0.0',
    license='GPLv2+',
    packages=find_packages(exclude=['test', 'test.*']),
    author='Pulp Team',
    author_email='pulp-list@redhat.com',
    entry_points = {
        'pulp.distributors': [
            'distributor = pulp_puppet.plugins.distributors.distributor:entry_point',
        ],
        'pulp.importers': [
            'importer = pulp_puppet.plugins.importers.importer:entry_point',
        ],
    }
)

Directory Loading

For one-off testing purposes, the code for a plugin can be placed directly in a specific directory without the need to install to site-packages. The entry point method described above is the preferred way to integrate new plugins:

  • Create directory in /usr/lib/pulp/plugins/ under the appropriate plugin type.

  • Add __init__.py to created directory.

  • Add importer.py or distributor.py as appropriate.

  • In the above module, add the classes that subclass Importer or Distributor as appropriate.

Additionally, for directory loaded plugins, Pulp will automatically load any configuration files found in the plugin’s directory. The configuration within will be made available to each call as described in Configuration. The only restriction on the name of the configuration file is that it end with .conf and be placed in the directory created in the first step above.