Skip to content

Nebari Extension System

Updated 2 min read

This guide is to help developers extend and modify the behavior of Nebari. We leverage the plugin system pluggy to enable easy extensibility. Currently, Nebari supports:

  • overriding a given stages in a deployment
  • adding additional stages before, after and between existing stages
  • arbitrary subcommands

We maintain an examples repository which contains up to date usable examples.

Registering a plugin should be easy for the end user. Plugins are either installed into your existing environment e.g.

Terminal window
pip install my-nebari-plugin

Alternatively if you only want to temporarily add an extension.

Terminal window
nebari --import-plugin path/to/plugin.py <command> ...
nebari --import-plugin import.module.plugin.path <command> ...

The most important step to developing a plugin is ensuring that the setuptools entrypoint is set.

pyproject.toml
...
[project.entry-points.nebari]
my-subcommand = "path.to.subcommand.module"
...

Adding this one line to your pyproject.toml will ensure that upon installation of the package the Nebari plugins are registered.

Nebari exposes a hook nebari_subcommand which exposes the typer CLI instance. This allows the developer to attach any arbitrary number of subcommands.

from nebari.hookspecs import hookimpl
import typer
@hookimpl
def nebari_subcommand(cli):
@cli.command()
def hello(
name: str = typer.Option(
"Nebari", help="Who to say hello to"
)
):
print(f"Hello {name}")

There is a dedicated working example in nebari-plugin-examples.

Nebari exposes a hook nebari_stage which uses the NebariStage class. NebriStage exposes render, deploy, destroy as arbitrary functions calls. See below for a complete example.

Nebari decides the order of stages based on two things the name: str attribute and priority: int attribute. The rules are as follows:

  • stages are ordered by priority
  • stages which have the same name the one with highest priority number is chosen
import contextlib
import os
from typing import Dict, Any
from nebari.hookspecs import hookimpl, NebariStage
class HelloWorldStage(NebariStage):
name = "hello_world"
priority = 100
def render(self):
return {
"hello_world.txt": "File that says hello world"
}
@contextlib.contextmanager
def deploy(self, stage_outputs: Dict[str, Dict[str, Any]]):
print("I ran deploy")
# set environment variables for stages that run after
os.environ['HELLO'] = 'WORLD'
# set output state for future stages to use
stage_outputs[self.name] = {'hello': 'world'}
yield
# cleanup after deployment (rarely needed)
os.environ.pop('HELLO')
def check(self, stage_outputs: Dict[str, Dict[str, Any]]):
if 'HELLO' not in os.environ:
raise ValueError('stage did not deploy successfully since HELLO environment variable not set')
@contextlib.contextmanager
def destroy(self, stage_outputs: Dict[str, Dict[str, Any]], status: Dict[str, bool]):
print('faking to destroy things for hello world stage')
yield
@hookimpl
def nebari_stage():
return [HelloWorldStage]

There is a dedicated working example in nebari-plugin-examples.