You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

113 lines
4.2 KiB

#
#
#
from octodns.processor.base import BaseProcessor
class Templating(BaseProcessor):
'''
Record templating using python format. For simple records like TXT and CAA
that is the value itself. For multi-field records like MX or SRV it's the
text portions, exchange and target respectively.
Example Processor Config:
templating:
class: octodns.processor.templating.Templating
# Any k/v present in context will be passed into the .format method and
# thus be available as additional variables in the template. This is all
# optional.
context:
key: value
another: 42
Example Records:
foo:
type: TXT
value: The zone this record lives in is {zone_name}. There are {zone_num_records} record(s).
bar:
type: MX
values:
- preference: 1
exchange: mx1.{zone_name}.mail.mx.
- preference: 1
exchange: mx2.{zone_name}.mail.mx.
Note that validations for some types reject values with {}. When
encountered the best option is to use record level `lenient: true`
https://github.com/octodns/octodns/blob/main/docs/records.md#lenience
Note that if you need to add dynamic context you can create a custom
processor that inherits from Templating and passes them into the call to
super, e.g.
class MyTemplating(Templating):
def __init__(self, *args, context={}, **kwargs):
context['year'] = lambda desired, sources: datetime.now().strftime('%Y')
super().__init__(*args, context, **kwargs)
See https://docs.python.org/3/library/string.html#custom-string-formatting
for details on formatting options. Anything possible in an `f-string` or
`.format` should work here.
'''
def __init__(self, id, *args, context={}, **kwargs):
super().__init__(id, *args, **kwargs)
self.context = context
def process_source_zone(self, desired, sources):
sources = sources or []
zone_params = {
'zone_name': desired.decoded_name,
'zone_decoded_name': desired.decoded_name,
'zone_encoded_name': desired.name,
'zone_num_records': len(desired.records),
'zone_source_ids': ', '.join(s.id for s in sources),
# add any extra context provided to us, if the value is a callable
# object call it passing our params so that arbitrary dynamic
# context can be added for use in formatting
**{
k: (v(desired, sources) if callable(v) else v)
for k, v in self.context.items()
},
}
def params(record):
return {
'record_name': record.decoded_name,
'record_decoded_name': record.decoded_name,
'record_encoded_name': record.name,
'record_fqdn': record.decoded_fqdn,
'record_decoded_fqdn': record.decoded_fqdn,
'record_encoded_fqdn': record.fqdn,
'record_type': record._type,
'record_ttl': record.ttl,
'record_source_id': record.source.id if record.source else None,
**zone_params,
}
for record in desired.records:
if hasattr(record, 'values'):
if record.values and not hasattr(record.values[0], 'template'):
# the (custom) value type does not support templating
continue
new_values = [v.template(params(record)) for v in record.values]
if record.values != new_values:
new = record.copy()
new.values = new_values
desired.add_record(new, replace=True)
else:
if not hasattr(record.value, 'template'):
# the (custom) value type does not support templating
continue
new_value = record.value.template(params(record))
if record.value != new_value:
new = record.copy()
new.value = new_value
desired.add_record(new, replace=True)
return desired