diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index 287e1a9..74ac48d 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -1090,8 +1090,9 @@ class MxValue(EqualityTupleMixin): reasons.append(f'invalid preference "{value["preference"]}"') exchange = None try: - exchange = value.get('exchange', None) or value['value'] - if not FQDN(str(exchange), allow_underscores=True).is_valid: + exchange = str(value.get('exchange', None) or value['value']) + if exchange != '.' and \ + not FQDN(exchange, allow_underscores=True).is_valid: reasons.append(f'Invalid MX exchange "{exchange}" is not ' 'a valid FQDN.') elif not exchange.endswith('.'): diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index b01d91a..176c11d 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -2717,6 +2717,17 @@ class TestRecordValidation(TestCase): self.assertEqual(['Invalid MX exchange "100 foo.bar.com." is not a ' 'valid FQDN.'], ctx.exception.reasons) + # exchange can be a single `.` + record = Record.new(self.zone, '', { + 'type': 'MX', + 'ttl': 600, + 'value': { + 'preference': 0, + 'exchange': '.' + } + }) + self.assertEqual('.', record.values[0].exchange) + def test_NXPTR(self): # doesn't blow up Record.new(self.zone, '', {