summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-09-13 13:17:03 +0200
committerChristian Poessinger <christian@poessinger.com>2020-09-13 13:17:03 +0200
commit25136d9a9501dcc40c31f9db8e90be3eb5569d24 (patch)
tree921f49db7b89c65317f1806704a844c43ce81410
parent5136870ca682b2f32e5ccfc68251cba5664623f3 (diff)
downloadvyos-1x-25136d9a9501dcc40c31f9db8e90be3eb5569d24.tar.gz
vyos-1x-25136d9a9501dcc40c31f9db8e90be3eb5569d24.zip
ddclient: T2858: migrate to get_config_dict()
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.tmpl73
-rw-r--r--interface-definitions/dns-dynamic.xml.in1
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py217
3 files changed, 99 insertions, 192 deletions
diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl
index 9c7219230..6fbbb50c3 100644
--- a/data/templates/dynamic-dns/ddclient.conf.tmpl
+++ b/data/templates/dynamic-dns/ddclient.conf.tmpl
@@ -3,44 +3,47 @@ daemon=1m
syslog=yes
ssl=yes
-{% for interface in interfaces -%}
+{% for iface in interface %}
+# ddclient configuration for interface "{{ iface }}"
+{% if interface[iface].use_web is defined and interface[iface].use_web is not none %}
+{% set web_skip = ", web-skip='" + interface[iface].use_web.skip + "'" if interface[iface].use_web.skip is defined else '' %}
+use=web, web='{{ interface[iface].use_web.url }}'{{ web_skip }}
+{% else %}
+use=if, if={{ iface }}
+{% endif %}
-#
-# ddclient configuration for interface "{{ interface.interface }}":
-#
-{% if interface.web_url -%}
-use=web, web='{{ interface.web_url}}' {%- if interface.web_skip %}, web-skip='{{ interface.web_skip }}'{% endif %}
-{% else -%}
-use=if, if={{ interface.interface }}
-{% endif -%}
-
-{% for rfc in interface.rfc2136 -%}
-{% for record in rfc.record %}
-# RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }}
-server={{ rfc.server }}
+{% if interface[iface].rfc2136 is defined and interface[iface].rfc2136 is not none %}
+{% for rfc2136, config in interface[iface].rfc2136.items() %}
+{% for dns_record in config.record if config.record is defined %}
+# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }}
+server={{ config.server }}
protocol=nsupdate
-password={{ rfc.keyfile }}
-ttl={{ rfc.ttl }}
-zone={{ rfc.zone }}
-{{ record }}
-{% endfor -%}
-{% endfor -%}
+password={{ config.keyfile }}
+ttl={{ config.ttl }}
+zone={{ config.zone }}
+{{ dns_record }}
+
+{% endfor %}
+{% endfor %}
+{% endif %}
-{% for srv in interface.service %}
-{% for host in srv.host %}
-# DynDNS provider configuration for {{ host }}
-protocol={{ srv.protocol }},
+{% if interface[iface].service is defined and interface[iface].service is not none %}
+{% for service, config in interface[iface].service.items() %}
+{% for dns_record in config.host_name %}
+# DynDNS provider configuration for {{ service }}, {{ dns_record }}
+protocol={{ config.protocol }},
max-interval=28d,
-login={{ srv.login }},
-password='{{ srv.password }}',
-{% if srv.server -%}
-server={{ srv.server }},
-{% endif -%}
-{% if srv.zone -%}
-zone={{ srv.zone }},
-{% endif -%}
-{{ host }}
-{% endfor %}
-{% endfor %}
+login={{ config.login }},
+password='{{ config.password }}',
+{% if config.server %}
+server={{ config.server }},
+{% endif %}
+{% if config.zone %}
+zone={{ config.zone }},
+{% endif %}
+{{ dns_record }}
+{% endfor %}
+{% endfor %}
+{% endif %}
{% endfor %}
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index 143c04ef6..34a31a7c5 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -58,6 +58,7 @@
<validator name="numeric" argument="--range 1-86400"/>
</constraint>
</properties>
+ <defaultValue>600</defaultValue>
</leafNode>
<leafNode name="zone">
<properties>
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index 57c910a68..93e995b78 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -17,14 +17,13 @@
import os
from sys import exit
-from copy import deepcopy
-from stat import S_IRUSR, S_IWUSR
from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call
+from vyos.configdict import dict_merge
from vyos.template import render
-
+from vyos.util import call
+from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -45,197 +44,101 @@ default_service_protocol = {
'zoneedit': 'zoneedit1'
}
-default_config_data = {
- 'interfaces': [],
- 'deleted': False
-}
-
def get_config(config=None):
- dyndns = deepcopy(default_config_data)
if config:
conf = config
else:
conf = Config()
- base_level = ['service', 'dns', 'dynamic']
+ base_level = ['service', 'dns', 'dynamic']
if not conf.exists(base_level):
- dyndns['deleted'] = True
- return dyndns
-
- for interface in conf.list_nodes(base_level + ['interface']):
- node = {
- 'interface': interface,
- 'rfc2136': [],
- 'service': [],
- 'web_skip': '',
- 'web_url': ''
- }
-
- # set config level to e.g. "service dns dynamic interface eth0"
- conf.set_level(base_level + ['interface', interface])
- # Handle RFC2136 - Dynamic Updates in the Domain Name System
- for rfc2136 in conf.list_nodes(['rfc2136']):
- rfc = {
- 'name': rfc2136,
- 'keyfile': '',
- 'record': [],
- 'server': '',
- 'ttl': '600',
- 'zone': ''
- }
-
- # set config level
- conf.set_level(base_level + ['interface', interface, 'rfc2136', rfc2136])
-
- if conf.exists(['key']):
- rfc['keyfile'] = conf.return_value(['key'])
-
- if conf.exists(['record']):
- rfc['record'] = conf.return_values(['record'])
-
- if conf.exists(['server']):
- rfc['server'] = conf.return_value(['server'])
-
- if conf.exists(['ttl']):
- rfc['ttl'] = conf.return_value(['ttl'])
-
- if conf.exists(['zone']):
- rfc['zone'] = conf.return_value(['zone'])
-
- node['rfc2136'].append(rfc)
-
- # set config level to e.g. "service dns dynamic interface eth0"
- conf.set_level(base_level + ['interface', interface])
- # Handle DynDNS service providers
- for service in conf.list_nodes(['service']):
- srv = {
- 'provider': service,
- 'host': [],
- 'login': '',
- 'password': '',
- 'protocol': '',
- 'server': '',
- 'custom' : False,
- 'zone' : ''
- }
-
- # set config level
- conf.set_level(base_level + ['interface', interface, 'service', service])
-
- # preload protocol from default service mapping
- if service in default_service_protocol.keys():
- srv['protocol'] = default_service_protocol[service]
- else:
- srv['custom'] = True
-
- if conf.exists(['login']):
- srv['login'] = conf.return_value(['login'])
-
- if conf.exists(['host-name']):
- srv['host'] = conf.return_values(['host-name'])
-
- if conf.exists(['protocol']):
- srv['protocol'] = conf.return_value(['protocol'])
-
- if conf.exists(['password']):
- srv['password'] = conf.return_value(['password'])
-
- if conf.exists(['server']):
- srv['server'] = conf.return_value(['server'])
-
- if conf.exists(['zone']):
- srv['zone'] = conf.return_value(['zone'])
- elif srv['provider'] == 'cloudflare':
- # default populate zone entry with bar.tld if
- # host-name is foo.bar.tld
- srv['zone'] = srv['host'][0].split('.',1)[1]
-
- node['service'].append(srv)
-
- # Set config back to appropriate level for these options
- conf.set_level(base_level + ['interface', interface])
-
- # Additional settings in CLI
- if conf.exists(['use-web', 'skip']):
- node['web_skip'] = conf.return_value(['use-web', 'skip'])
-
- if conf.exists(['use-web', 'url']):
- node['web_url'] = conf.return_value(['use-web', 'url'])
-
- # set config level back to top level
- conf.set_level(base_level)
-
- dyndns['interfaces'].append(node)
+ return None
+
+ dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True)
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ for interface in dyndns['interface']:
+ if 'service' in dyndns['interface'][interface]:
+ # 'Autodetect' protocol used by DynDNS service
+ for service in dyndns['interface'][interface]['service']:
+ if service in default_service_protocol:
+ dyndns['interface'][interface]['service'][service].update(
+ {'protocol' : default_service_protocol.get(service)})
+ else:
+ dyndns['interface'][interface]['service'][service].update(
+ {'custom': ''})
+
+ if 'rfc2136' in dyndns['interface'][interface]:
+ default_values = defaults(base_level + ['interface', 'rfc2136'])
+ for rfc2136 in dyndns['interface'][interface]['rfc2136']:
+ dyndns['interface'][interface]['rfc2136'][rfc2136] = dict_merge(
+ default_values, dyndns['interface'][interface]['rfc2136'][rfc2136])
return dyndns
def verify(dyndns):
# bail out early - looks like removal from running config
- if dyndns['deleted']:
+ if not dyndns:
return None
# A 'node' corresponds to an interface
- for node in dyndns['interfaces']:
+ if 'interface' not in dyndns:
+ return None
+ for interface in dyndns['interface']:
# RFC2136 - configuration validation
- for rfc2136 in node['rfc2136']:
- if not rfc2136['record']:
- raise ConfigError('Set key for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ if 'rfc2136' in dyndns['interface'][interface]:
+ for rfc2136, config in dyndns['interface'][interface]['rfc2136'].items():
- if not rfc2136['zone']:
- raise ConfigError('Set zone for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ for tmp in ['record', 'zone', 'server', 'key']:
+ if tmp not in config:
+ raise ConfigError(f'"{tmp}" required for rfc2136 based '
+ f'DynDNS service on "{interface}"')
- if not rfc2136['keyfile']:
- raise ConfigError('Set keyfile for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
- else:
- if not os.path.isfile(rfc2136['keyfile']):
- raise ConfigError('Keyfile for service "{0}" to send DDNS updates for interface "{1}" does not exist'.format(rfc2136['name'], node['interface']))
-
- if not rfc2136['server']:
- raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ if not os.path.isfile(config['key']):
+ raise ConfigError(f'"key"-file not found for rfc2136 based '
+ f'DynDNS service on "{interface}"')
# DynDNS service provider - configuration validation
- for service in node['service']:
- if not service['host']:
- raise ConfigError('Set host-name for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'service' in dyndns['interface'][interface]:
+ for service, config in dyndns['interface'][interface]['service'].items():
+ error_msg = f'required for DynDNS service "{service}" on "{interface}"'
+ if 'host_name' not in config:
+ raise ConfigError(f'"host-name" {error_msg}')
- if not service['login']:
- raise ConfigError('Set login for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'login' not in config:
+ raise ConfigError(f'"login" (username) {error_msg}')
- if not service['password']:
- raise ConfigError('Set password for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'password' not in config:
+ raise ConfigError(f'"password" {error_msg}')
- if service['custom'] is True:
- if not service['protocol']:
- raise ConfigError('Set protocol for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'zone' in config:
+ if service != 'cloudflare':
+ raise ConfigError(f'"zone" option only supported with CloudFlare')
- if not service['server']:
- raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'custom' in config:
+ if 'protocol' not in config:
+ raise ConfigError(f'"protocol" {error_msg}')
- if service['zone']:
- if service['provider'] != 'cloudflare':
- raise ConfigError('Zone option not allowed for "{0}", it can only be used for CloudFlare'.format(service['provider']))
+ if 'server' not in config:
+ raise ConfigError(f'"server" {error_msg}')
return None
def generate(dyndns):
# bail out early - looks like removal from running config
- if dyndns['deleted']:
+ if not dyndns:
return None
- render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns)
-
- # Config file must be accessible only by its owner
- os.chmod(config_file, S_IRUSR | S_IWUSR)
-
+ render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, trim_blocks=True, permission=0o600)
return None
def apply(dyndns):
- if dyndns['deleted']:
+ if not dyndns:
call('systemctl stop ddclient.service')
if os.path.exists(config_file):
os.unlink(config_file)
-
else:
call('systemctl restart ddclient.service')