#####################
Developing Backends
#####################
Due to the unlimited amount of possible use cases and implementations
available, we want to expose the ability for developers to create custom
:ref:`backends`.
********************
What Is A Backend?
********************
A backend is a Python package that declares an `entry_point
`_
in the ``jiav.backend`` group.
That entry point should reference a subclass of the
``jiav.backend.BaseBackend`` abstract base class. This allows jiav
to discover installed backends and instantiate a selected backend at
run-time.
****************
Implementation
****************
All backends are epxected to share the custom ``jiav.backend.Result``
interface, populate it and return it at the end:
.. list-table::
:widths: 25 25 50
:header-rows: 1
- - Attribute
- Type
- Descrption
- - successful
- boolean
- Represents if the backend executed successfully.
- - errors
- List[str]
- List of strings containing errors.
- - output
- List[str]
- List of strings containing output.
All baceknds inherit from ``BaseBackend`` and must implement the
following methods:
- ``validate_schema`` - validate a schema using `jsonschema
`_.
- ``execute_backend`` - Executes the backend, return a ``Result``.
All backends are expected to subscribe to the global logger
``jiav.logger`` to log information.
*****************************************
Developing A Custom ``example`` Backend
*****************************************
.. note::
Based on a backend `template
`_.
In this section, we will create an ``example`` backend that checks if an
environment variable is set.
#. Your backend must register an entry point in the ``jiav.backend``
group, using the packaging software you use to build your project.
With Poetry, you can define the entry point in your
``pyproject.toml`` file:
.. code::
[tool.poetry.plugins."jiav.backend"]
example = "jiav_example.ExampleBackend"
#. Ensure that your class is discovarable in the `jiav_example`
namespace, populate ``src/jiav_example/__init__.py``:
.. code:: python
from jiav_example.backend import ExampleBackend
__all__ = ["ExampleBackend"]
#. Create a ``src/jiav_example/backend.py`` and import all dependencies
.. code:: python
import os
# Global logger
from jiav import logger
# Required interafces
from jiav.backend import BaseBackend, Result
# Type enforcement
from typing import List
#. Subscribe to the global logger:
.. code:: python
jiav_logger = logger.subscribe_to_logger()
#. In the current implementation of backends, we need to create a mock
step that will be used during JSON schema validation when
constructing an initial object:
.. code:: python
# Mock step that will be used when initializing an initial object
MOCK_STEP = {"example": "example"}
#. define the schema that will be used to validate the step supplied by
the user:
.. code:: python
SCHEMA = {
"type": "object",
"required": ["example"],
"properties": {"example": {"type": "string"}},
"additionalProperties": False,
}
#. Create an initial ``ExampleBackend`` interface which inherits from
``BaseBackend``:
.. code:: python
class ExampleBackend(BaseBackend):
"""
ExampleBackend object
An example backend for jiav
Attributes:
name - Backend name
schema - json_schema to be used to verify that the supplied step is
valid according to the backends's requirements
step - Backend excution instructions
"""
MOCK_STEP = {"example": "example"}
SCHEMA = {
"type": "object",
"required": ["example"],
"properties": {"example": {"type": "string"}},
"additionalProperties": False,
}
def __init__(self) -> None:
self.name = "example"
self.schema = self.SCHEMA
self.step = self.MOCK_STEP
super().__init__(name=self.name, schema=self.schema, step=self.step)
#. Implement `execute_backend` method that will execute the backend and
return a ``Result``:
.. code:: python
# Overrdie method of BaseBackend
def execute_backend(self) -> None:
"""
Execute backend
Returns a namedtuple describing the jiav manifest execution
"""
# Parse required arugments
example: str = self.step["example"]
output: List = []
errors: List = []
successful: bool = False
jiav_logger.debug(f"Example: {example}")
try:
os.environ["JIAV_EXAMPLE"] = example
successful = True
jiav_logger.debug(
f"Environment variable 'JIAV_EXAMPLE' was set to '{example}'"
)
output.append(f"Environment variable 'JIAV_EXAMPLE' was set to '{example}'")
except Exception as e:
jiav_logger.error(e.text)
errors.append(e.text)
self.result = Result(successful, output, errors)
#. Install the package and verify ``example`` backend is registered in
``jiav``:
.. code:: bash
jiav --version
jiav, version 0.3.0
Installed Backends:
- example, version {'version': '0.1.0', 'class': 'jiav_example.ExampleBackend'}
- jira_issue, version {'version': '0.3.0', 'class': 'jiav_jira_issue.JiraIssueBackend'}
- lineinfile, version {'version': '0.3.0', 'class': 'jiav_lineinfile.LineInFileBackend'}
- regexinfile, version {'version': '0.3.0', 'class': 'jiav_regexinfile.RegexInFileBackend'}
#. Create a test manifest ``/tmp/example_manifest.yaml``, and verify
that it is valid
.. code:: shell
cat << EOF > /tmp/example_manifest.yaml
jiav:
verified_status: "Done"
verification_steps:
- name: "Example"
backend: example
example: "example"
EOF
export JIAV_EXAMPLE="/tmp/example_manifest.yaml"
jiav validate-manifest --from-file="/tmp/example_manifest.yaml"