Building your own Plugin
Plugins extend the CMDBSyncer with new import/export sources, CLI commands, admin views, and REST endpoints. Every built-in integration (Checkmk, Netbox, Ansible, …) is itself a plugin — so you can use them as reference.
Note
Only import Syncer functions from syncerapi.v1. Importing from internal application modules directly will break when the Syncer is updated.
Plugin Structure
A plugin lives in its own subfolder under application/plugins/:
application/plugins/myplugin/
├── __init__.py # CLI commands and cronjob registration
├── plugin.json # Plugin metadata and account field presets
├── admin_views.py # Flask-Admin view registration (optional)
├── models.py # MongoEngine models (optional)
├── views.py # Flask-Admin view classes (optional)
└── rest_api/
└── myplugin.py # REST API namespace (optional)
plugin.json
The plugin.json is read by the Syncer on startup to register the plugin and pre-fill account fields in the UI.
{
"version": "1.0.0",
"author": "Your Name",
"name": "My Plugin",
"description": "Short description",
"ident": "myplugin",
"account_presets": {
"address": "https://api.example.com"
},
"account_custom_field_presets": {
"verify_cert": "True",
"some_option": ""
},
"enabled": true
}
| Field | Description |
|---|---|
ident |
Unique identifier used internally to match accounts to this plugin |
account_presets |
Default values for the standard account fields (address, etc.) |
account_custom_field_presets |
Pre-filled keys for the account's custom fields section |
enabled |
Set to false to disable the plugin without removing it |
__init__.py — CLI Commands and Cronjobs
The __init__.py is the entry point. It defines CLI commands (via Click) and registers cronjobs.
A minimal example:
import click
from syncerapi.v1 import Host, register_cronjob
# Define a click group for your plugin
@click.group(name='myplugin')
def cli_myplugin():
"""Commands for My Plugin"""
# Register a CLI command
@cli_myplugin.command('import_hosts')
@click.argument('account')
def import_hosts(account):
"""Import hosts from My Plugin"""
# your import logic here
pass
# Register the same function as a cronjob
register_cronjob('My Plugin: Import Hosts', import_hosts)
The CLI group name (myplugin) becomes the subcommand:
./cmdbsyncer myplugin import_hosts MYACCOUNT
The Syncer automatically discovers the CLI group from __init__.py and adds it to the main command.
Note
Register cronjobs only for functions that accept an account argument as the first parameter, or no arguments at all — that is what the cronjob scheduler expects.
admin_views.py — Admin UI
To add views to the web UI, create an admin_views.py with a register_admin_views(admin) function.
The Syncer calls this function automatically on startup if it exists.
from .models import MyPluginRule
from .views import MyPluginRuleView
def register_admin_views(admin):
"""Register Flask-Admin views for My Plugin."""
admin.add_sub_category(name="My Plugin", parent_name="Modules")
admin.add_view(
MyPluginRuleView(
MyPluginRule,
name="My Plugin Rules",
category="My Plugin",
menu_icon_type='fa',
menu_icon_value='fa-cogs',
)
)
This creates a sub-menu entry under Modules → My Plugin in the navigation.
REST API Endpoints
To expose REST API endpoints, create a rest_api/ subfolder with a Flask-RESTX Namespace and register it in __init__.py.
rest_api/myplugin.py:
from flask_restx import Namespace, Resource
from syncerapi.v1.rest import require_token
API = Namespace('myplugin')
@API.route('/')
class MyPluginApi(Resource):
@require_token
def get(self):
"""Return data from My Plugin"""
return {'status': 'ok'}
Register in __init__.py:
from syncerapi.v1.rest import API
from .rest_api.myplugin import API as myplugin_api
API.add_namespace(myplugin_api, path='/myplugin')
The endpoint is then available at:
GET /api/v1/myplugin/
Use @require_token on every endpoint to enforce authentication. See the API documentation for details on authentication.
Example: REST Import Plugin
The following is a complete, realistic example of a plugin that fetches hosts from a JSON REST API and imports them into the Syncer.
It uses the Plugin base class for HTTP requests, account config handling, and automatic logging.
File layout
application/plugins/myplugin/
├── __init__.py
├── plugin.json
└── syncer.py
plugin.json
{
"version": "1.0.0",
"author": "Your Name",
"name": "My Plugin",
"description": "Import hosts from My REST API",
"ident": "myplugin",
"account_presets": {
"address": "https://api.example.com"
},
"account_custom_field_presets": {
"verify_cert": "True"
},
"enabled": true
}
syncer.py
from syncerapi.v1 import Host, cc
from syncerapi.v1.core import Plugin
class MyPluginImport(Plugin):
"""
Import hosts from My REST API
"""
name = "My Plugin: Import Hosts"
source = "myplugin_import"
def __init__(self, account):
super().__init__(account)
self.base_url = self.config['address'].rstrip('/')
self.headers = {
'Accept': 'application/json',
'Authorization': f"Bearer {self.config['password']}",
}
def run(self):
"""Fetch all hosts from the API and import them into the Syncer."""
url = f"{self.base_url}/api/v1/hosts"
print(f"{cc.OKBLUE} -- {cc.ENDC} Fetching hosts from {url}")
response = self.inner_request(method="GET", url=url, headers=self.headers)
hosts = response.json().get('hosts', [])
self.log_details.append(('hosts_found', len(hosts)))
imported = 0
for entry in hosts:
hostname = entry.get('name')
if not hostname:
continue
db_host = Host.get_host(hostname)
if not db_host:
continue
do_save = db_host.set_account(account_dict=self.config)
if not do_save:
continue
db_host.update_host({
'os': entry.get('os', ''),
'location': entry.get('location', ''),
'status': entry.get('status', ''),
})
db_host.save()
imported += 1
print(f"{cc.OKGREEN} +{cc.ENDC} {hostname}")
self.log_details.append(('hosts_imported', imported))
__init__.py
import click
from application.helpers.cron import register_cronjob
@click.group(name='myplugin')
def cli_myplugin():
"""My Plugin Commands"""
def _run_import(account, debug=False):
from .syncer import MyPluginImport
syncer = MyPluginImport(account)
syncer.debug = debug
syncer.run()
@cli_myplugin.command('import_hosts')
@click.argument('account')
@click.option('--debug', is_flag=True)
def import_hosts(account, debug):
"""
Import hosts from My Plugin
### Example
_./cmdbsyncer myplugin import_hosts MYACCOUNT_
"""
_run_import(account, debug)
register_cronjob('My Plugin: Import Hosts', _run_import)
How it works
Plugin.__init__loads the account config from the database intoself.configand setsself.verifyfrom the account'sverify_certfield.inner_requesthandles retries, SSL verification, dry-run mode, and debug logging automatically.set_accountmarks the host as belonging to this import source and returnsFalseif the host is already owned by a different account — in that case the host is skipped.update_hostreplaces all labels;update_inventorystores additional data under a namespaced key (myplugin__serial,myplugin__model, …).log_detailsentries are written to the Syncer log automatically when the plugin finishes.
For the full API reference, see Plugin API.