Last updated Jan 2, 2025

Write your own context plugin

Sovereign provides a few basic context plugins, however you may need something more custom for your own environment. Maybe you need to authenticate in a particular way, or you need to use a specific protocol to obtain data from somewhere.

A context plugin can be added to your deployment by writing a Python module and installing it as an extension point.

Tutorial: DNS service-discovery plugin

In this tutorial we'll go through the steps required to create a plugin that will resolve SRV records using DNS, resulting in endpoints that can be used for example in a clusters template.

Create a module

First, create an empty Python module.

Choose your preferred package management

Create a new directory my_context_plugin (or any name you want) and then create the following file/folder structure

1
2
├── my_context_plugin
│  └── __init__.py
└── setup.py

Create a subclass

Sovereign provides a CustomLoader class that can be subclassed

1
2
from typing import Any
from dns.resolver import Resolver
from sovereign.dynamic_config.loaders import CustomLoader


class ServiceDiscovery(CustomLoader):
    # You can specify the default way to deserialize
    # or none if you're handing back python objects
    default_deser = "none"

    def __init__(self):
        # If the init needs to be overwritten, it should start as follows
        super(ServiceDiscovery, self).__init__()
        # Start your customizations here
        self.resolver = Resolver()

    # The load method is the only method that must be implemented. 
    # It returns any type, and you must handle this within the templates where it is used
    def load(self, path: str) -> Any:
        # Any logic you want can be implemented here.
        # You can include logging, caching, or whatever else suits your scenario.
        ret = list()
        for srv in path.split(','):
            query = self.resolver.resolve(srv, rdtype='SRV')
            service = {
                'name': srv,
                'hosts': []
            }
            for answer in query.response.answer:
                *_, priority, weight, port, target = answer.to_text().split()
                service['hosts'].append({
                    'address': target,
                    'port': port,
                    'weight': weight,
                    'priority': priority,
                })
            ret.append(service)
        return ret

Add the entry point to your package

The following script adds your Python module to the list of available context plugins, which Sovereign checks at runtime:

1
2
from setuptools import setup, find_packages

setup(
    name='my_context_plugin',
    packages=find_packages(),
    entry_points={
        "sovereign.loaders": [
            "service_discovery = my_context_plugin.service_discovery:ServiceDiscovery",
        ]
    }
)

This will install the above Python module into an entry point named sovereign.loaders, with a name of service_discovery

Install the module alongside Sovereign

You'll need to install the package on the same machine or container where you've installed sovereign, otherwise it won't be able to access the entry point when it starts.

Run python setup.py install and you should see output similar to the following:

1
2
$ python setup.py install

TODO: example output

Adding the plugin to your template context

Using the name specified in the entry points, you can refer to your new plugin in template context:

1
2
template_context:
  context:
    srv_records:
      protocol: 'service_discovery'
      serialization: 'none'
      path: '_imaps._tcp.gmail.com.'  # Real example

The above example config should result in something like the following being made available via context:

1
2
{
  "name": "_imaps._tcp.gmail.com.", 
  "hosts": [{
    "address": "imap.gmail.com.",
    "port": "993", 
    "weight": "0", 
    "priority": "5"
  }]
}

We can then use this data in templates, like in this example clusters template:

1
2
resources:
{% for service in srv_records %}
- name: {{ service['name'] }}
  connect_timeout: 0.25s
  type: STRICT_DNS
  load_assignment:
    cluster_name: {{ service['name'] }}
    endpoints:
      {% for host in service['hosts'] %}
      - priority: {{ host['priority'] }}
        load_balancing_weight: {{ host['weight'] }}
        lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: {{ host['address'] }}
                  port_value: {{ host['port'] }}
      {% endfor %}
{% endfor %}

TODO: verification / run the server and look at the clusters

Recap

  • We created a Python module, containing an object that inherits from Source from the Sovereign library
  • We added code that does a DNS lookup on SRV records using a 3rd party library and parses the output into instances
  • We made a setup script, and installed it to the same machine which has Sovereign installed
  • We added the service_discovery source to the list of sources, with an example list of SRV records to lookup
  • We verified ... TODO

Rate this page: