Technical workbook¶
Provider conventions¶
The conventions described in this section must be followed by any provider implementation.
Contract of a Lexicon record¶
A Lexicon record is the internal representation of a DNS entry fetched or pushed to a DNS provider API. These records are JSON objects that must follows the given contract.
Required fields¶
name Clients should provide FQDN. Providers should handle both FQDN and relative names.
ttl Reasonable default is 6 hours since it’s supported by most services. Any service that does not support this must be explicitly mentioned somewhere.
record All provider/API records must be translated to the following format:
Example of a Lexicon record¶
{
'id': string, // optional, provider specified unique id. Clients to treat this as opaque.
'type': string, // upper case, valid record type. eg. A, CNAME, TXT
'name': string, // lowercase, FQDN. eg. test.record.example.com
'ttl': integer, // positive integer, in seconds. eg. 3600
'content': string, //double quoted/escaped values should be unescaped. eg. "\"TXT content\"" should become "TXT content"
'options': {
'mx': { // MX options
'priority': integer
}
}
}
DNS operations¶
A Lexicon provider will have to make operations against a DNS provider API. Here are the 5 possible operations, and the behavior each operation must follow.
authenticate¶
Normal Behavior Execute all required operations to authenticate against the provider API, then retrieves the identifier of the domain and assign it to the
self.domain_id
property of theProvider
instance.Authentication failure In case of authentication failure, the method must raise a
lexicon.exceptions.AuthenticationError
exception and break the flow.
create_record¶
Normal Behavior Create a new DNS record. Return a boolean
True
if successful.If Record Already Exists Do nothing. DO NOT throw exception.
TTL If not specified or set to
0
, use reasonable default.Record Sets If service supports record sets, create new record set or append value to existing record set as required.
list_record¶
Normal Behaviour List all records. If filters are provided, send to the API if possible, else apply filter locally. Return value should be a list of records.
Record Sets Ungroup record sets into individual records. Eg: If a record set contains 3 values, provider ungroup them into 3 different records.
Linked Records For services that support some form of linked record, do not resolve, treat as CNAME.
update_record¶
Normal Behaviour Update a record. Record to be updated can be specified by providing id OR name, type and content. Return a boolean
True
if successful.Record Sets If matched record is part of a record set, only update the record that matches. Update the record set so that records other than the matched one are unmodified.
TTL
If not specified, do not modify ttl.
If set to
0
, reset to reasonable default.
No Match Throw exception?
delete_record¶
Normal Behaviour Remove a record. Record to be deleted can be specified by providing id OR name, type and content. Return a boolean
True
if successful.Record sets Remove only the record that matches all the filters.
If content is not specified, remove the record set.
If length of record set becomes 0 after removing record, remove the record set.
Otherwise, remove only the value that matches and leave other records as-is.
No Match Do nothing. DO NOT throw exception
Code documentation¶
This section describes the public API of Lexicon code (classes, methods, functions) useful to implement a new provider, or to interface Lexicon as a library to another project.
Module lexicon.client¶
Main module of Lexicon. Defines the Client class, that holds all Lexicon logic.
- class lexicon.client.Client(config: ConfigResolver | dict[str, Any] | None = None)¶
This is the Lexicon client, that will execute all the logic.
- execute() bool | list[dict[str, Any]] ¶
(deprecated) Execute provided configuration in class constructor to the DNS records
Module lexicon.interfaces¶
Base provider module for all Lexicon providers
- class lexicon.interfaces.Provider(config: ConfigResolver | dict[str, Any])¶
This is the abstract class for all lexicon Providers. It provides common functionality and ensures that all implemented Providers follow a standard ducktype. All standardized options will be provided here as defaults, but can be overwritten by environmental variables and cli arguments.
Common options are:
action domain type name content ttl priority identifier
The provider_env_cli_options will also contain any Provider specific options:
auth_username auth_token auth_password …
- Parameters:
config – is a ConfigResolver object that contains all the options for this provider, merged from CLI and Env variables.
- abstract authenticate() None ¶
Authenticate against provider, Make any requests required to get the domain’s id for this provider, so it can be used in subsequent calls. Should throw AuthenticationError or requests.HTTPError if authentication fails for any reason, of if the domain does not exist.
- cleanup() None ¶
Clean any relevant resource before this provider instance is closed.
- abstract static configure_parser(parser: ArgumentParser) None ¶
Configure the given parser for the provider needs (e.g. specific CLI flags for auth)
- abstract create_record(rtype: str, name: str, content: str) bool ¶
Create record. If record already exists with the same content, do nothing.
- abstract delete_record(identifier: str | None = None, rtype: str | None = None, name: str | None = None, content: str | None = None) bool ¶
Delete an existing record. If record does not exist, do nothing. If an identifier is specified, use it, otherwise do a lookup using type, name and content.
- abstract static get_nameservers() list[str] | list[Pattern] ¶
Return the list of nameservers for this DNS provider
- abstract list_records(rtype: str | None = None, name: str | None = None, content: str | None = None) list[dict[str, Any]] ¶
List all records. Return an empty list if no records found type, name and content are used to filter records. If possible filter during the query, otherwise filter after response is received.
- abstract update_record(identifier: str | None = None, rtype: str | None = None, name: str | None = None, content: str | None = None) bool ¶
Update a record. Identifier must be specified.
Module lexicon.config¶
Definition of the ConfigResolver to configure Lexicon, and convenient classes to build various configuration sources.
- class lexicon.config.ArgsConfigSource(namespace: Namespace)¶
ConfigSource that resolve configuration against an argparse namespace.
- resolve(config_key: str) str | None ¶
Using the given config_parameter value (in the form of
lexicon:config_key
orlexicon:[provider]:config_key
), try to get the associated value.None
must be returned if no value could be found.Must be implemented by each ConfigSource concrete child class.
- class lexicon.config.ConfigResolver¶
Highly customizable configuration resolver object, that gets configuration parameters from various sources with a precedence order. Sources and their priority are configured by calling the
with*
methods of this object, in the decreasing priority order.A configuration parameter can be retrieved using the
resolve()
method. The configuration parameter key needs to conform to a namespace, whose delimeters is:
. Two namespaces will be used in the context of Lexicon:the parameters relevant for Lexicon itself:
lexicon:global_parameter
the parameters specific to a DNS provider:
lexicon:cloudflare:cloudflare_parameter
Example:
# This will resolve configuration parameters from environment variables, # then from a configuration file named ``/my/path/to/lexicon.yml``. from lexicon.config import ConfigResolver config = ConfigResolver() config.with_env().with_config_file() print(config.resolve('lexicon:delegated')) print(config.resolve('lexicon:cloudflare:auth_token'))
- Config can resolve parameters for Lexicon and providers from:
environment variables
arguments parsed by ArgParse library
YAML configuration files, generic or specific to a provider
any object implementing the underlying
ConfigSource
class
Each parameter will be resolved against each source, and value from the higher priority source is returned. If a parameter could not be resolved by any source, then None will be returned.
- add_config_source(config_source: ConfigSource, position: int | None = None) None ¶
Add a config source to the current ConfigResolver instance. If position is not set, this source will be inserted with the lowest priority.
- resolve(config_key: str) str | None ¶
Resolve the value of the given config parameter key. Key must be correctly scoped for Lexicon, and optionally for the DNS provider for which the parameter is consumed.
- For instance:
config.resolve('lexicon:delegated')
will get the delegated parameter for Lexiconconfig.resolve('lexicon:cloudflare:auth_token')
will get the auth_token parameter consumed by cloudflare DNS provider.
Value is resolved against each configured source, and value from the highest priority source is returned.
None
will be returned if the given config parameter key could not be resolved from any source.
- with_args(argparse_namespace: Namespace) ConfigResolver ¶
Configure current resolver to use a Namespace object given by a ArgParse instance using
arg_parse()
as a source. This method is typically used to allow a ConfigResolver to get parameters from the command line.It is assumed that the argument parser have already checked that provided arguments are valid for Lexicon or the current provider. No further namespace check on parameter keys will be done here. Meaning that if
lexicon:cloudflare:auth_token
is asked, any auth_token present in the given Namespace object will be returned.
- with_config_dir(dir_path: str | PathLike[str]) ConfigResolver ¶
Configure current resolver to use every valid YAML configuration files available in the given directory path. To be taken into account, a configuration file must conform to the following naming convention:
lexicon.yml
for a global Lexicon config file (seewith_config_file
doc)lexicon_[provider].yml
for a DNS provider specific configuration file, with[provider]
equals to the DNS provider name (see
with_provider_config_file
doc)
Example:
$ ls /etc/lexicon lexicon.yml # global Lexicon configuration file lexicon_cloudflare.yml # specific configuration file for clouflare DNS provder
- with_config_file(file_path: str | bytes | PathLike[str] | PathLike[bytes]) ConfigResolver ¶
Configure current resolver to use a YAML configuration file specified on the given path. This file provides configuration parameters for Lexicon and any DNS provider.
Typical format is:
$ cat lexicon.yml # Will define properties 'lexicon:delegated' and 'lexicon:cloudflare:auth_token' delegated: 'onedelegated' cloudflare: auth_token: SECRET_TOKEN
- with_config_source(config_source: ConfigSource) ConfigResolver ¶
Configure current resolver to use the provided ConfigSource instance to be used as a source. See documentation of ConfigSource to see how to implement correctly a ConfigSource.
- with_dict(dict_object: dict[str, Any]) ConfigResolver ¶
Configure current resolver to use the given dict object, scoped to lexicon namespace.
Example of valid dict object for lexicon, defining properties
lexicon:delegated
andlexicon:cloudflare:auth_token
{ "delegated": "onedelegated", "cloudflare": { "auth_token": "SECRET_TOKEN" } }
- with_env() ConfigResolver ¶
Configure current resolver to use available environment variables as a source. Only environment variables starting with ‘LEXICON’ or ‘LEXICON_[PROVIDER]’ will be taken into account.
- with_legacy_dict(legacy_dict_object: dict[str, Any]) ConfigResolver ¶
Configure a source that consumes the dict that where used on Lexicon 2.x
- with_provider_config_file(provider_name: str, file_path: str | bytes | PathLike[str] | PathLike[bytes]) ConfigResolver ¶
Configure current resolver to use a YAML configuration file specified on the given path. This file provides configuration parameters for a DNS provider exclusively.
Typical format is:
$ cat lexicon_cloudflare.yml auth_token: SECRET_TOKEN # Will define property 'lexicon:cloudflare:auth_token' auth_username: USERNAME # Will define property 'lexicon:cloudflare:auth_username'
NB: If
file_path
is not specified,/etc/lexicon/lexicon_[provider].yml
will be taken by default, with[provider]
equals to the given provider_name parameter.
- class lexicon.config.ConfigSource¶
Base class to implement a configuration source for a ConfigResolver. The relevant method to override is
resolve(self, config_parameter)
.- resolve(config_key: str) str | None ¶
Using the given config_parameter value (in the form of
lexicon:config_key
orlexicon:[provider]:config_key
), try to get the associated value.None
must be returned if no value could be found.Must be implemented by each ConfigSource concrete child class.
- class lexicon.config.DictConfigSource(dict_object: dict[str, Any])¶
ConfigSource that resolve configuration against a dict object.
- resolve(config_key: str) str | None ¶
Using the given config_parameter value (in the form of
lexicon:config_key
orlexicon:[provider]:config_key
), try to get the associated value.None
must be returned if no value could be found.Must be implemented by each ConfigSource concrete child class.
- class lexicon.config.EnvironmentConfigSource¶
ConfigSource that resolve configuration against existing environment variables.
- resolve(config_key: str) str | None ¶
Using the given config_parameter value (in the form of
lexicon:config_key
orlexicon:[provider]:config_key
), try to get the associated value.None
must be returned if no value could be found.Must be implemented by each ConfigSource concrete child class.
- class lexicon.config.FileConfigSource(file_path: str | bytes | PathLike[str] | PathLike[bytes])¶
ConfigSource that resolve configuration against a lexicon config file.
- class lexicon.config.LegacyDictConfigSource(dict_object: dict[str, Any])¶
ConfigSource that resolve configuration against a legacy Lexicon 2.x dict object.
- class lexicon.config.ProviderFileConfigSource(provider_name: str, file_path: str | bytes | PathLike[str] | PathLike[bytes])¶
ConfigSource that resolve configuration against a provider config file.
- lexicon.config.legacy_config_resolver(legacy_dict: dict[str, Any]) ConfigResolver ¶
With the old legacy approach, we juste got a plain configuration dict object. Custom logic was to enrich this configuration with env variables.
This function create a resolve that respect the expected behavior, by using the relevant
ConfigSources
, and we add the config files from working directory.
- lexicon.config.non_interactive_config_resolver() ConfigResolver ¶
Create a typical config resolver in a non-interactive context (e.g. lexicon used as a library). Configuration will be resolved against env variables and lexicon config files in working dir.
Module lexicon.exceptions¶
Lexicon exceptions module
- exception lexicon.exceptions.AuthenticationError¶
Authentication to the provider failed, likely username, password or domain mismatch
- exception lexicon.exceptions.LexiconError¶
Base Class for the Lexicon Exception hierarchy
- exception lexicon.exceptions.ProviderNotAvailableError¶
Custom exception to raise when a provider is not available, typically because some optional dependencies are missing