Skip to content

Adding plugins to trestle¤

Trestle provides a mechanism for 3rd party providers to extend its command interface via a plugin architecture. All trestle plugins that conforms to this specification will be automatically discovered by trestle if installed, and their command(s) will be added to trestle sub-commands list. Below we describe this plugin mechanism with the help of an example plugin compliance-trestle-fedramp that we created as a separate python project that can be installed via pip.

Create the trestle plugin proejct¤

A separate plugin project needs to be created that will conatin the code for plugin and its commands. This plugin can be given any name and should be available for installation via pip. For example, we created a plugin project called compliance-trestle-fedramp which can be installed as pip install compliance-trestle-fedramp. The project name doesn't need to start with compliance-trestle.

Project Organization¤

The plugin project should be organized as shown below.

compliance-trestle-fedramp
├── trestle_fedramp
│   ├── __init__.py
│   ├── commands
|   |   ├── __init__.py
|   |   ├── validate.py
|   ├── jinja_ext
|   |   ├── __init__.py
|   |   ├── filters.py
│   ├── <other source files or folder>
├── <other files or folder>

Trestle uses a naming convention to discover the top-level module of the plugin projects. It expects the top-level module to be named trestle_{plugin_name}. This covention must be followed by plugins to be discoverable by trestle. In the above example, the top-level module is named as trestle_fedramp so that it can be autmatically discovered by trestle. All the python source files should be created inside this module (folder).

To add commands to the CLI interface, the top-level module should contain a commands directory where all the plugin command files should be stored. Each command should have its own python file. In the above example, validate.py file contains one command for this plugin. Other python files or folders should be created in the top-level module folder, outside the commands folder. This helps in keeping the commands separate and in their discovery by trestle.

To add jinja extensions available during trestle author jinja, the top-level module should contain a jinja_ext directory where all extension files should be stored. Each extension should have its own python file. In the above example, filters.py file contains a single extension class, which may define many filters or custom tags. Supporting code should be created in the top-level module folder, outside the jinja_ext folder. This helps in keeping the extensions separate and in their discovery by trestle.

Command Creation¤

The plugin command should be created as shown in the below code snippet.

from trestle.core.commands.command_docs import CommandBase
from trestle.core.commands.command_docs import CommandPlusDocs

class ValidateCmd(CommandBase):
    """Validate contents of an OSCAL model based on FedRAMP specifications."""

    name = 'fedramp-validate'

    def _init_arguments(self) -> None:
        logger.debug('Init arguments')
        self.add_argument('-f', '--file', help='OSCAL file to validate.', type=str, required=True)

        self.add_argument(
            '-o', '--output-dir', help='Output directory for validation results.', type=str, required=True
        )

    def _run(self, args: argparse.Namespace) -> int:
        model_file = pathlib.Path(args.file).resolve()

        output_dir = pathlib.Path(args.output_dir).resolve()
        ...
        ...
        return 0 if valid else 1

There should be a command class for example, ValidateCmd which should either extend from CommandBase or CommandPlusDocs. Trestle uses ilcli package to create commands. CommandBase extends from ilcli.Command that initializes the command including help messages and input parameters. CommandPlusDocs in turn extends from CommandBase. The difference between CommandBase and CommandPLusDocs is that CommandBase does not require command line parameter trestle-root to be set or the current directory to be a valid trestle root, whereas CommandPlusDocs requires a valid trestle-root and checks for it. Hence, depending upon the requirement of the plugin command it can extend from either of these classes.

The docstring of the command class is used as the help message for the command. Input arguments to the command should be specified in _init_arguments method as shown above. The acutal code of the command is contained in_run method. This method is called by ilcli when the command is excuted on the commandline. The command arguments can be accessed from the args input parameter as shown above. The command should return 0 in case of successful execution, or any number greater than 0 in case of failure. Please see trestle.core.commands.common.return_codes.CmdReturnCodes class for specific return codes in case of failure.

The command class should conatin the name field which should be set to the desired command name. In the above example, the command is called fedramp-validate. This name is automatically added to the list of sub-command names of trestle during the plugin discovery process. This command can then be invoked as trestle {name} from the commandline e.g., trestle fedramp-validate. Any input parameters to the command can also be passed on the commandline after the command name.

Jinja Extension Creation¤

The plugin extension should be created as shown in the below code snippet.

from jinja2 import Environment
from trestle.core.jinja.base import TrestleJinjaExtension

def _mark_tktk(value: str) -> str:
    """Mark a value with TKTK to easily find it for future revision."""
    return f'TKTK {value} TKTK'


class Filters(TrestleJinjaExtension):
    def __init__(self, environment: Environment) -> None:
        super(Filters, self).__init__(environment)

        environment.filters['tktk'] = _mark_tktk

There should be an extension class, for example Filters that must extend from TrestleJinjaExtension or jinja2.ext.Extention. The __init__ method must call init for its superclass. Beyond that, any behavior for standard jinja2 custom extensions is supported.

Examples for implementing extensions can be found at trestle/core/jinja/tags.py and trestle/core/jinja/filters.py