From b203756a886f8d3a16079bea7c047e595b221121 Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Tue, 21 Jun 2011 14:07:19 +0200 Subject: Add ability to specify DNS reverse zone name by IP network address. In order for this to work, chaining of parameters through default_from is made possible. ticket 1474 --- API.txt | 23 +++++++++++++---------- VERSION | 2 +- ipalib/frontend.py | 6 +++++- ipalib/plugins/dns.py | 30 ++++++++++++++++++++++++++++++ tests/test_ipalib/test_frontend.py | 27 +++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 12 deletions(-) diff --git a/API.txt b/API.txt index a8669b64c..3d7150493 100644 --- a/API.txt +++ b/API.txt @@ -737,8 +737,9 @@ output: Output('summary', (, ), 'User-friendly output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") command: dnszone_add -args: 1,18,3 -arg: Str('idnsname', attribute=True, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, required=True) +args: 1,19,3 +arg: Str('idnsname', attribute=True, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, required=True) +option: Str('name_from_ip', _validate_ipnet, attribute=True, cli_name='name_from_ip', label=Gettext('Reverse zone IP network', domain='ipa', localedir=None), multivalue=False, required=False) option: Str('idnssoamname', attribute=True, cli_name='name_server', label=Gettext('Authoritative nameserver', domain='ipa', localedir=None), multivalue=False, required=True) option: Str('idnssoarname', attribute=True, cli_name='admin_email', default_from=DefaultFrom(, 'idnsname'), label=Gettext('Administrator e-mail address', domain='ipa', localedir=None), multivalue=False, normalizer=_rname_normalizer, required=True) option: Int('idnssoaserial', attribute=True, autofill=True, cli_name='serial', create_default=_create_zone_serial, label=Gettext('SOA serial', domain='ipa', localedir=None), minvalue=1, multivalue=False, required=False) @@ -762,27 +763,28 @@ output: Entry('result', , Gettext('A dictionary representing an LDA output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") command: dnszone_del args: 1,1,3 -arg: Str('idnsname', attribute=True, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=True, normalizer=, primary_key=True, query=True, required=True) +arg: Str('idnsname', attribute=True, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=True, normalizer=, primary_key=True, query=True, required=True) option: Flag('continue', autofill=True, cli_name='continue', default=False) output: Output('summary', (, ), 'User-friendly description of action performed') output: Output('result', , 'list of deletions that failed') output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") command: dnszone_disable args: 1,0,3 -arg: Str('idnsname', attribute=True, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) +arg: Str('idnsname', attribute=True, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) output: Output('summary', (, ), 'User-friendly description of action performed') output: Output('result', , 'True means the operation was successful') output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") command: dnszone_enable args: 1,0,3 -arg: Str('idnsname', attribute=True, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) +arg: Str('idnsname', attribute=True, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) output: Output('summary', (, ), 'User-friendly description of action performed') output: Output('result', , 'True means the operation was successful') output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") command: dnszone_find -args: 1,19,4 +args: 1,20,4 arg: Str('criteria?', noextrawhitespace=False) -option: Str('idnsname', attribute=True, autofill=False, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=False) +option: Str('idnsname', attribute=True, autofill=False, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=False) +option: Str('name_from_ip', _validate_ipnet, attribute=True, autofill=False, cli_name='name_from_ip', label=Gettext('Reverse zone IP network', domain='ipa', localedir=None), multivalue=False, query=True, required=False) option: Str('idnssoamname', attribute=True, autofill=False, cli_name='name_server', label=Gettext('Authoritative nameserver', domain='ipa', localedir=None), multivalue=False, query=True, required=False) option: Str('idnssoarname', attribute=True, autofill=False, cli_name='admin_email', default_from=DefaultFrom(, 'idnsname'), label=Gettext('Administrator e-mail address', domain='ipa', localedir=None), multivalue=False, normalizer=_rname_normalizer, query=True, required=False) option: Int('idnssoaserial', attribute=True, autofill=False, cli_name='serial', create_default=_create_zone_serial, label=Gettext('SOA serial', domain='ipa', localedir=None), minvalue=1, multivalue=False, query=True, required=False) @@ -806,8 +808,9 @@ output: ListOfEntries('result', (, ), Gettext('A list output: Output('count', , 'Number of entries returned') output: Output('truncated', , 'True if not all results were returned') command: dnszone_mod -args: 1,17,3 -arg: Str('idnsname', attribute=True, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) +args: 1,18,3 +arg: Str('idnsname', attribute=True, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) +option: Str('name_from_ip', _validate_ipnet, attribute=True, autofill=False, cli_name='name_from_ip', label=Gettext('Reverse zone IP network', domain='ipa', localedir=None), multivalue=False, required=False) option: Str('idnssoamname', attribute=True, autofill=False, cli_name='name_server', label=Gettext('Authoritative nameserver', domain='ipa', localedir=None), multivalue=False, required=False) option: Str('idnssoarname', attribute=True, autofill=False, cli_name='admin_email', default_from=DefaultFrom(, 'idnsname'), label=Gettext('Administrator e-mail address', domain='ipa', localedir=None), multivalue=False, normalizer=_rname_normalizer, required=False) option: Int('idnssoaserial', attribute=True, autofill=False, cli_name='serial', create_default=_create_zone_serial, label=Gettext('SOA serial', domain='ipa', localedir=None), minvalue=1, multivalue=False, required=False) @@ -830,7 +833,7 @@ output: Entry('result', , Gettext('A dictionary representing an LDA output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") command: dnszone_show args: 1,4,3 -arg: Str('idnsname', attribute=True, cli_name='name', label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) +arg: Str('idnsname', attribute=True, cli_name='name', default_from=DefaultFrom(, 'name_from_ip'), label=Gettext('Zone name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, query=True, required=True) option: Flag('rights', autofill=True, default=False, label=Gettext('Rights', domain='ipa', localedir=None)) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui', flags=['no_output']) option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui', flags=['no_output']) diff --git a/VERSION b/VERSION index 224c73567..98e92c5dd 100644 --- a/VERSION +++ b/VERSION @@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=9 +IPA_API_VERSION_MINOR=10 diff --git a/ipalib/frontend.py b/ipalib/frontend.py index 8ac04662f..35343105c 100644 --- a/ipalib/frontend.py +++ b/ipalib/frontend.py @@ -408,7 +408,11 @@ class Command(HasParam): self.debug( 'raw: %s(%s)', self.name, ', '.join(self._repr_iter(**params)) ) - params.update(self.get_default(**params)) + while True: + default = self.get_default(**params) + if len(default) == 0: + break + params.update(default) params = self.normalize(**params) params = self.convert(**params) self.debug( diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index 2928a9000..23abdd94d 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -28,6 +28,10 @@ EXAMPLES: ipa dnszone-add example.com --name-server nameserver.example.com --admin-email admin@example.com + Add new reverse zone specified by network IP address: + ipa dnszone-add --name-from-ip 80.142.15.0/24 + --name-server nameserver.example.com + Add second nameserver for example.com: ipa dnsrecord-add example.com @ --ns-rec nameserver2.example.com @@ -141,6 +145,16 @@ def _create_zone_serial(**kwargs): """Generate serial number for zones.""" return int('%s01' % time.strftime('%Y%d%m')) +def _reverse_zone_name(netstr): + net = netaddr.IPNetwork(netstr) + items = net.ip.reverse_dns.split('.') + if net.version == 4: + return u'.'.join(items[4 - net.prefixlen / 8:]) + elif net.version == 6: + return u'.'.join(items[32 - net.prefixlen / 4:]) + else: + return None + def _validate_ipaddr(ugettext, ipaddr): try: ip = netaddr.IPAddress(ipaddr) @@ -293,9 +307,14 @@ class dnszone(LDAPObject): cli_name='name', label=_('Zone name'), doc=_('Zone name (FQDN)'), + default_from=lambda name_from_ip: _reverse_zone_name(name_from_ip), normalizer=lambda value: value.lower(), primary_key=True, ), + Str('name_from_ip?', _validate_ipnet, + label=_('Reverse zone IP network'), + doc=_('IP network to create reverse zone name from'), + ), Str('idnssoamname', cli_name='name_server', label=_('Authoritative nameserver'), @@ -401,6 +420,9 @@ class dnszone_add(LDAPCreate): if not dns_container_exists(self.api.Backend.ldap2): raise errors.NotFound(reason=_('DNS is not configured')) + if 'name_from_ip' in entry_attrs: + del entry_attrs['name_from_ip'] + entry_attrs['idnszoneactive'] = 'TRUE' entry_attrs['idnsallowdynupdate'] = str( entry_attrs.get('idnsallowdynupdate', False) @@ -445,6 +467,8 @@ class dnszone_mod(LDAPUpdate): Modify DNS zone (SOA record). """ def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + if 'name_from_ip' in entry_attrs: + del entry_attrs['name_from_ip'] entry_attrs['idnsallowdynupdate'] = str( entry_attrs.get('idnsallowdynupdate', False) ).upper() @@ -457,6 +481,12 @@ class dnszone_find(LDAPSearch): """ Search for DNS zones (SOA records). """ + def args_options_2_entry(self, *args, **options): + if 'name_from_ip' in options: + if 'idnsname' not in options: + options['idnsname'] = self.obj.params['idnsname'].get_default(**options) + del options['name_from_ip'] + return super(dnszone_find, self).args_options_2_entry(self, *args, **options) takes_options = LDAPSearch.takes_options + ( Flag('forward_only', diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py index 7381d9ad8..0f6aecb3d 100644 --- a/tests/test_ipalib/test_frontend.py +++ b/tests/test_ipalib/test_frontend.py @@ -418,6 +418,33 @@ class test_Command(ClassChecker): """ # FIXME: Add an updated unit tests for get_default() + def test_default_from_chaining(self): + """ + Test chaining of parameters through default_from. + """ + class my_cmd(self.cls): + takes_options = ( + Str('option0'), + Str('option1', default_from=lambda option0: option0), + Str('option2', default_from=lambda option1: option1), + ) + + def run(self, *args, **options): + return dict(result=options) + + kw = dict(option0=u'some value') + + (api, home) = create_test_api() + api.finalize() + o = my_cmd() + o.set_api(api) + o.finalize() + e = o(**kw) + assert type(e) is dict + assert 'result' in e + assert 'option2' in e['result'] + assert e['result']['option2'] == u'some value' + def test_validate(self): """ Test the `ipalib.frontend.Command.validate` method. -- cgit