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.
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.
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
Sovereign provides a CustomLoader
class that can be subclassed
1 2from 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
The following script adds your Python module to the list of available context plugins, which Sovereign checks at runtime:
1 2from 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
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
Using the name specified in the entry points, you can refer to your new plugin in template context:
1 2template_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 2resources: {% 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 %}
service_discovery
source to the list of sources, with an example list of SRV records to lookupRate this page: