diff --git a/octodns/record.py b/octodns/record.py index 6517d74..60a3686 100644 --- a/octodns/record.py +++ b/octodns/record.py @@ -406,6 +406,22 @@ class _DynamicPool(object): return '{}'.format(self.data) +class _DynamicRuleGeo(object): + geo_re = re.compile(r'^(?P\w\w)(-(?P\w\w)' + r'(-(?P\w\w))?)?$') + + @classmethod + def validate(cls, rule_num, code): + reasons = [] + # TODO: ideally this would validate the actual code... + match = cls.geo_re.match(code) + if not match: + reasons.append('rule {} invalid geo "{}"'.format(rule_num, code)) + return reasons + + # TODO: flesh this out + + class _DynamicRule(object): def __init__(self, i, data): @@ -517,6 +533,8 @@ class _DynamicMixin(object): elif not rules: reasons.append('missing rules') else: + seen_default = False + for rule_num, rule in enumerate(rules): rule_num += 1 try: @@ -532,7 +550,21 @@ class _DynamicMixin(object): reasons.append('rule {} undefined pool "{}"' .format(rule_num, pool)) - # TODO: validate GEOs if present + try: + geos = rule['geos'] + except KeyError: + geos = [] + if seen_default: + reasons.append('rule {} duplicate default' + .format(rule_num)) + seen_default = True + + if not isinstance(geos, (list, tuple)): + reasons.append('rule {} geos must be a list' + .format(rule_num)) + else: + for geo in geos: + reasons.extend(_DynamicRuleGeo.validate(rule_num, geo)) return reasons diff --git a/tests/config/dynamic.tests.yaml b/tests/config/dynamic.tests.yaml index ac83ad5..c48735e 100644 --- a/tests/config/dynamic.tests.yaml +++ b/tests/config/dynamic.tests.yaml @@ -21,9 +21,11 @@ a: - value: 5.5.5.5 weight: 25 rules: - - geo: EU-UK + - geos: + - EU-UK pool: iad - - geo: EU + - geos: + - EU pool: ams - geos: - NA-US-CA @@ -55,9 +57,11 @@ aaaa: - value: 2601:642:500:e210:62f8:1dff:feb8:9476 weight: 2 rules: - - geo: EU-UK + - geos: + - EU-UK pool: iad - - geo: EU + - geos: + - EU pool: ams - geos: - NA-US-CA @@ -88,9 +92,11 @@ cname: - value: target-sea-2.unit.tests. weight: 175 rules: - - geo: EU-UK + - geos: + - EU-UK pool: iad - - geo: EU + - geos: + - EU pool: ams - geos: - NA-US-CA diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 0a4e4df..45853d7 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -2688,6 +2688,113 @@ class TestDynamicRecords(TestCase): self.assertEquals(["rule 1 undefined pool \"non-existant\""], ctx.exception.reasons) + # rule with invalid geos + a_data = { + 'dynamic': { + 'pools': { + 'one': { + 'values': [{ + 'value': '3.3.3.3', + }] + }, + 'two': { + 'values': [{ + 'value': '4.4.4.4', + }, { + 'value': '5.5.5.5', + }] + }, + }, + 'rules': [{ + 'geos': 'NA-US-CA', + 'pool': 'two', + }, { + 'pool': 'one', + }], + }, + 'ttl': 60, + 'type': 'A', + 'values': [ + '1.1.1.1', + '2.2.2.2', + ], + } + with self.assertRaises(ValidationError) as ctx: + Record.new(self.zone, 'bad', a_data) + self.assertEquals(['rule 1 geos must be a list'], + ctx.exception.reasons) + + # rule with invalid geo + a_data = { + 'dynamic': { + 'pools': { + 'one': { + 'values': [{ + 'value': '3.3.3.3', + }] + }, + 'two': { + 'values': [{ + 'value': '4.4.4.4', + }, { + 'value': '5.5.5.5', + }] + }, + }, + 'rules': [{ + 'geos': ['invalid'], + 'pool': 'two', + }, { + 'pool': 'one', + }], + }, + 'ttl': 60, + 'type': 'A', + 'values': [ + '1.1.1.1', + '2.2.2.2', + ], + } + with self.assertRaises(ValidationError) as ctx: + Record.new(self.zone, 'bad', a_data) + self.assertEquals(['rule 1 invalid geo "invalid"'], + ctx.exception.reasons) + + # multiple default rules + a_data = { + 'dynamic': { + 'pools': { + 'one': { + 'values': [{ + 'value': '3.3.3.3', + }] + }, + 'two': { + 'values': [{ + 'value': '4.4.4.4', + }, { + 'value': '5.5.5.5', + }] + }, + }, + 'rules': [{ + 'pool': 'two', + }, { + 'pool': 'one', + }], + }, + 'ttl': 60, + 'type': 'A', + 'values': [ + '1.1.1.1', + '2.2.2.2', + ], + } + with self.assertRaises(ValidationError) as ctx: + Record.new(self.zone, 'bad', a_data) + self.assertEquals(['rule 2 duplicate default'], + ctx.exception.reasons) + def test_dynamic_lenient(self): # Missing pools a_data = {