summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Kosek <mkosek@redhat.com>2012-01-06 15:12:41 +0100
committerMartin Kosek <mkosek@redhat.com>2012-01-12 09:43:05 +0100
commit52ea3a6b2958875da6370433d14509bdbd4c4943 (patch)
treec523383fdba7b9c0bfa72376ece4bdca2b962e0b
parent91c10419f8a32d070ac4b8fa0378ea48bb82388e (diff)
downloadfreeipa-52ea3a6b2958875da6370433d14509bdbd4c4943.tar.gz
freeipa-52ea3a6b2958875da6370433d14509bdbd4c4943.tar.xz
freeipa-52ea3a6b2958875da6370433d14509bdbd4c4943.zip
Refactor dnsrecord processing
Current DNS record processing architecture has many flaws, including custom execute() methods which does not take advantage of base LDAP commands or nonstandard and confusing DNS record option processing. This patch refactors DNS record processing with the following improvements: * Every DNS record has now own Parameter type. Each DNS record consists from one or more "parts" which are also Parameters. This architecture will enable much easier implementation of future per-DNS-type API. * Validation is now not written as a separate function for every parameter but is delegated to DNS record parts. * Normalization is also delegated to DNS record parts. * Since standard LDAP base commands execute method is now used, dnsrecord-add and dnsrecord-mod correctly supports --setattr and --addattr options. * In order to prevent confusion unsupported DNS record types are now hidden. They are still present in the plugin so that old clients receive proper validation error. The patch also contains several fixes: * Fix domain-name validation and normalization- allow domain names that are not fully qualified. For example --cname-rec=bar is a valid domain-name for bind which will translate it then as bar.<owning-domain>. This change implies, that fully qualified domain names must end with '.'. * Do not let user accidentally remove entire zone with command "ipa dnsrecord-del @ --del-all". * Fix --ttl and --class option processing in dnsrecord-add and dnsrecord-mod. All API changes are compatible with clients without this patch. https://fedorahosted.org/freeipa/ticket/2082
-rw-r--r--API.txt328
-rw-r--r--ipalib/plugins/dns.py1347
-rw-r--r--ipalib/util.py16
-rw-r--r--tests/test_xmlrpc/test_dns_plugin.py19
4 files changed, 971 insertions, 739 deletions
diff --git a/API.txt b/API.txt
index 493d5a3ff..895225a1c 100644
--- a/API.txt
+++ b/API.txt
@@ -613,135 +613,93 @@ arg: Str('dnszoneidnsname', cli_name='dnszone', query=True, required=True)
arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
option: Int('dnsttl', attribute=True, cli_name='ttl', multivalue=False, required=False)
option: StrEnum('dnsclass', attribute=True, cli_name='class', multivalue=False, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
+option: ARecord('arecord', attribute=True, cli_name='a_rec', csv=True, multivalue=True, required=False)
+option: AAAARecord('aaaarecord', attribute=True, cli_name='aaaa_rec', csv=True, multivalue=True, required=False)
+option: A6Record('a6record', attribute=True, cli_name='a6_rec', csv=True, multivalue=True, required=False)
+option: AFSDBRecord('afsdbrecord', attribute=True, cli_name='afsdb_rec', csv=True, multivalue=True, required=False)
+option: APLRecord('aplrecord', attribute=True, cli_name='apl_rec', csv=True, multivalue=True, required=False)
+option: CERTRecord('certrecord', attribute=True, cli_name='cert_rec', csv=True, multivalue=True, required=False)
+option: CNAMERecord('cnamerecord', attribute=True, cli_name='cname_rec', csv=True, multivalue=True, required=False)
+option: DHCIDRecord('dhcidrecord', attribute=True, cli_name='dhcid_rec', csv=True, multivalue=True, required=False)
+option: DLVRecord('dlvrecord', attribute=True, cli_name='dlv_rec', csv=True, multivalue=True, required=False)
+option: DNAMERecord('dnamerecord', attribute=True, cli_name='dname_rec', csv=True, multivalue=True, required=False)
+option: DNSKEYRecord('dnskeyrecord', attribute=True, cli_name='dnskey_rec', csv=True, multivalue=True, required=False)
+option: DSRecord('dsrecord', attribute=True, cli_name='ds_rec', csv=True, multivalue=True, required=False)
+option: HIPRecord('hiprecord', attribute=True, cli_name='hip_rec', csv=True, multivalue=True, required=False)
+option: IPSECKEYRecord('ipseckeyrecord', attribute=True, cli_name='ipseckey_rec', csv=True, multivalue=True, required=False)
+option: KEYRecord('keyrecord', attribute=True, cli_name='key_rec', csv=True, multivalue=True, required=False)
+option: KXRecord('kxrecord', attribute=True, cli_name='kx_rec', csv=True, multivalue=True, required=False)
+option: LOCRecord('locrecord', attribute=True, cli_name='loc_rec', csv=True, multivalue=True, required=False)
+option: MXRecord('mxrecord', attribute=True, cli_name='mx_rec', csv=True, multivalue=True, required=False)
+option: NAPTRRecord('naptrrecord', attribute=True, cli_name='naptr_rec', csv=True, multivalue=True, required=False)
+option: NSRecord('nsrecord', attribute=True, cli_name='ns_rec', csv=True, multivalue=True, required=False)
+option: NSECRecord('nsecrecord', attribute=True, cli_name='nsec_rec', csv=True, multivalue=True, required=False)
+option: NSEC3Record('nsec3record', attribute=True, cli_name='nsec3_rec', csv=True, multivalue=True, required=False)
+option: NSEC3PARAMRecord('nsec3paramrecord', attribute=True, cli_name='nsec3param_rec', csv=True, multivalue=True, required=False)
+option: PTRRecord('ptrrecord', attribute=True, cli_name='ptr_rec', csv=True, multivalue=True, required=False)
+option: RRSIGRecord('rrsigrecord', attribute=True, cli_name='rrsig_rec', csv=True, multivalue=True, required=False)
+option: RPRecord('rprecord', attribute=True, cli_name='rp_rec', csv=True, multivalue=True, required=False)
+option: SIGRecord('sigrecord', attribute=True, cli_name='sig_rec', csv=True, multivalue=True, required=False)
+option: SPFRecord('spfrecord', attribute=True, cli_name='spf_rec', csv=True, multivalue=True, required=False)
+option: SRVRecord('srvrecord', attribute=True, cli_name='srv_rec', csv=True, multivalue=True, required=False)
+option: SSHFPRecord('sshfprecord', attribute=True, cli_name='sshfp_rec', csv=True, multivalue=True, required=False)
+option: TARecord('tarecord', attribute=True, cli_name='ta_rec', csv=True, multivalue=True, required=False)
+option: TKEYRecord('tkeyrecord', attribute=True, cli_name='tkey_rec', csv=True, multivalue=True, required=False)
+option: TSIGRecord('tsigrecord', attribute=True, cli_name='tsig_rec', csv=True, multivalue=True, required=False)
+option: TXTRecord('txtrecord', attribute=True, cli_name='txt_rec', csv=True, multivalue=True, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('force', autofill=True, default=False)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui')
-option: Str('arecord*', attribute=True, cli_name='a_rec', csv=True)
-option: Str('aaaarecord*', attribute=True, cli_name='aaaa_rec', csv=True)
-option: Str('a6record*', attribute=True, cli_name='a6_rec', csv=True)
-option: Str('afsdbrecord*', attribute=True, cli_name='afsdb_rec', csv=True)
-option: Str('aplrecord*', attribute=True, cli_name='apl_rec', csv=True)
-option: Str('certrecord*', attribute=True, cli_name='cert_rec', csv=True)
-option: Str('cnamerecord*', attribute=True, cli_name='cname_rec', csv=True)
-option: Str('dhcidrecord*', attribute=True, cli_name='dhcid_rec', csv=True)
-option: Str('dlvrecord*', attribute=True, cli_name='dlv_rec', csv=True)
-option: Str('dnamerecord*', attribute=True, cli_name='dname_rec', csv=True)
-option: Str('dnskeyrecord*', attribute=True, cli_name='dnskey_rec', csv=True)
-option: Str('dsrecord*', attribute=True, cli_name='ds_rec', csv=True)
-option: Str('hiprecord*', attribute=True, cli_name='hip_rec', csv=True)
-option: Str('ipseckeyrecord*', attribute=True, cli_name='ipseckey_rec', csv=True)
-option: Str('keyrecord*', attribute=True, cli_name='key_rec', csv=True)
-option: Str('kxrecord*', attribute=True, cli_name='kx_rec', csv=True)
-option: Str('locrecord*', attribute=True, cli_name='loc_rec', csv=True)
-option: Str('mxrecord*', attribute=True, cli_name='mx_rec', csv=True)
-option: Str('naptrrecord*', attribute=True, cli_name='naptr_rec', csv=True)
-option: Str('nsrecord*', attribute=True, cli_name='ns_rec', csv=True)
-option: Str('nsecrecord*', attribute=True, cli_name='nsec_rec', csv=True)
-option: Str('nsec3record*', attribute=True, cli_name='nsec3_rec', csv=True)
-option: Str('nsec3paramrecord*', attribute=True, cli_name='nsec3param_rec', csv=True)
-option: Str('ptrrecord*', attribute=True, cli_name='ptr_rec', csv=True)
-option: Str('rrsigrecord*', attribute=True, cli_name='rrsig_rec', csv=True)
-option: Str('rprecord*', attribute=True, cli_name='rp_rec', csv=True)
-option: Str('sigrecord*', attribute=True, cli_name='sig_rec', csv=True)
-option: Str('spfrecord*', attribute=True, cli_name='spf_rec', csv=True)
-option: Str('srvrecord*', attribute=True, cli_name='srv_rec', csv=True)
-option: Str('sshfprecord*', attribute=True, cli_name='sshfp_rec', csv=True)
-option: Str('tarecord*', attribute=True, cli_name='ta_rec', csv=True)
-option: Str('tkeyrecord*', attribute=True, cli_name='tkey_rec', csv=True)
-option: Str('tsigrecord*', attribute=True, cli_name='tsig_rec', csv=True)
-option: Str('txtrecord*', attribute=True, cli_name='txt_rec', csv=True)
-output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
-output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
-output: Output('value', <type 'unicode'>, None)
-command: dnsrecord_add_record
-args: 2,37,3
-arg: Str('dnszoneidnsname', cli_name='dnszone', query=True, required=True)
-arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
-option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
-option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
-option: Str('version?', exclude='webui')
-option: Str('arecord*', attribute=True, cli_name='a_rec', csv=True)
-option: Str('aaaarecord*', attribute=True, cli_name='aaaa_rec', csv=True)
-option: Str('a6record*', attribute=True, cli_name='a6_rec', csv=True)
-option: Str('afsdbrecord*', attribute=True, cli_name='afsdb_rec', csv=True)
-option: Str('aplrecord*', attribute=True, cli_name='apl_rec', csv=True)
-option: Str('certrecord*', attribute=True, cli_name='cert_rec', csv=True)
-option: Str('cnamerecord*', attribute=True, cli_name='cname_rec', csv=True)
-option: Str('dhcidrecord*', attribute=True, cli_name='dhcid_rec', csv=True)
-option: Str('dlvrecord*', attribute=True, cli_name='dlv_rec', csv=True)
-option: Str('dnamerecord*', attribute=True, cli_name='dname_rec', csv=True)
-option: Str('dnskeyrecord*', attribute=True, cli_name='dnskey_rec', csv=True)
-option: Str('dsrecord*', attribute=True, cli_name='ds_rec', csv=True)
-option: Str('hiprecord*', attribute=True, cli_name='hip_rec', csv=True)
-option: Str('ipseckeyrecord*', attribute=True, cli_name='ipseckey_rec', csv=True)
-option: Str('keyrecord*', attribute=True, cli_name='key_rec', csv=True)
-option: Str('kxrecord*', attribute=True, cli_name='kx_rec', csv=True)
-option: Str('locrecord*', attribute=True, cli_name='loc_rec', csv=True)
-option: Str('mxrecord*', attribute=True, cli_name='mx_rec', csv=True)
-option: Str('naptrrecord*', attribute=True, cli_name='naptr_rec', csv=True)
-option: Str('nsrecord*', attribute=True, cli_name='ns_rec', csv=True)
-option: Str('nsecrecord*', attribute=True, cli_name='nsec_rec', csv=True)
-option: Str('nsec3record*', attribute=True, cli_name='nsec3_rec', csv=True)
-option: Str('nsec3paramrecord*', attribute=True, cli_name='nsec3param_rec', csv=True)
-option: Str('ptrrecord*', attribute=True, cli_name='ptr_rec', csv=True)
-option: Str('rrsigrecord*', attribute=True, cli_name='rrsig_rec', csv=True)
-option: Str('rprecord*', attribute=True, cli_name='rp_rec', csv=True)
-option: Str('sigrecord*', attribute=True, cli_name='sig_rec', csv=True)
-option: Str('spfrecord*', attribute=True, cli_name='spf_rec', csv=True)
-option: Str('srvrecord*', attribute=True, cli_name='srv_rec', csv=True)
-option: Str('sshfprecord*', attribute=True, cli_name='sshfp_rec', csv=True)
-option: Str('tarecord*', attribute=True, cli_name='ta_rec', csv=True)
-option: Str('tkeyrecord*', attribute=True, cli_name='tkey_rec', csv=True)
-option: Str('tsigrecord*', attribute=True, cli_name='tsig_rec', csv=True)
-option: Str('txtrecord*', attribute=True, cli_name='txt_rec', csv=True)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None)
command: dnsrecord_del
-args: 2,38,3
+args: 2,40,3
arg: Str('dnszoneidnsname', cli_name='dnszone', query=True, required=True)
arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Int('dnsttl', attribute=True, autofill=False, cli_name='ttl', multivalue=False, required=False)
+option: StrEnum('dnsclass', attribute=True, autofill=False, cli_name='class', multivalue=False, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
+option: ARecord('arecord', attribute=True, autofill=False, cli_name='a_rec', csv=True, multivalue=True, required=False)
+option: AAAARecord('aaaarecord', attribute=True, autofill=False, cli_name='aaaa_rec', csv=True, multivalue=True, required=False)
+option: A6Record('a6record', attribute=True, autofill=False, cli_name='a6_rec', csv=True, multivalue=True, required=False)
+option: AFSDBRecord('afsdbrecord', attribute=True, autofill=False, cli_name='afsdb_rec', csv=True, multivalue=True, required=False)
+option: APLRecord('aplrecord', attribute=True, autofill=False, cli_name='apl_rec', csv=True, multivalue=True, required=False)
+option: CERTRecord('certrecord', attribute=True, autofill=False, cli_name='cert_rec', csv=True, multivalue=True, required=False)
+option: CNAMERecord('cnamerecord', attribute=True, autofill=False, cli_name='cname_rec', csv=True, multivalue=True, required=False)
+option: DHCIDRecord('dhcidrecord', attribute=True, autofill=False, cli_name='dhcid_rec', csv=True, multivalue=True, required=False)
+option: DLVRecord('dlvrecord', attribute=True, autofill=False, cli_name='dlv_rec', csv=True, multivalue=True, required=False)
+option: DNAMERecord('dnamerecord', attribute=True, autofill=False, cli_name='dname_rec', csv=True, multivalue=True, required=False)
+option: DNSKEYRecord('dnskeyrecord', attribute=True, autofill=False, cli_name='dnskey_rec', csv=True, multivalue=True, required=False)
+option: DSRecord('dsrecord', attribute=True, autofill=False, cli_name='ds_rec', csv=True, multivalue=True, required=False)
+option: HIPRecord('hiprecord', attribute=True, autofill=False, cli_name='hip_rec', csv=True, multivalue=True, required=False)
+option: IPSECKEYRecord('ipseckeyrecord', attribute=True, autofill=False, cli_name='ipseckey_rec', csv=True, multivalue=True, required=False)
+option: KEYRecord('keyrecord', attribute=True, autofill=False, cli_name='key_rec', csv=True, multivalue=True, required=False)
+option: KXRecord('kxrecord', attribute=True, autofill=False, cli_name='kx_rec', csv=True, multivalue=True, required=False)
+option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, required=False)
+option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, required=False)
+option: NAPTRRecord('naptrrecord', attribute=True, autofill=False, cli_name='naptr_rec', csv=True, multivalue=True, required=False)
+option: NSRecord('nsrecord', attribute=True, autofill=False, cli_name='ns_rec', csv=True, multivalue=True, required=False)
+option: NSECRecord('nsecrecord', attribute=True, autofill=False, cli_name='nsec_rec', csv=True, multivalue=True, required=False)
+option: NSEC3Record('nsec3record', attribute=True, autofill=False, cli_name='nsec3_rec', csv=True, multivalue=True, required=False)
+option: NSEC3PARAMRecord('nsec3paramrecord', attribute=True, autofill=False, cli_name='nsec3param_rec', csv=True, multivalue=True, required=False)
+option: PTRRecord('ptrrecord', attribute=True, autofill=False, cli_name='ptr_rec', csv=True, multivalue=True, required=False)
+option: RRSIGRecord('rrsigrecord', attribute=True, autofill=False, cli_name='rrsig_rec', csv=True, multivalue=True, required=False)
+option: RPRecord('rprecord', attribute=True, autofill=False, cli_name='rp_rec', csv=True, multivalue=True, required=False)
+option: SIGRecord('sigrecord', attribute=True, autofill=False, cli_name='sig_rec', csv=True, multivalue=True, required=False)
+option: SPFRecord('spfrecord', attribute=True, autofill=False, cli_name='spf_rec', csv=True, multivalue=True, required=False)
+option: SRVRecord('srvrecord', attribute=True, autofill=False, cli_name='srv_rec', csv=True, multivalue=True, required=False)
+option: SSHFPRecord('sshfprecord', attribute=True, autofill=False, cli_name='sshfp_rec', csv=True, multivalue=True, required=False)
+option: TARecord('tarecord', attribute=True, autofill=False, cli_name='ta_rec', csv=True, multivalue=True, required=False)
+option: TKEYRecord('tkeyrecord', attribute=True, autofill=False, cli_name='tkey_rec', csv=True, multivalue=True, required=False)
+option: TSIGRecord('tsigrecord', attribute=True, autofill=False, cli_name='tsig_rec', csv=True, multivalue=True, required=False)
+option: TXTRecord('txtrecord', attribute=True, autofill=False, cli_name='txt_rec', csv=True, multivalue=True, required=False)
option: Flag('del_all', autofill=True, default=False)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui')
-option: Str('arecord*', attribute=True, cli_name='a_rec', csv=True)
-option: Str('aaaarecord*', attribute=True, cli_name='aaaa_rec', csv=True)
-option: Str('a6record*', attribute=True, cli_name='a6_rec', csv=True)
-option: Str('afsdbrecord*', attribute=True, cli_name='afsdb_rec', csv=True)
-option: Str('aplrecord*', attribute=True, cli_name='apl_rec', csv=True)
-option: Str('certrecord*', attribute=True, cli_name='cert_rec', csv=True)
-option: Str('cnamerecord*', attribute=True, cli_name='cname_rec', csv=True)
-option: Str('dhcidrecord*', attribute=True, cli_name='dhcid_rec', csv=True)
-option: Str('dlvrecord*', attribute=True, cli_name='dlv_rec', csv=True)
-option: Str('dnamerecord*', attribute=True, cli_name='dname_rec', csv=True)
-option: Str('dnskeyrecord*', attribute=True, cli_name='dnskey_rec', csv=True)
-option: Str('dsrecord*', attribute=True, cli_name='ds_rec', csv=True)
-option: Str('hiprecord*', attribute=True, cli_name='hip_rec', csv=True)
-option: Str('ipseckeyrecord*', attribute=True, cli_name='ipseckey_rec', csv=True)
-option: Str('keyrecord*', attribute=True, cli_name='key_rec', csv=True)
-option: Str('kxrecord*', attribute=True, cli_name='kx_rec', csv=True)
-option: Str('locrecord*', attribute=True, cli_name='loc_rec', csv=True)
-option: Str('mxrecord*', attribute=True, cli_name='mx_rec', csv=True)
-option: Str('naptrrecord*', attribute=True, cli_name='naptr_rec', csv=True)
-option: Str('nsrecord*', attribute=True, cli_name='ns_rec', csv=True)
-option: Str('nsecrecord*', attribute=True, cli_name='nsec_rec', csv=True)
-option: Str('nsec3record*', attribute=True, cli_name='nsec3_rec', csv=True)
-option: Str('nsec3paramrecord*', attribute=True, cli_name='nsec3param_rec', csv=True)
-option: Str('ptrrecord*', attribute=True, cli_name='ptr_rec', csv=True)
-option: Str('rrsigrecord*', attribute=True, cli_name='rrsig_rec', csv=True)
-option: Str('rprecord*', attribute=True, cli_name='rp_rec', csv=True)
-option: Str('sigrecord*', attribute=True, cli_name='sig_rec', csv=True)
-option: Str('spfrecord*', attribute=True, cli_name='spf_rec', csv=True)
-option: Str('srvrecord*', attribute=True, cli_name='srv_rec', csv=True)
-option: Str('sshfprecord*', attribute=True, cli_name='sshfp_rec', csv=True)
-option: Str('tarecord*', attribute=True, cli_name='ta_rec', csv=True)
-option: Str('tkeyrecord*', attribute=True, cli_name='tkey_rec', csv=True)
-option: Str('tsigrecord*', attribute=True, cli_name='tsig_rec', csv=True)
-option: Str('txtrecord*', attribute=True, cli_name='txt_rec', csv=True)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None)
@@ -760,91 +718,97 @@ arg: Str('criteria?', noextrawhitespace=False)
option: Str('idnsname', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
option: Int('dnsttl', attribute=True, autofill=False, cli_name='ttl', multivalue=False, query=True, required=False)
option: StrEnum('dnsclass', attribute=True, autofill=False, cli_name='class', multivalue=False, query=True, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
+option: ARecord('arecord', attribute=True, autofill=False, cli_name='a_rec', csv=True, multivalue=True, query=True, required=False)
+option: AAAARecord('aaaarecord', attribute=True, autofill=False, cli_name='aaaa_rec', csv=True, multivalue=True, query=True, required=False)
+option: A6Record('a6record', attribute=True, autofill=False, cli_name='a6_rec', csv=True, multivalue=True, query=True, required=False)
+option: AFSDBRecord('afsdbrecord', attribute=True, autofill=False, cli_name='afsdb_rec', csv=True, multivalue=True, query=True, required=False)
+option: APLRecord('aplrecord', attribute=True, autofill=False, cli_name='apl_rec', csv=True, multivalue=True, query=True, required=False)
+option: CERTRecord('certrecord', attribute=True, autofill=False, cli_name='cert_rec', csv=True, multivalue=True, query=True, required=False)
+option: CNAMERecord('cnamerecord', attribute=True, autofill=False, cli_name='cname_rec', csv=True, multivalue=True, query=True, required=False)
+option: DHCIDRecord('dhcidrecord', attribute=True, autofill=False, cli_name='dhcid_rec', csv=True, multivalue=True, query=True, required=False)
+option: DLVRecord('dlvrecord', attribute=True, autofill=False, cli_name='dlv_rec', csv=True, multivalue=True, query=True, required=False)
+option: DNAMERecord('dnamerecord', attribute=True, autofill=False, cli_name='dname_rec', csv=True, multivalue=True, query=True, required=False)
+option: DNSKEYRecord('dnskeyrecord', attribute=True, autofill=False, cli_name='dnskey_rec', csv=True, multivalue=True, query=True, required=False)
+option: DSRecord('dsrecord', attribute=True, autofill=False, cli_name='ds_rec', csv=True, multivalue=True, query=True, required=False)
+option: HIPRecord('hiprecord', attribute=True, autofill=False, cli_name='hip_rec', csv=True, multivalue=True, query=True, required=False)
+option: IPSECKEYRecord('ipseckeyrecord', attribute=True, autofill=False, cli_name='ipseckey_rec', csv=True, multivalue=True, query=True, required=False)
+option: KEYRecord('keyrecord', attribute=True, autofill=False, cli_name='key_rec', csv=True, multivalue=True, query=True, required=False)
+option: KXRecord('kxrecord', attribute=True, autofill=False, cli_name='kx_rec', csv=True, multivalue=True, query=True, required=False)
+option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, query=True, required=False)
+option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, query=True, required=False)
+option: NAPTRRecord('naptrrecord', attribute=True, autofill=False, cli_name='naptr_rec', csv=True, multivalue=True, query=True, required=False)
+option: NSRecord('nsrecord', attribute=True, autofill=False, cli_name='ns_rec', csv=True, multivalue=True, query=True, required=False)
+option: NSECRecord('nsecrecord', attribute=True, autofill=False, cli_name='nsec_rec', csv=True, multivalue=True, query=True, required=False)
+option: NSEC3Record('nsec3record', attribute=True, autofill=False, cli_name='nsec3_rec', csv=True, multivalue=True, query=True, required=False)
+option: NSEC3PARAMRecord('nsec3paramrecord', attribute=True, autofill=False, cli_name='nsec3param_rec', csv=True, multivalue=True, query=True, required=False)
+option: PTRRecord('ptrrecord', attribute=True, autofill=False, cli_name='ptr_rec', csv=True, multivalue=True, query=True, required=False)
+option: RRSIGRecord('rrsigrecord', attribute=True, autofill=False, cli_name='rrsig_rec', csv=True, multivalue=True, query=True, required=False)
+option: RPRecord('rprecord', attribute=True, autofill=False, cli_name='rp_rec', csv=True, multivalue=True, query=True, required=False)
+option: SIGRecord('sigrecord', attribute=True, autofill=False, cli_name='sig_rec', csv=True, multivalue=True, query=True, required=False)
+option: SPFRecord('spfrecord', attribute=True, autofill=False, cli_name='spf_rec', csv=True, multivalue=True, query=True, required=False)
+option: SRVRecord('srvrecord', attribute=True, autofill=False, cli_name='srv_rec', csv=True, multivalue=True, query=True, required=False)
+option: SSHFPRecord('sshfprecord', attribute=True, autofill=False, cli_name='sshfp_rec', csv=True, multivalue=True, query=True, required=False)
+option: TARecord('tarecord', attribute=True, autofill=False, cli_name='ta_rec', csv=True, multivalue=True, query=True, required=False)
+option: TKEYRecord('tkeyrecord', attribute=True, autofill=False, cli_name='tkey_rec', csv=True, multivalue=True, query=True, required=False)
+option: TSIGRecord('tsigrecord', attribute=True, autofill=False, cli_name='tsig_rec', csv=True, multivalue=True, query=True, required=False)
+option: TXTRecord('txtrecord', attribute=True, autofill=False, cli_name='txt_rec', csv=True, multivalue=True, query=True, required=False)
option: Int('timelimit?', autofill=False, minvalue=0)
option: Int('sizelimit?', autofill=False, minvalue=0)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui')
option: Flag('pkey_only?', autofill=True, default=False)
-option: Str('arecord', attribute=True, cli_name='a_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('aaaarecord', attribute=True, cli_name='aaaa_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('a6record', attribute=True, cli_name='a6_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('afsdbrecord', attribute=True, cli_name='afsdb_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('aplrecord', attribute=True, cli_name='apl_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('certrecord', attribute=True, cli_name='cert_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('cnamerecord', attribute=True, cli_name='cname_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('dhcidrecord', attribute=True, cli_name='dhcid_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('dlvrecord', attribute=True, cli_name='dlv_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('dnamerecord', attribute=True, cli_name='dname_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('dnskeyrecord', attribute=True, cli_name='dnskey_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('dsrecord', attribute=True, cli_name='ds_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('hiprecord', attribute=True, cli_name='hip_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('ipseckeyrecord', attribute=True, cli_name='ipseckey_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('keyrecord', attribute=True, cli_name='key_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('kxrecord', attribute=True, cli_name='kx_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('locrecord', attribute=True, cli_name='loc_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('mxrecord', attribute=True, cli_name='mx_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('naptrrecord', attribute=True, cli_name='naptr_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('nsrecord', attribute=True, cli_name='ns_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('nsecrecord', attribute=True, cli_name='nsec_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('nsec3record', attribute=True, cli_name='nsec3_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('nsec3paramrecord', attribute=True, cli_name='nsec3param_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('ptrrecord', attribute=True, cli_name='ptr_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('rrsigrecord', attribute=True, cli_name='rrsig_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('rprecord', attribute=True, cli_name='rp_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('sigrecord', attribute=True, cli_name='sig_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('spfrecord', attribute=True, cli_name='spf_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('srvrecord', attribute=True, cli_name='srv_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('sshfprecord', attribute=True, cli_name='sshfp_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('tarecord', attribute=True, cli_name='ta_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('tkeyrecord', attribute=True, cli_name='tkey_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('tsigrecord', attribute=True, cli_name='tsig_rec', csv=True, multivalue=True, query=True, required=False)
-option: Str('txtrecord', attribute=True, cli_name='txt_rec', csv=True, multivalue=True, query=True, required=False)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
output: Output('count', <type 'int'>, None)
output: Output('truncated', <type 'bool'>, None)
command: dnsrecord_mod
-args: 2,37,3
+args: 2,43,3
arg: Str('dnszoneidnsname', cli_name='dnszone', query=True, required=True)
arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Int('dnsttl', attribute=True, autofill=False, cli_name='ttl', multivalue=False, required=False)
+option: StrEnum('dnsclass', attribute=True, autofill=False, cli_name='class', multivalue=False, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
+option: ARecord('arecord', attribute=True, autofill=False, cli_name='a_rec', csv=True, multivalue=True, required=False)
+option: AAAARecord('aaaarecord', attribute=True, autofill=False, cli_name='aaaa_rec', csv=True, multivalue=True, required=False)
+option: A6Record('a6record', attribute=True, autofill=False, cli_name='a6_rec', csv=True, multivalue=True, required=False)
+option: AFSDBRecord('afsdbrecord', attribute=True, autofill=False, cli_name='afsdb_rec', csv=True, multivalue=True, required=False)
+option: APLRecord('aplrecord', attribute=True, autofill=False, cli_name='apl_rec', csv=True, multivalue=True, required=False)
+option: CERTRecord('certrecord', attribute=True, autofill=False, cli_name='cert_rec', csv=True, multivalue=True, required=False)
+option: CNAMERecord('cnamerecord', attribute=True, autofill=False, cli_name='cname_rec', csv=True, multivalue=True, required=False)
+option: DHCIDRecord('dhcidrecord', attribute=True, autofill=False, cli_name='dhcid_rec', csv=True, multivalue=True, required=False)
+option: DLVRecord('dlvrecord', attribute=True, autofill=False, cli_name='dlv_rec', csv=True, multivalue=True, required=False)
+option: DNAMERecord('dnamerecord', attribute=True, autofill=False, cli_name='dname_rec', csv=True, multivalue=True, required=False)
+option: DNSKEYRecord('dnskeyrecord', attribute=True, autofill=False, cli_name='dnskey_rec', csv=True, multivalue=True, required=False)
+option: DSRecord('dsrecord', attribute=True, autofill=False, cli_name='ds_rec', csv=True, multivalue=True, required=False)
+option: HIPRecord('hiprecord', attribute=True, autofill=False, cli_name='hip_rec', csv=True, multivalue=True, required=False)
+option: IPSECKEYRecord('ipseckeyrecord', attribute=True, autofill=False, cli_name='ipseckey_rec', csv=True, multivalue=True, required=False)
+option: KEYRecord('keyrecord', attribute=True, autofill=False, cli_name='key_rec', csv=True, multivalue=True, required=False)
+option: KXRecord('kxrecord', attribute=True, autofill=False, cli_name='kx_rec', csv=True, multivalue=True, required=False)
+option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, required=False)
+option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, required=False)
+option: NAPTRRecord('naptrrecord', attribute=True, autofill=False, cli_name='naptr_rec', csv=True, multivalue=True, required=False)
+option: NSRecord('nsrecord', attribute=True, autofill=False, cli_name='ns_rec', csv=True, multivalue=True, required=False)
+option: NSECRecord('nsecrecord', attribute=True, autofill=False, cli_name='nsec_rec', csv=True, multivalue=True, required=False)
+option: NSEC3Record('nsec3record', attribute=True, autofill=False, cli_name='nsec3_rec', csv=True, multivalue=True, required=False)
+option: NSEC3PARAMRecord('nsec3paramrecord', attribute=True, autofill=False, cli_name='nsec3param_rec', csv=True, multivalue=True, required=False)
+option: PTRRecord('ptrrecord', attribute=True, autofill=False, cli_name='ptr_rec', csv=True, multivalue=True, required=False)
+option: RRSIGRecord('rrsigrecord', attribute=True, autofill=False, cli_name='rrsig_rec', csv=True, multivalue=True, required=False)
+option: RPRecord('rprecord', attribute=True, autofill=False, cli_name='rp_rec', csv=True, multivalue=True, required=False)
+option: SIGRecord('sigrecord', attribute=True, autofill=False, cli_name='sig_rec', csv=True, multivalue=True, required=False)
+option: SPFRecord('spfrecord', attribute=True, autofill=False, cli_name='spf_rec', csv=True, multivalue=True, required=False)
+option: SRVRecord('srvrecord', attribute=True, autofill=False, cli_name='srv_rec', csv=True, multivalue=True, required=False)
+option: SSHFPRecord('sshfprecord', attribute=True, autofill=False, cli_name='sshfp_rec', csv=True, multivalue=True, required=False)
+option: TARecord('tarecord', attribute=True, autofill=False, cli_name='ta_rec', csv=True, multivalue=True, required=False)
+option: TKEYRecord('tkeyrecord', attribute=True, autofill=False, cli_name='tkey_rec', csv=True, multivalue=True, required=False)
+option: TSIGRecord('tsigrecord', attribute=True, autofill=False, cli_name='tsig_rec', csv=True, multivalue=True, required=False)
+option: TXTRecord('txtrecord', attribute=True, autofill=False, cli_name='txt_rec', csv=True, multivalue=True, required=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Flag('rights', autofill=True, default=False)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui')
-option: Str('arecord*', attribute=True, cli_name='a_rec', csv=True)
-option: Str('aaaarecord*', attribute=True, cli_name='aaaa_rec', csv=True)
-option: Str('a6record*', attribute=True, cli_name='a6_rec', csv=True)
-option: Str('afsdbrecord*', attribute=True, cli_name='afsdb_rec', csv=True)
-option: Str('aplrecord*', attribute=True, cli_name='apl_rec', csv=True)
-option: Str('certrecord*', attribute=True, cli_name='cert_rec', csv=True)
-option: Str('cnamerecord*', attribute=True, cli_name='cname_rec', csv=True)
-option: Str('dhcidrecord*', attribute=True, cli_name='dhcid_rec', csv=True)
-option: Str('dlvrecord*', attribute=True, cli_name='dlv_rec', csv=True)
-option: Str('dnamerecord*', attribute=True, cli_name='dname_rec', csv=True)
-option: Str('dnskeyrecord*', attribute=True, cli_name='dnskey_rec', csv=True)
-option: Str('dsrecord*', attribute=True, cli_name='ds_rec', csv=True)
-option: Str('hiprecord*', attribute=True, cli_name='hip_rec', csv=True)
-option: Str('ipseckeyrecord*', attribute=True, cli_name='ipseckey_rec', csv=True)
-option: Str('keyrecord*', attribute=True, cli_name='key_rec', csv=True)
-option: Str('kxrecord*', attribute=True, cli_name='kx_rec', csv=True)
-option: Str('locrecord*', attribute=True, cli_name='loc_rec', csv=True)
-option: Str('mxrecord*', attribute=True, cli_name='mx_rec', csv=True)
-option: Str('naptrrecord*', attribute=True, cli_name='naptr_rec', csv=True)
-option: Str('nsrecord*', attribute=True, cli_name='ns_rec', csv=True)
-option: Str('nsecrecord*', attribute=True, cli_name='nsec_rec', csv=True)
-option: Str('nsec3record*', attribute=True, cli_name='nsec3_rec', csv=True)
-option: Str('nsec3paramrecord*', attribute=True, cli_name='nsec3param_rec', csv=True)
-option: Str('ptrrecord*', attribute=True, cli_name='ptr_rec', csv=True)
-option: Str('rrsigrecord*', attribute=True, cli_name='rrsig_rec', csv=True)
-option: Str('rprecord*', attribute=True, cli_name='rp_rec', csv=True)
-option: Str('sigrecord*', attribute=True, cli_name='sig_rec', csv=True)
-option: Str('spfrecord*', attribute=True, cli_name='spf_rec', csv=True)
-option: Str('srvrecord*', attribute=True, cli_name='srv_rec', csv=True)
-option: Str('sshfprecord*', attribute=True, cli_name='sshfp_rec', csv=True)
-option: Str('tarecord*', attribute=True, cli_name='ta_rec', csv=True)
-option: Str('tkeyrecord*', attribute=True, cli_name='tkey_rec', csv=True)
-option: Str('tsigrecord*', attribute=True, cli_name='tsig_rec', csv=True)
-option: Str('txtrecord*', attribute=True, cli_name='txt_rec', csv=True)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None)
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index d4f91af74..33856c276 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -1,6 +1,6 @@
# Authors:
-# Pavel Zuna <pzuna@redhat.com>
# Martin Kosek <mkosek@redhat.com>
+# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2010 Red Hat
# see file 'COPYING' for use and warranty information
@@ -22,9 +22,10 @@ import netaddr
import time
import re
+from ipalib.request import context
from ipalib import api, errors, output
from ipalib import Command
-from ipalib import Flag, Bool, Int, Str, StrEnum
+from ipalib.parameters import Flag, Bool, Int, Float, Str, StrEnum
from ipalib.plugins.baseldap import *
from ipalib import _, ngettext
from ipalib.util import validate_zonemgr, normalize_zonemgr, validate_hostname
@@ -166,13 +167,24 @@ def _reverse_zone_name(netstr):
else:
return None
-def _validate_ipaddr(ugettext, ipaddr):
+def _validate_ipaddr(ugettext, ipaddr, ip_version=None):
try:
ip = netaddr.IPAddress(ipaddr)
+
+ if ip_version is not None:
+ if ip.version != ip_version:
+ return _('invalid IP address version (is %(value)d, must be %(required_value)d)!') \
+ % dict(value=ip.version, required_value=ip_version)
except (netaddr.AddrFormatError, ValueError):
return _('invalid IP address format')
return None
+def _validate_ip4addr(ugettext, ipaddr):
+ return _validate_ipaddr(ugettext, ipaddr, 4)
+
+def _validate_ip6addr(ugettext, ipaddr):
+ return _validate_ipaddr(ugettext, ipaddr, 6)
+
def _validate_ipnet(ugettext, ipnet):
try:
net = netaddr.IPNetwork(ipnet)
@@ -180,191 +192,401 @@ def _validate_ipnet(ugettext, ipnet):
return _('invalid IP network format')
return None
-def _validate_srv(ugettext, srv):
- """see RFC 2782"""
- try:
- prio, weight, port, host = srv.split()
- except ValueError:
- return _('format must be specified as "priority weight port target"')
-
- try:
- prio = int(prio)
- weight = int(weight)
- port = int(port)
- except ValueError:
- return _('format must be specified as "priority weight port target" '\
- '(see RFC 2782 for details)')
-
- return None
-
-def _validate_mx(ugettext, mx):
- """see RFC 1035"""
- try:
- prio, host = mx.split()
- except ValueError:
- return _('format must be specified as "priority mailserver" '\
- '(see RFC 1035 for details)')
-
- try:
- prio = int(prio)
- except ValueError:
- return _('the value of priority must be integer')
-
- if prio < 0 or prio > 65535:
- return _('the value of priority must be between 0 and 65535')
-
- return None
-
-def _validate_naptr(ugettext, naptr):
- """see RFC 2915"""
+def _domain_name_validator(ugettext, value):
try:
- order, pref, flags, svc, regexp, replacement = naptr.split()
- except ValueError:
- return _('format must be specified as "order preference flags service '\
- 'regexp replacement" (see RFC 2915 for details)')
-
- try:
- order = int(order)
- pref = int(pref)
- except ValueError:
- return _('order and preference must be integers')
-
- if order < 0 or order > 65535 or pref < 0 or pref > 65535:
- return _('the value of order and preference must be between 0 and 65535')
-
- flags = flags.replace('"','')
- flags = flags.replace('\'','')
- if len(flags) != 1:
- return _('flag must be a single character (quotation is allowed)')
- if flags.upper() not in "SAUP":
- return _('flag must be one of "S", "A", "U", or "P"')
+ # Allow domain name which is not fully qualified. These are supported
+ # in bind and then translated as <non-fqdn-name>.<domain>.
+ validate_hostname(value, check_fqdn=False)
+ except ValueError, e:
+ return _('invalid domain-name: %s') \
+ % unicode(e)
return None
-def _validate_afsdb(ugettext, afsdb):
- """see RFC 1183"""
- try:
- sub, host = afsdb.split()
- except ValueError:
- return _('format must be specified as "subtype hostname" (see RFC 1183 for details)')
-
+def _hostname_validator(ugettext, value):
try:
- sub = int(sub)
- except ValueError:
- return _('the value of subtype must be integer')
-
- if sub < 0 or sub > 65535:
- return _('the value of subtype must be between 0 and 65535')
+ validate_hostname(value)
+ except ValueError, e:
+ return _('invalid domain-name: %s') \
+ % unicode(e)
return None
-def _validate_cert(ugettext, cert):
- """see RFC 4398"""
- try:
- cert_type, key_tag, algorithm, certificate = cert.split()
- except ValueError:
- return _('format must be specified as "type key_tag algorithm certificate_or_crl" '\
- '(see RFC 4398 for details)')
+def _normalize_hostname(domain_name):
+ """Make it fully-qualified"""
+ if domain_name[-1] != '.':
+ return domain_name + '.'
+ else:
+ return domain_name
- try:
- cert_type = int(cert_type)
- key_tag = int(key_tag)
- algorithm = int(algorithm)
- except ValueError:
- return _('key_tag, algorithm and digest_type must be integers')
+class DNSRecord(Str):
+ parts = None
+ supported = True
+ # supported RR types: https://fedorahosted.org/bind-dyndb-ldap/browser/doc/schema
+
+ label_format = _("%s record")
+ part_label_format = "%s %s"
+ doc_format = _('Comma-separated list of raw %s records')
+ option_group_format = _('%s Record')
+ see_rfc_msg = _("(see RFC %s for details)")
+ part_name_format = "%s_part_%s"
+ cli_name_format = "%s_%s"
+ format_error_msg = None
+
+ kwargs = Str.kwargs + (
+ ('validatedns', bool, True),
+ ('normalizedns', bool, True),
+ )
- if cert_type < 0 or cert_type > 65535 or key_tag < 0 or key_tag > 65535:
- return _('the value of type and key_tag must be between 0 and 65535')
+ # should be replaced in subclasses
+ rrtype = None
+ rfc = None
+
+ def __init__(self, name=None, *rules, **kw):
+ if self.rrtype not in _record_types:
+ raise ValueError("Unknown RR type: %s. Must be one of %s" % \
+ (str(self.rrtype), ", ".join(_record_types)))
+ if not name:
+ name = "%srecord*" % self.rrtype.lower()
+ kw.setdefault('cli_name', '%s_rec' % self.rrtype.lower())
+ kw.setdefault('label', self.label_format % self.rrtype)
+ kw.setdefault('doc', self.doc_format % self.rrtype)
+ kw['csv'] = True
+
+ if not self.supported:
+ kw['flags'] = ('no_option',)
+
+ super(DNSRecord, self).__init__(name, *rules, **kw)
+
+ def _get_part_values(self, value):
+ values = value.split()
+ if len(values) != len(self.parts):
+ return None
+ return tuple(values)
+
+ def _convert_scalar(self, value, index=None):
+ if isinstance(value, (tuple, list)):
+ # convert parsed values to the string
+ if len(value) != len(self.parts):
+ raise errors.ConversionError(name=self.name, index=index,
+ error=_("Invalid number of parts!"))
+ return u" ".join(super(DNSRecord, self)._convert_scalar(v, index) \
+ for v in value if v is not None)
+ return super(DNSRecord, self)._convert_scalar(value, index)
+
+ def normalize(self, value):
+ if self.normalizedns: #pylint: disable=E1101
+ if isinstance(value, (tuple, list)):
+ value = tuple(
+ self._normalize_parts(v) for v in value \
+ if v is not None
+ )
+ elif value is not None:
+ value = (self._normalize_parts(value),)
+
+ return super(DNSRecord, self).normalize(value)
+
+ def _normalize_parts(self, value):
+ """
+ Normalize a DNS record value using normalizers for its parts.
+ """
+ if self.parts is None:
+ return value
+ try:
+ values = self._get_part_values(value)
+ if not values:
+ return value
- if algorithm < 0 or algorithm > 255:
- return _('the value of algorithm must be between 0 and 255')
+ new_values = [ part.normalize(values[part_id]) \
+ for part_id, part in enumerate(self.parts) ]
- return None
+ value = self._convert_scalar(new_values)
+ except Exception:
+ # cannot normalize, rather return original value than fail
+ pass
+ return value
-def _validate_cname(ugettext, cname):
- """see RFC 1035"""
- try:
- validate_hostname(cname)
- except ValueError, e:
- return _('format must be specified as "domain_name" (see RFC 1035 for details): %s') \
- % unicode(e)
+ def _rule_validatedns(self, _, value):
+ if not self.validatedns: #pylint: disable=E1101
+ return
- return None
+ if value is None:
+ return
-def _validate_dname(ugettext, dname):
- """see RFC 2672"""
- try:
- validate_hostname(dname)
- except ValueError, e:
- return _('format must be specified as "target" (see RFC 2672 for details): %s') \
- % unicode(e)
+ if not self.supported:
+ return _('DNS RR type "%s" is not supported by bind-dyndb-ldap plugin') \
+ % self.rrtype
- return None
+ if self.parts is None:
+ return
-def _validate_ds(ugettext, ds):
- """see RFC 4034"""
- try:
- key_tag, algorithm, digest_type, digest = ds.split()
- except ValueError:
- return _('format must be specified as "key_tag algorithm digest_type digest" '\
- '(see RFC 4034 for details)')
+ # validate record format
+ values = self._get_part_values(value)
+ if not values:
+ if not self.format_error_msg:
+ part_names = [part.name.upper() for part in self.parts]
+
+ if self.rfc:
+ see_rfc_msg = " " + self.see_rfc_msg % self.rfc
+ else:
+ see_rfc_msg = ""
+ return _('format must be specified as "%(format)s" %(rfcs)s') \
+ % dict(format=" ".join(part_names), rfcs=see_rfc_msg)
+ else:
+ return self.format_error_msg
+
+ # validate every part
+ for part_id, part in enumerate(self.parts):
+ val = part.normalize(values[part_id])
+ val = part.convert(val)
+ part.validate(val)
+ return None
- try:
- key_tag = int(key_tag)
- algorithm = int(algorithm)
- digest_type = int(digest_type)
- except ValueError:
- return _('key_tag, algorithm and digest_type must be integers')
+class ARecord(DNSRecord):
+ rrtype = 'A'
+ rfc = 1035
+ parts = (
+ Str('ip_address',
+ _validate_ip4addr,
+ label=_('IP Address'),
+ ),
+ )
- if key_tag < 0 or key_tag > 65535:
- return _('the value of flags must be between 0 and 65535')
+class A6Record(DNSRecord):
+ rrtype = 'A6'
+ rfc = 3226
+ parts = None # experimental rr type
+
+class AAAARecord(DNSRecord):
+ rrtype = 'AAAA'
+ rfc = 3596
+ parts = (
+ Str('ip_address',
+ _validate_ip6addr,
+ label=_('IP Address'),
+ ),
+ )
- if algorithm < 0 or algorithm > 255 or digest_type < 0 or digest_type > 255:
- return _('the value of algorithm and digest_type must be between 0 and 255')
+class AFSDBRecord(DNSRecord):
+ rrtype = 'AFSDB'
+ rfc = 1183
+ parts = (
+ Int('subtype?',
+ label=_('Subtype'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Str('hostname',
+ _domain_name_validator,
+ label=_('Hostname'),
+ ),
+ )
- return None
+class APLRecord(DNSRecord):
+ rrtype = 'APL'
+ rfc = 3123
+ supported = False
+
+class CERTRecord(DNSRecord):
+ rrtype = 'CERT'
+ rfc = 4398
+ parts = (
+ Int('type',
+ label=_('Certificate Type'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('key_tag',
+ label=_('Key Tag'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('algorithm',
+ label=_('Algorithm'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Str('certificate_or_crl',
+ label=_('Certificate/CRL'),
+ ),
+ )
-def _validate_key(ugettext, key):
- """see RFC 2535"""
- try:
- flags, protocol, algorithm, digest = key.split()
- except ValueError:
- return _('format must be specified as "flags protocol algorithm public_key" '\
- '(see RFC 2535 for details)')
+class CNAMERecord(DNSRecord):
+ rrtype = 'CNAME'
+ rfc = 1035
+ parts = (
+ Str('hostname',
+ _domain_name_validator,
+ label=_('Hostname'),
+ doc=_('A hostname which this alias hostname points to'),
+ ),
+ )
- try:
- flags = int(flags)
- protocol = int(protocol)
- algorithm = int(algorithm)
- except ValueError:
- return _('flags, protocol and algorithm must be integers')
+class DHCIDRecord(DNSRecord):
+ rrtype = 'DHCID'
+ rfc = 4701
+ supported = False
+
+class DLVRecord(DNSRecord):
+ rrtype = 'DLV'
+ rfc = 4431
+ supported = False
+
+class DNAMERecord(DNSRecord):
+ rrtype = 'DNAME'
+ rfc = 2672
+ parts = (
+ Str('target',
+ _domain_name_validator,
+ label=_('Target'),
+ ),
+ )
- if flags < 0 or flags > 65535:
- return _('the value of flags must be between 0 and 65535')
+class DNSKEYRecord(DNSRecord):
+ rrtype = 'DNSKEY'
+ rfc = 4034
+ supported = False
+
+class DSRecord(DNSRecord):
+ rrtype = 'DS'
+ rfc = 4034
+ parts = (
+ Int('key_tag',
+ label=_('Key Tag'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('algorithm',
+ label=_('Algorithm'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Int('digest_type',
+ label=_('Digest Type'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Str('digest',
+ label=_('Digest'),
+ ),
+ )
- if protocol < 0 or protocol > 255:
- return _('the value of protocol must be between 0 and 255')
+class HIPRecord(DNSRecord):
+ rrtype = 'HIP'
+ rfc = 5205
+ supported = False
+
+class KEYRecord(DNSRecord):
+ rrtype = 'KEY'
+ rfc = 2535
+ parts = (
+ Int('flags',
+ label=_('Flags'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('protocol',
+ label=_('Protocol'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Int('algorithm',
+ label=_('Algorithm'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Str('public_key',
+ label=_('Public Key'),
+ ),
+ )
- if algorithm < 0 or algorithm > 255:
- return _('the value of algorithm must be between 0 and 255')
+class IPSECKEYRecord(DNSRecord):
+ rrtype = 'IPSECKEY'
+ rfc = 4025
+ supported = False
+
+class KXRecord(DNSRecord):
+ rrtype = 'KX'
+ rfc = 2230
+ parts = (
+ Int('preference',
+ label=_('Preference'),
+ doc=_('Preference given to this exchanger. Lower values are more preferred'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Str('exchanger',
+ _domain_name_validator,
+ label=_('Exchanger'),
+ doc=_('A host willing to act as a key exchanger'),
+ ),
+ )
- return None
+class LOCRecord(DNSRecord):
+ rrtype = 'LOC'
+ rfc = 1876
+ parts = (
+ Int('lat_deg',
+ label=_('Degrees Latitude'),
+ minvalue=0,
+ maxvalue=90,
+ ),
+ Int('lat_min?',
+ label=_('Minutes Latitude'),
+ minvalue=0,
+ maxvalue=59,
+ ),
+ Float('lat_sec?',
+ label=_('Seconds Latitude'),
+ minvalue=0.0,
+ maxvalue=59.999,
+ ),
+ StrEnum('lat_dir',
+ label=_('Direction Latitude'),
+ values=(u'N', u'S',),
+ ),
+ Int('lon_deg',
+ label=_('Degrees Longtitude'),
+ minvalue=0,
+ maxvalue=180,
+ ),
+ Int('lon_min?',
+ label=_('Minutes Longtitude'),
+ minvalue=0,
+ maxvalue=59,
+ ),
+ Float('lon_sec?',
+ label=_('Seconds Longtitude'),
+ minvalue=0.0,
+ maxvalue=59.999,
+ ),
+ StrEnum('lon_dir',
+ label=_('Direction Longtitude'),
+ values=(u'E', u'W',),
+ ),
+ Float('altitude',
+ label=_('Altitude'),
+ minvalue=-100000.00,
+ maxvalue=42849672.95,
+ ),
+ Float('size?',
+ label=_('Size'),
+ minvalue=0.0,
+ maxvalue=90000000.00,
+ ),
+ Float('h_precision?',
+ label=_('Horizontal Precision'),
+ minvalue=0.0,
+ maxvalue=90000000.00,
+ ),
+ Float('v_precision?',
+ label=_('Vertical Precision'),
+ minvalue=0.0,
+ maxvalue=90000000.00,
+ ),
+ )
-def _validate_loc(ugettext, loc):
- """see RFC 1876"""
- regex = re.compile(\
- r'(?P<d1>\d{1,2}\s+)(?P<m1>\d{1,2}\s+)?(?P<s1>\d{1,2}\.?\d{1,3}?\s+)'\
- r'?[N|S]\s+'\
- r'(?P<d2>\d{1,2}\s+)(?P<m2>\d{1,2}\s+)?(?P<s2>\d{1,2}\.?\d{1,3}?\s+)'\
- r'?[W|E]\s+'\
- r'(?P<alt>-?\d{1,8}\.?\d{1,2}?)m?\s*'\
- r'(?P<siz>\d{1,8}\.?\d{1,2}?)?m?\s*'\
- r'(?P<hp>\d{1,8}\.?\d{1,2}?)?m?\s*(?P<vp>\d{1,8}\.?\d{1,2}?)?m?\s*$')
-
- m = regex.match(loc)
-
- if m is None:
- return _("""format must be specified as
+ format_error_msg = _("""format must be specified as
"d1 [m1 [s1]] {"N"|"S"} d2 [m2 [s2]] {"E"|"W"} alt["m"] [siz["m"] [hp["m"] [vp["m"]]]]"
where:
d1: [0 .. 90] (degrees latitude)
@@ -375,208 +597,308 @@ def _validate_loc(ugettext, loc):
siz, hp, vp: [0 .. 90000000.00] (size/precision in meters)
See RFC 1876 for details""")
- attrs = {}
- for attr in ('d1', 'd2', 'm1', 'm2'):
- if m.group(attr) is not None:
- try:
- attrs[attr] = int(m.group(attr))
- except ValueError:
- return _('%s must be integer') % attr
-
- for attr in ('s1', 's2', 'alt', 'siz', 'hp', 'vp'):
- if m.group(attr) is not None:
- try:
- attrs[attr] = float(m.group(attr))
- except ValueError:
- return _('%s must be float') % attr
-
- if attrs.get('d1', 0) > 90 or attrs.get('d2', 0) > 90:
- return _(u'd1 and d2 must be between 0 and 90')
-
- if attrs.get('m1', 0) >= 60 or attrs.get('m2', 0) >= 60 or \
- attrs.get('s1', 0) >= 60 or attrs.get('s2', 0) >= 60:
- return _('m1, m2, s1 and s2 must be between 0 and 59.999')
-
- if attrs.get('alt', 0) < -100000.00 or attrs.get('alt', 0) > 42849672.95:
- return _('alt must be between -100000.00 and 42849672.95')
-
- if attrs.get('siz', 0) > 90000000.00 or attrs.get('hp', 0) > 90000000.00 or \
- attrs.get('vp', 0) > 90000000.00:
- return _('siz, hp and vp must be between 0 and 90000000.00')
-
- return None
+ def _get_part_values(self, value):
+ regex = re.compile(\
+ r'(?P<d1>\d{1,2}\s+)(?P<m1>\d{1,2}\s+)?(?P<s1>\d{1,2}\.?\d{1,3}?\s+)?'\
+ r'(?P<dir1>[N|S])\s+'\
+ r'(?P<d2>\d{1,3}\s+)(?P<m2>\d{1,2}\s+)?(?P<s2>\d{1,2}\.?\d{1,3}?\s+)?'\
+ r'(?P<dir2>[W|E])\s+'\
+ r'(?P<alt>-?\d{1,8}\.?\d{1,2}?)m?\s*'\
+ r'(?P<siz>\d{1,8}\.?\d{1,2}?)?m?\s*'\
+ r'(?P<hp>\d{1,8}\.?\d{1,2}?)?m?\s*(?P<vp>\d{1,8}\.?\d{1,2}?)?m?\s*$')
+
+ m = regex.match(value)
+
+ if m is None:
+ return None
+
+ return tuple(x.strip() if x is not None else x for x in m.groups())
+
+class MXRecord(DNSRecord):
+ rrtype = 'MX'
+ rfc = 1035
+ parts = (
+ Int('preference',
+ label=_('Preference'),
+ doc=_('Preference given to this exchanger. Lower values are more preferred'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Str('exchanger',
+ _domain_name_validator,
+ label=_('Exchanger'),
+ doc=_('A host willing to act as a mail exchanger'),
+ ),
+ )
-def _validate_ns(ugettext, ns):
- """see RFC 1035"""
- try:
- ns, = ns.split()
- except ValueError:
- return _('format must be specified as "domain_name" (see RFC 1035 for details)')
+class NSRecord(DNSRecord):
+ rrtype = 'NS'
+ rfc = 1035
- return None
+ parts = (
+ Str('hostname',
+ _domain_name_validator,
+ label=_('Hostname'),
+ ),
+ )
-def _validate_nsec(ugettext, nsec):
- """see RFC 4034"""
- fields = nsec.split()
+class NSECRecord(DNSRecord):
+ rrtype = 'NSEC'
+ rfc = 4034
+ format_error_msg = _('format must be specified as "NEXT TYPE1 '\
+ '[TYPE2 [TYPE3 [...]]]" (see RFC 4034 for details)')
+ _allowed_types = (u'SOA',) + _record_types
+
+ parts = (
+ Str('next',
+ _domain_name_validator,
+ label=_('Next Domain Name'),
+ ),
+ StrEnum('types',
+ label=_('Type Map'),
+ multivalue=True,
+ values=_allowed_types,
+ ),
+ )
- if len(fields) < 2:
- return _('format must be specified as "next_domain_name type1 '\
- '[type2 [type3 [...]]]" (see RFC 4034 for details)')
+ def _get_part_values(self, value):
+ values = value.split()
- allowed_types = (u'SOA',) + _record_types
- for i in range(1, len(fields)):
- sig_type = fields[i]
- if sig_type not in allowed_types:
- return _('type must be one of ' + u', '.join(allowed_types))
+ if len(values) < 2:
+ return None
- return None
+ return (values[0], tuple(values[1:]))
-def _validate_kx(ugettext, kx):
- """see RFC 2230"""
- try:
- preference, exchanger = kx.split()
- except ValueError:
- return _('format must be specified as "preference exchanger" '\
- '(see RFC 2230 for details)')
+class NSEC3Record(DNSRecord):
+ rrtype = 'NSEC3'
+ rfc = 5155
+ supported = False
- try:
- preference = int(preference)
- except ValueError:
- return _(u'the value of preference must be integer')
+class NSEC3PARAMRecord(DNSRecord):
+ rrtype = 'NSEC3PARAM'
+ rfc = 5155
+ supported = False
- if preference < 0 or preference > 65535:
- return _('the value of preference must be between 0 and 65535')
+def _validate_naptr_flags(ugettext, flags):
+ allowed_flags = u'SAUP'
+ flags = flags.replace('"','').replace('\'','')
- return None
+ for flag in flags:
+ if flag not in allowed_flags:
+ return _('flags must be one of "S", "A", "U", or "P"')
-def _validate_ptr(ugettext, ptr):
- """see RFC 1035"""
- try:
- validate_hostname(ptr)
- except ValueError, e:
- return _('format must be specified as "domain_name" (see RFC 1035 for details): %s') \
- % unicode(e)
+class NAPTRRecord(DNSRecord):
+ rrtype = 'NAPTR'
+ rfc = 2915
- return None
+ parts = (
+ Int('order',
+ label=_('Order'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('preference',
+ label=_('Preference'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Str('flags',
+ _validate_naptr_flags,
+ label=_('Flags'),
+ normalizer=lambda x:x.upper()
+ ),
+ Str('service',
+ label=_('Service'),
+ ),
+ Str('regexp',
+ label=_('Regular Expression'),
+ ),
+ Str('replacement',
+ label=_('Replacement'),
+ ),
+ )
-def _validate_sig(ugettext, sig):
- """see RFCs 2535, 4034"""
- try:
- sig_type, algorithm, labels, ttl, sig_expiration, \
- sig_inception, tag, signer, signature = sig.split()
- except ValueError:
- return _('format must be specified as "type_covered algorithm labels original_ttl ' \
- 'signature_expiration signature_inception key_tag signers_name signature" '\
- '(see RFC 2535, 4034 for details)')
+class PTRRecord(DNSRecord):
+ rrtype = 'PTR'
+ rfc = 1035
+ parts = (
+ Str('hostname',
+ _hostname_validator,
+ normalizer=_normalize_hostname,
+ label=_('Hostname'),
+ doc=_('The hostname this reverse record points to'),
+ ),
+ )
- allowed_types = [x for x in _record_types if x != u'SIG']
- if sig_type not in allowed_types:
- return _('type_covered must be one of ' + u', '.join(allowed_types))
+class RPRecord(DNSRecord):
+ rrtype = 'RP'
+ rfc = 1183
+ supported = False
+
+class SRVRecord(DNSRecord):
+ rrtype = 'SRV'
+ rfc = 2782
+ parts = (
+ Int('priority',
+ label=_('Priority'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('weight',
+ label=_('Weight'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Int('port',
+ label=_('Port'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Str('target',
+ label=_('Target'),
+ doc=_('The domain name of the target host or \'.\' if the service is decidedly not available at this domain'),
+ ),
+ )
+def _sig_time_validator(ugettext, value):
+ time_format = "%Y%m%d%H%M%S"
try:
- algorithm = int(algorithm)
- labels = int(labels)
- ttl = int(ttl)
- tag = int(tag)
+ time.strptime(value, time_format)
except ValueError:
- return _('algorithm, labels, original_ttl and key_tag must be integers')
-
- try:
- time_format = "%Y%m%d%H%M%S"
- sig_inception = time.strptime(sig_inception, time_format)
- sig_expiration = time.strptime(sig_expiration, time_format)
- except ValueError, e:
- return _('signature_expiration and signature_inception must follow time ' \
- 'format "YYYYMMDDHHMMSS"')
-
- if algorithm < 0 or algorithm > 255 or labels < 0 or labels > 255:
- return _('the value of algorithm and labels must be between 0 and 255')
+ return _('the value does not follow "YYYYMMDDHHMMSS" time format')
- if ttl < 0 or ttl > 4294967295:
- return _('the value of original_ttl must be between 0 and 4294967295')
- if tag < 0 or tag > 65535:
- return _('the value of tag must be between 0 and 65535')
+class SIGRecord(DNSRecord):
+ rrtype = 'SIG'
+ rfc = 2535
+ _allowed_types = tuple([u'SOA'] + [x for x in _record_types if x != u'SIG'])
- return None
-
-def _validate_sshfp(ugettext, sshfp):
- """see RFCs 4255"""
- try:
- algorithm, fp_type, fingerprint = sshfp.split()
- except ValueError:
- return _('format must be specified as "algorithm fp_type fingerprint" '\
- '(see RFC 4255 for details)')
-
- try:
- algorithm = int(algorithm)
- fp_type = int(fp_type)
- except ValueError:
- return _('algorithm and fp_type must be integers')
-
- if algorithm < 0 or algorithm > 255 or fp_type < 0 or fp_type > 255:
- return _('the value of algorithm and fp_type must be between 0 and 255')
+ parts = (
+ StrEnum('type_covered',
+ label=_('Type Covered'),
+ values=_allowed_types,
+ ),
+ Int('algorithm',
+ label=_('Algorithm'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Int('labels',
+ label=_('Labels'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Int('original_ttl',
+ label=_('Original TTL'),
+ minvalue=0,
+ maxvalue=4294967295,
+ ),
+ Str('signature_expiration',
+ _sig_time_validator,
+ label=_('Signature Expiration'),
+ ),
+ Str('signature_inception',
+ _sig_time_validator,
+ label=_('Signature Inception'),
+ ),
+ Int('key_tag',
+ label=_('Key Tag'),
+ minvalue=0,
+ maxvalue=65535,
+ ),
+ Str('signers_name',
+ label=_('Signer\'s Name'),
+ ),
+ Str('signature',
+ label=_('Signature'),
+ ),
+ )
- return None
+class SPFRecord(DNSRecord):
+ rrtype = 'SPF'
+ rfc = 4408
+ supported = False
+
+class RRSIGRecord(SIGRecord):
+ rrtype = 'RRSIG'
+ rfc = 4034
+
+class SSHFPRecord(DNSRecord):
+ rrtype = 'SSHFP'
+ rfc = 4255
+ parts = (
+ Int('algorithm',
+ label=_('Algorithm'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Int('fp_type',
+ label=_('Fingerprint Type'),
+ minvalue=0,
+ maxvalue=255,
+ ),
+ Str('fingerprint',
+ label=_('Fingerprint'),
+ ),
+ )
-def _validate_unsupported(ugettext, val):
- """
- See https://fedorahosted.org/bind-dyndb-ldap/browser/doc/schema for a
- list of supported records in bind-dyndb-ldap plugin
- """
- return _('This DNS RR type is not supported by bind-dyndb-ldap plugin')
+class TARecord(DNSRecord):
+ rrtype = 'TA'
+ supported = False
+class TKEYRecord(DNSRecord):
+ rrtype = 'TKEY'
+ supported = False
-def _normalize_domain_name(domain_name):
- """Make it fully-qualified"""
- if domain_name[-1] != '.':
- return domain_name + '.'
- else:
- return domain_name
+class TSIGRecord(DNSRecord):
+ rrtype = 'TSIG'
+ supported = False
-# Not validated RR types:
-# - A6: downgraded to experimental state by RFC 3363, AAAA is preferred
-
-_record_validators = {
- u'A': _validate_ipaddr,
- u'AAAA': _validate_ipaddr,
- u'AFSDB': _validate_afsdb,
- u'APL': _validate_unsupported,
- u'CERT': _validate_cert,
- u'CNAME': _validate_cname,
- u'DHCID': _validate_unsupported,
- u'DLV': _validate_unsupported,
- u'DNAME': _validate_dname,
- u'DNSKEY': _validate_unsupported,
- u'DS': _validate_ds,
- u'HIP': _validate_unsupported,
- u'KEY': _validate_key,
- u'IPSECKEY': _validate_unsupported,
- u'KX': _validate_kx,
- u'LOC': _validate_loc,
- u'MX': _validate_mx,
- u'NS': _validate_ns,
- u'NSEC': _validate_nsec,
- u'NSEC3': _validate_unsupported,
- u'NSEC3PARAM': _validate_unsupported,
- u'NAPTR': _validate_naptr,
- u'PTR': _validate_ptr,
- u'RP': _validate_unsupported,
- u'SRV': _validate_srv,
- u'SIG': _validate_sig,
- u'RRSIG': _validate_sig,
- u'SSHFP': _validate_sshfp,
- u'TA': _validate_unsupported,
- u'TKEY': _validate_unsupported,
- u'TSIG': _validate_unsupported,
-}
+class TXTRecord(DNSRecord):
+ rrtype = 'TXT'
+ rfc = 1035
+ parts = (
+ Str('data',
+ label=_('Text Data'),
+ ),
+ )
-_record_normalizers = {
- u'CNAME': _normalize_domain_name,
- u'DNAME': _normalize_domain_name,
- u'NS': _normalize_domain_name,
- u'PTR': _normalize_domain_name,
-}
+_dns_record_options = (
+ ARecord(),
+ AAAARecord(),
+ A6Record(),
+ AFSDBRecord(),
+ APLRecord(),
+ CERTRecord(),
+ CNAMERecord(),
+ DHCIDRecord(),
+ DLVRecord(),
+ DNAMERecord(),
+ DNSKEYRecord(),
+ DSRecord(),
+ HIPRecord(),
+ IPSECKEYRecord(),
+ KEYRecord(),
+ KXRecord(),
+ LOCRecord(),
+ MXRecord(),
+ NAPTRRecord(),
+ NSRecord(),
+ NSECRecord(),
+ NSEC3Record(),
+ NSEC3PARAMRecord(),
+ PTRRecord(),
+ RRSIGRecord(),
+ RPRecord(),
+ SIGRecord(),
+ SPFRecord(),
+ SRVRecord(),
+ SSHFPRecord(),
+ TARecord(),
+ TKEYRecord(),
+ TSIGRecord(),
+ TXTRecord(),
+)
# dictionary of valid reverse zone -> number of address components
_valid_reverse_zones = {
@@ -591,18 +913,6 @@ def zone_is_reverse(zone_name):
return False
-
-def has_cli_options(entry, no_option_msg, allow_empty_attrs=False):
- entry = dict((t, entry.get(t, [])) for t in _record_attributes)
- if allow_empty_attrs:
- numattr = len(entry)
- else:
- numattr = reduce(lambda x,y: x+y,
- map(lambda x: len(x), [ v for v in entry.values() if v is not None ]))
- if numattr == 0:
- raise errors.OptionError(no_option_msg)
- return entry
-
def is_ns_rec_resolvable(name):
try:
return api.Command['dns_resolve'](name)
@@ -841,7 +1151,7 @@ class dnszone_find(LDAPSearch):
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)
+ return super(dnszone_find, self).args_options_2_entry(*args, **options)
takes_options = LDAPSearch.takes_options + (
Flag('forward_only',
@@ -922,7 +1232,7 @@ class dnsrecord(LDAPObject):
object_name = _('DNS resource record')
object_name_plural = _('DNS resource records')
object_class = ['top', 'idnsrecord']
- default_attributes = _record_attributes + ['idnsname']
+ default_attributes = ['idnsname'] + _record_attributes
label = _('DNS Resource Records')
label_singular = _('DNS Resource Record')
@@ -945,7 +1255,7 @@ class dnsrecord(LDAPObject):
doc=_('DNS class'),
values=_record_classes,
),
- )
+ ) + _dns_record_options
def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
if options.get('force', False):
@@ -974,8 +1284,8 @@ class dnsrecord(LDAPObject):
ip_addr_comp_count = len(addr.split('.')) + len(zone.split('.'))
if ip_addr_comp_count != zone_len:
raise errors.ValidationError(name='cn',
- error=unicode(_('Reverse zone %s requires exactly %d IP address components, %d given')
- % (zone_name, zone_len, ip_addr_comp_count)))
+ error=unicode(_('Reverse zone %(name)s requires exactly %(count)d IP address components, %(user_count)d given')
+ % dict(name=zone_name, count=zone_len, user_count=ip_addr_comp_count)))
return dn
@@ -1016,47 +1326,38 @@ class dnsrecord(LDAPObject):
return dns_masters
-api.register(dnsrecord)
-
-
-class dnsrecord_cmd_w_record_options(Command):
- """
- Base class for DNS record commands with record options.
- """
- record_param_doc = 'comma-separated list of %s records'
+ def has_cli_options(self, options, no_option_msg, allow_empty_attrs=False):
+ if any(k in options for k in ('setattr', 'addattr', 'delattr')):
+ return
- def get_record_options(self):
- for t in _record_types:
- t = t.encode('utf-8')
- yield self.get_record_option(t)
+ has_options = False
+ for attr in options.keys():
+ if attr in self.params and not self.params[attr].primary_key:
+ if options[attr] or allow_empty_attrs:
+ has_options = True
+ break
- def record_options_2_entry(self, **options):
- entries = dict((t, options.get(t, [])) for t in _record_attributes)
- entries.update(dict((k, []) for (k,v) in entries.iteritems() if v == None ))
- return entries
+ if not has_options:
+ raise errors.OptionError(no_option_msg)
def get_record_option(self, rec_type):
- doc = self.record_param_doc % rec_type
- validator = _record_validators.get(rec_type)
- normalizer = _record_normalizers.get(rec_type)
- if validator:
- return Str(
- '%srecord*' % rec_type.lower(), validator, normalizer=normalizer,
- cli_name='%s_rec' % rec_type.lower(), doc=doc,
- label='%s record' % rec_type, csv=True, attribute=True
- )
+ name = '%srecord' % rec_type.lower()
+ if name in self.params:
+ return self.params[name]
else:
- return Str(
- '%srecord*' % rec_type.lower(), cli_name='%s_rec' % rec_type.lower(),
- normalizer=normalizer, doc=doc, label='%s record' % rec_type,
- csv=True, attribute=True
- )
+ return None
+
+ def get_record_entry_attrs(self, entry_attrs):
+ return dict((attr, val) for attr,val in entry_attrs.iteritems() \
+ if attr in self.params and not self.params[attr].primary_key)
def prompt_record_options(self, rec_type_list):
user_options = {}
# ask for all usual record types
for rec_type in rec_type_list:
rec_option = self.get_record_option(rec_type)
+ if rec_option is None:
+ continue
raw = self.Backend.textui.prompt(rec_option.label,optional=True)
rec_value = rec_option(raw)
if rec_value is not None:
@@ -1064,88 +1365,10 @@ class dnsrecord_cmd_w_record_options(Command):
return user_options
-
-class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
- """
- Base class for adding/removing records from DNS resource entries.
- """
- has_output = output.standard_entry
-
- def get_options(self):
- for option in super(dnsrecord_mod_record, self).get_options():
- yield option
- for option in self.get_record_options():
- yield option
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
-
- entry_attrs = self.record_options_2_entry(**options)
-
- dn = self.pre_callback(ldap, dn, entry_attrs, *keys, **options)
-
- try:
- (dn, old_entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- self.update_old_entry_callback(entry_attrs, old_entry_attrs)
-
- try:
- ldap.update_entry(dn, old_entry_attrs)
- except errors.EmptyModlist:
- pass
-
- if options.get('all', False):
- attrs_list = ['*']
- else:
- attrs_list = list(
- set(self.obj.default_attributes + entry_attrs.keys())
- )
-
- try:
- (dn, entry_attrs) = ldap.get_entry(dn, attrs_list)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
-
- retval = self.post_callback(keys, entry_attrs)
- if retval:
- return retval
-
- return dict(result=entry_attrs, value=keys[-1])
-
- def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
- pass
-
- def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- return dn
-
- def post_callback(self, keys, entry_attrs):
- pass
-
-
-class dnsrecord_add_record(dnsrecord_mod_record):
- """
- Add records to DNS resource.
- """
- NO_CLI = True
-
- def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
- for (a, v) in entry_attrs.iteritems():
- if not isinstance(v, (list, tuple)):
- v = [v]
- old_entry_attrs.setdefault(a, [])
- old_entry_attrs[a] += v
-
-api.register(dnsrecord_add_record)
+api.register(dnsrecord)
-class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
+class dnsrecord_add(LDAPCreate):
__doc__ = _('Add new DNS resource record.')
no_option_msg = 'No options to add a specific record provided.\n' \
@@ -1158,21 +1381,18 @@ class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
),
)
- def get_options(self):
- for option in super(dnsrecord_add, self).get_options():
- yield option
- for option in self.get_record_options():
- yield option
-
def args_options_2_entry(self, *keys, **options):
- has_cli_options(options, self.no_option_msg)
+ self.obj.has_cli_options(options, self.no_option_msg)
return super(dnsrecord_add, self).args_options_2_entry(*keys, **options)
def interactive_prompt_callback(self, kw):
- for param in kw.keys():
- if param in _record_attributes:
- # some record type entered, skip this helper
- return
+ try:
+ self.obj.has_cli_options(kw, self.no_option_msg)
+ except errors.OptionError:
+ pass
+ else:
+ # some record type entered, skip this helper
+ return
# check zone type
if kw['idnsname'] == _dns_zone_record:
@@ -1183,45 +1403,55 @@ class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
top_record_types = _top_record_types
# ask for all usual record types
- user_options = self.prompt_record_options(top_record_types)
+ user_options = self.obj.prompt_record_options(top_record_types)
kw.update(user_options)
- def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
for rtype in options:
rtype_cb = '_%s_pre_callback' % rtype
if hasattr(self.obj, rtype_cb):
dn = getattr(self.obj, rtype_cb)(ldap, dn, entry_attrs, *keys, **options)
+ try:
+ (dn_, old_entry) = ldap.get_entry(
+ dn, entry_attrs.keys(),
+ normalize=self.obj.normalize_dn)
+ for attr in old_entry.keys():
+ if attr not in _record_attributes:
+ continue
+ if not isinstance(entry_attrs[attr], (tuple, list)):
+ vals = [entry_attrs[attr]]
+ else:
+ vals = list(entry_attrs[attr])
+ entry_attrs[attr] = list(set(old_entry[attr] + vals))
+ except errors.NotFound:
+ pass
return dn
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
if call_func.func_name == 'add_entry':
if isinstance(exc, errors.DuplicateEntry):
- self.obj.methods.add_record(
- *keys, **self.record_options_2_entry(**options)
- )
+ # A new record is being added to existing LDAP DNS object
+ # Update can be safely run as old record values has been
+ # already merged in pre_callback
+ ldap = self.obj.backend
+ dn = call_args[0]
+ entry_attrs = self.obj.get_record_entry_attrs(call_args[1])
+ ldap.update_entry(dn, entry_attrs, **call_kwargs)
return
raise exc
api.register(dnsrecord_add)
-class dnsrecord_mod(dnsrecord_mod_record):
+class dnsrecord_mod(LDAPUpdate):
__doc__ = _('Modify a DNS resource record.')
no_option_msg = 'No options to modify a specific record provided.'
- def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
- for (a, v) in entry_attrs.iteritems():
- if not isinstance(v, (list, tuple)):
- v = [v]
- old_entry_attrs.setdefault(a, [])
- if v or v is None: # overwrite the old entry
- old_entry_attrs[a] = v
-
- def record_options_2_entry(self, **options):
- entries = dict((t, options.get(t, [])) for t in _record_attributes)
- return has_cli_options(entries, self.no_option_msg, True)
+ def args_options_2_entry(self, *keys, **options):
+ self.obj.has_cli_options(options, self.no_option_msg, True)
+ return super(dnsrecord_mod, self).args_options_2_entry(*keys, **options)
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
for rtype in options:
@@ -1233,12 +1463,26 @@ class dnsrecord_mod(dnsrecord_mod_record):
return dn
- def post_callback(self, keys, entry_attrs):
+ def execute(self, *keys, **options):
+ result = super(dnsrecord_mod, self).execute(*keys, **options)
+
+ # remove if empty
if not self.obj.is_pkey_zone_record(*keys):
- for a in _record_attributes:
- if a in entry_attrs and entry_attrs[a]:
- return
- return self.obj.methods.delentry(*keys)
+ dn = self.obj.get_dn(*keys, **options)
+ ldap = self.obj.backend
+ (dn_, old_entry) = ldap.get_entry(
+ dn, _record_attributes,
+ normalize=self.obj.normalize_dn)
+
+ del_all = True
+ for attr in old_entry:
+ if old_entry[attr]:
+ del_all = False
+ break
+
+ if del_all:
+ return self.obj.methods.delentry(*keys)
+ return result
api.register(dnsrecord_mod)
@@ -1253,7 +1497,7 @@ class dnsrecord_delentry(LDAPDelete):
api.register(dnsrecord_delentry)
-class dnsrecord_del(dnsrecord_mod_record):
+class dnsrecord_del(LDAPUpdate):
__doc__ = _('Delete DNS resource record.')
no_option_msg = _('Neither --del-all nor options to delete a specific record provided.\n'\
@@ -1265,23 +1509,70 @@ class dnsrecord_del(dnsrecord_mod_record):
),
)
+ def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ try:
+ (dn_, old_entry) = ldap.get_entry(
+ dn, _record_attributes,
+ normalize=self.obj.normalize_dn)
+ except errors.NotFound:
+ self.obj.handle_not_found(*keys)
+
+ for attr in entry_attrs.keys():
+ if attr not in _record_attributes:
+ continue
+ if not isinstance(entry_attrs[attr], (tuple, list)):
+ vals = [entry_attrs[attr]]
+ else:
+ vals = entry_attrs[attr]
+
+ for val in vals:
+ try:
+ old_entry[attr].remove(val)
+ except (KeyError, ValueError):
+ raise errors.AttrValueNotFound(attr=attr,
+ value=val)
+ entry_attrs[attr] = list(set(old_entry[attr]))
+
+ if not self.obj.is_pkey_zone_record(*keys):
+ del_all = True
+ for attr in old_entry:
+ if old_entry[attr]:
+ del_all = False
+ break
+ setattr(context, 'del_all', del_all)
+
+ return dn
+
def execute(self, *keys, **options):
if options.get('del_all', False):
+ if self.obj.is_pkey_zone_record(*keys):
+ raise errors.ValidationError(
+ name='del_all',
+ error=_('Zone record \'%s\' cannot be deleted') \
+ % _dns_zone_record
+ )
return self.obj.methods.delentry(*keys)
- return super(dnsrecord_del, self).execute(*keys, **options)
+ result = super(dnsrecord_del, self).execute(*keys, **options)
+
+ if getattr(context, 'del_all', False):
+ return self.obj.methods.delentry(*keys)
+ return result
- def record_options_2_entry(self, **options):
- entry = super(dnsrecord_del, self).record_options_2_entry(**options)
- return has_cli_options(entry, self.no_option_msg)
+ def args_options_2_entry(self, *keys, **options):
+ self.obj.has_cli_options(options, self.no_option_msg)
+ return super(dnsrecord_del, self).args_options_2_entry(*keys, **options)
def interactive_prompt_callback(self, kw):
if kw.get('del_all', False):
return
- for param in kw.keys():
- if param in _record_attributes:
- # we have something to delete, skip this helper
- return
+ try:
+ self.obj.has_cli_options(kw, self.no_option_msg)
+ except errors.OptionError:
+ pass
+ else:
+ # some record type entered, skip this helper
+ return
# get DNS record first so that the NotFound exception is raised
# before the helper would start
@@ -1296,12 +1587,9 @@ class dnsrecord_del(dnsrecord_mod_record):
return
# ask user for records to be removed
- dns_record = api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
- rec_types = [rec_type for rec_type in dns_record if rec_type in _record_attributes]
-
self.Backend.textui.print_plain(_(u'Current DNS record contents:\n'))
present_params = []
- for param in self.params():
+ for param in self.params:
if param.name in _record_attributes and param.name in dns_record:
present_params.append(param)
rec_type_content = u', '.join(dns_record[param.name])
@@ -1313,41 +1601,20 @@ class dnsrecord_del(dnsrecord_mod_record):
deleted_values = []
for rec_value in dns_record[param.name]:
user_del_value = self.Backend.textui.prompt_yesno(
- _("Delete %s '%s'?") % (param.label, rec_value), default=False)
+ _("Delete %(name)s '%(value)s'?") \
+ % dict(name=param.label, value=rec_value), default=False)
if user_del_value is True:
deleted_values.append(rec_value)
if deleted_values:
deleted_list = u','.join(deleted_values)
kw[param.name] = param(deleted_list)
- def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
- for (a, v) in entry_attrs.iteritems():
- if not isinstance(v, (list, tuple)):
- v = [v]
- for val in v:
- try:
- old_entry_attrs[a].remove(val)
- except (KeyError, ValueError):
- raise errors.NotFound(reason=_('%s record with value %s not found') %
- (self.obj.attr_to_cli(a), val))
-
- def post_callback(self, keys, entry_attrs):
- if not self.obj.is_pkey_zone_record(*keys):
- for a in _record_attributes:
- if a in entry_attrs and entry_attrs[a]:
- return
- return self.obj.methods.delentry(*keys)
-
api.register(dnsrecord_del)
-class dnsrecord_show(LDAPRetrieve, dnsrecord_cmd_w_record_options):
+class dnsrecord_show(LDAPRetrieve):
__doc__ = _('Display DNS resource.')
- def has_output_params(self):
- for option in self.get_record_options():
- yield option
-
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
if self.obj.is_pkey_zone_record(*keys):
entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
@@ -1356,21 +1623,11 @@ class dnsrecord_show(LDAPRetrieve, dnsrecord_cmd_w_record_options):
api.register(dnsrecord_show)
-class dnsrecord_find(LDAPSearch, dnsrecord_cmd_w_record_options):
+class dnsrecord_find(LDAPSearch):
__doc__ = _('Search for DNS resources.')
- def get_options(self):
- for option in super(dnsrecord_find, self).get_options():
- yield option
- for option in self.get_record_options():
- yield option.clone(query=True)
-
def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
- record_attrs = self.record_options_2_entry(**options)
- record_filter = ldap.make_filter(record_attrs, rules=ldap.MATCH_ALL)
- filter = ldap.combine_filters(
- (filter, record_filter), rules=ldap.MATCH_ALL
- )
+ # include zone record (root entry) in the search
return (filter, base_dn, ldap.SCOPE_SUBTREE)
def post_callback(self, ldap, entries, truncated, *args, **options):
diff --git a/ipalib/util.py b/ipalib/util.py
index d575329e7..da933a86a 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -257,8 +257,12 @@ def validate_zonemgr(zonemgr):
if not all(regex_domain.match(part) for part in domain.split(".")):
raise ValueError(_('domain name may only include letters, numbers, and -'))
-def validate_hostname(hostname):
- """ See RFC 952, 1123"""
+def validate_hostname(hostname, check_fqdn=True):
+ """ See RFC 952, 1123
+
+ :param hostname Checked value
+ :param check_fqdn Check if hostname is fully qualified
+ """
regex_name = re.compile(r'^[a-z0-9]([a-z0-9-]?[a-z0-9])*$', re.IGNORECASE)
if len(hostname) > 255:
@@ -267,12 +271,12 @@ def validate_hostname(hostname):
if hostname.endswith('.'):
hostname = hostname[:-1]
- if '.' not in hostname:
- raise ValueError(_('hostname is not fully qualified'))
+ if check_fqdn and '.' not in hostname:
+ raise ValueError(_('not fully qualified'))
if not all(regex_name.match(part) for part in hostname.split(".")):
- raise ValueError(_('hostname parts may only include letters, numbers, and - ' \
- '(which is not allowed as the last character)'))
+ raise ValueError(_('only letters, numbers, and - are allowed. ' \
+ '- must not be the last name character'))
class cachedproperty(object):
"""
diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py
index 1d800f82a..00d4f9b7d 100644
--- a/tests/test_xmlrpc/test_dns_plugin.py
+++ b/tests/test_xmlrpc/test_dns_plugin.py
@@ -414,6 +414,13 @@ class test_dns(Declarative):
dict(
+ desc='Try to delete root zone record \'@\' in %r' % (dnszone1),
+ command=('dnsrecord_del', [dnszone1, u'@'], {'del_all' : True}),
+ expected=errors.ValidationError(name='del_all', error=''),
+ ),
+
+
+ dict(
desc='Create record %r in zone %r' % (dnszone1, dnsres1),
command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'127.0.0.1'}),
expected={
@@ -575,13 +582,13 @@ class test_dns(Declarative):
dict(
desc='Try to add invalid LOC record to zone %r using dnsrecord_add' % (dnszone1),
- command=('dnsrecord_add', [dnszone1, u'@'], {'locrecord': u"91 11 42.4 N 16 36 29.6 E 227.64m" }),
+ command=('dnsrecord_add', [dnszone1, u'@'], {'locrecord': u"91 11 42.4 N 16 36 29.6 E 227.64" }),
expected=errors.ValidationError(name='locrecord', error=''),
),
dict(
desc='Add LOC record to zone %r using dnsrecord_add' % (dnszone1),
- command=('dnsrecord_add', [dnszone1, u'@'], {'locrecord': u"49 11 42.4 N 16 36 29.6 E 227.64m" }),
+ command=('dnsrecord_add', [dnszone1, u'@'], {'locrecord': u"49 11 42.4 N 16 36 29.6 E 227.64" }),
expected={
'value': u'@',
'summary': None,
@@ -591,7 +598,7 @@ class test_dns(Declarative):
'idnsname': [dnszone1],
'mxrecord': [u"0 %s" % dnszone1_mname],
'nsrecord': [dnszone1_mname],
- 'locrecord': [u"49 11 42.4 N 16 36 29.6 E 227.64m"],
+ 'locrecord': [u"49 11 42.4 N 16 36 29.6 E 227.64"],
},
},
),
@@ -604,7 +611,7 @@ class test_dns(Declarative):
dict(
desc='Add CNAME record to %r using dnsrecord_add' % (dnsres1),
- command=('dnsrecord_add', [dnszone1, dnsres1], {'cnamerecord': u'foo-1.example.com' }),
+ command=('dnsrecord_add', [dnszone1, dnsres1], {'cnamerecord': u'foo-1.example.com.' }),
expected={
'value': dnsres1,
'summary': None,
@@ -626,7 +633,7 @@ class test_dns(Declarative):
dict(
desc='Add KX record to %r using dnsrecord_add' % (dnsres1),
- command=('dnsrecord_add', [dnszone1, dnsres1], {'kxrecord': u'1 foo-1.example.com' }),
+ command=('dnsrecord_add', [dnszone1, dnsres1], {'kxrecord': u'1 foo-1' }),
expected={
'value': dnsres1,
'summary': None,
@@ -636,7 +643,7 @@ class test_dns(Declarative):
'idnsname': [dnsres1],
'arecord': [u'10.10.0.1'],
'cnamerecord': [u'foo-1.example.com.'],
- 'kxrecord': [u'1 foo-1.example.com'],
+ 'kxrecord': [u'1 foo-1'],
},
},
),