mirror of
				https://github.com/github/octodns.git
				synced 2024-05-11 05:55:00 +00:00 
			
		
		
		
	Refactor PowerDNS version handling to be transparently cached properties
This commit is contained in:
		@@ -25,12 +25,11 @@ class PowerDnsBaseProvider(BaseProvider):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.host = host
 | 
					        self.host = host
 | 
				
			||||||
        self.port = port
 | 
					        self.port = port
 | 
				
			||||||
        self.version_detected = ''
 | 
					 | 
				
			||||||
        self.soa_edit_api = "INCEPTION-INCREMENT"
 | 
					 | 
				
			||||||
        self.check_status_not_found = False
 | 
					 | 
				
			||||||
        self.scheme = scheme
 | 
					        self.scheme = scheme
 | 
				
			||||||
        self.timeout = timeout
 | 
					        self.timeout = timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._powerdns_version = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sess = Session()
 | 
					        sess = Session()
 | 
				
			||||||
        sess.headers.update({'X-API-Key': api_key})
 | 
					        sess.headers.update({'X-API-Key': api_key})
 | 
				
			||||||
        self._sess = sess
 | 
					        self._sess = sess
 | 
				
			||||||
@@ -169,56 +168,46 @@ class PowerDnsBaseProvider(BaseProvider):
 | 
				
			|||||||
            'ttl': rrset['ttl']
 | 
					            'ttl': rrset['ttl']
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def detect_version(self):
 | 
					    @property
 | 
				
			||||||
        # Only detect version once
 | 
					    def powerdns_version(self):
 | 
				
			||||||
        if self.version_detected != '':
 | 
					        if self._powerdns_version is None:
 | 
				
			||||||
            self.log.debug('detect_version: version %s allready detected',
 | 
					 | 
				
			||||||
                           self.version_detected)
 | 
					 | 
				
			||||||
            return self.version_detected
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
            self.log.debug('detect_version: getting version from server')
 | 
					 | 
				
			||||||
                resp = self._get('')
 | 
					                resp = self._get('')
 | 
				
			||||||
            except HTTPError as e:
 | 
					            except HTTPError as e:
 | 
				
			||||||
                if e.response.status_code == 401:
 | 
					                if e.response.status_code == 401:
 | 
				
			||||||
                    # Nicer error message for auth problems
 | 
					                    # Nicer error message for auth problems
 | 
				
			||||||
                    raise Exception('PowerDNS unauthorized host={}'
 | 
					                    raise Exception('PowerDNS unauthorized host={}'
 | 
				
			||||||
                                    .format(self.host))
 | 
					                                    .format(self.host))
 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                raise
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.version_detected = resp.json()["version"]
 | 
					            version = resp.json()['version']
 | 
				
			||||||
        self.log.debug('detect_version: got version %s from server',
 | 
					            self.log.debug('powerdns_version: got version %s from server',
 | 
				
			||||||
                       self.version_detected)
 | 
					                           version)
 | 
				
			||||||
        self.configure_for_version(self.version_detected)
 | 
					            self._powerdns_version = [int(p) for p in version.split('.')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def configure_for_version(self, version):
 | 
					        return self._powerdns_version
 | 
				
			||||||
        major, minor, patch = version.split('.', 2)
 | 
					 | 
				
			||||||
        major, minor, patch = int(major), int(minor), int(patch)
 | 
					 | 
				
			||||||
        self.log.debug('configure_for_version: configure for '
 | 
					 | 
				
			||||||
                       'major: %s, minor: %s, patch: %s',
 | 
					 | 
				
			||||||
                       major, minor, patch)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Defaults for v4.0.0
 | 
					    @property
 | 
				
			||||||
        self.soa_edit_api = "INCEPTION-INCREMENT"
 | 
					    def soa_edit_api(self):
 | 
				
			||||||
        self.check_status_not_found = False
 | 
					        # >>> [4, 4, 3] >= [4, 3]
 | 
				
			||||||
 | 
					        # True
 | 
				
			||||||
 | 
					        # >>> [4, 3, 3] >= [4, 3]
 | 
				
			||||||
 | 
					        # True
 | 
				
			||||||
 | 
					        # >>> [4, 1, 3] >= [4, 3]
 | 
				
			||||||
 | 
					        # False
 | 
				
			||||||
 | 
					        if self.powerdns_version >= [4, 3]:
 | 
				
			||||||
 | 
					            return 'DEFAULT'
 | 
				
			||||||
 | 
					        return 'INCEPTION-INCREMENT'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if major == 4 and minor >= 2:
 | 
					    @property
 | 
				
			||||||
            self.log.debug("configure_for_version: Version >= 4.2")
 | 
					    def check_status_not_found(self):
 | 
				
			||||||
            self.soa_edit_api = "INCEPTION-INCREMENT"
 | 
					        # >=4.2.x returns 404 when not found
 | 
				
			||||||
            self.check_status_not_found = True
 | 
					        return self.powerdns_version >= [4, 2]
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if major == 4 and minor >= 3:
 | 
					 | 
				
			||||||
            self.log.debug("configure_for_version: Version >= 4.3")
 | 
					 | 
				
			||||||
            self.soa_edit_api = "DEFAULT"
 | 
					 | 
				
			||||||
            self.check_status_not_found = True
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def populate(self, zone, target=False, lenient=False):
 | 
					    def populate(self, zone, target=False, lenient=False):
 | 
				
			||||||
        self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name,
 | 
					        self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name,
 | 
				
			||||||
                       target, lenient)
 | 
					                       target, lenient)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.detect_version()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        resp = None
 | 
					        resp = None
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            resp = self._get('zones/{}'.format(zone.name))
 | 
					            resp = self._get('zones/{}'.format(zone.name))
 | 
				
			||||||
@@ -231,16 +220,16 @@ class PowerDnsBaseProvider(BaseProvider):
 | 
				
			|||||||
                                .format(self.host))
 | 
					                                .format(self.host))
 | 
				
			||||||
            elif e.response.status_code == 404 \
 | 
					            elif e.response.status_code == 404 \
 | 
				
			||||||
                    and self.check_status_not_found:
 | 
					                    and self.check_status_not_found:
 | 
				
			||||||
                # 404 or 422 means powerdns doesn't know anything about the
 | 
					                # 404 means powerdns doesn't know anything about the requested
 | 
				
			||||||
                # requested domain. We'll just ignore it here and leave the
 | 
					                # domain. We'll just ignore it here and leave the zone
 | 
				
			||||||
                # zone untouched.
 | 
					                # untouched.
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
            elif e.response.status_code == 422 \
 | 
					            elif e.response.status_code == 422 \
 | 
				
			||||||
                    and error.startswith('Could not find domain ') \
 | 
					                    and error.startswith('Could not find domain ') \
 | 
				
			||||||
                    and not self.check_status_not_found:
 | 
					                    and not self.check_status_not_found:
 | 
				
			||||||
                # 404 or 422 means powerdns doesn't know anything about the
 | 
					                # 422 means powerdns doesn't know anything about the requested
 | 
				
			||||||
                # requested domain. We'll just ignore it here and leave the
 | 
					                # domain. We'll just ignore it here and leave the zone
 | 
				
			||||||
                # zone untouched.
 | 
					                # untouched.
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # just re-throw
 | 
					                # just re-throw
 | 
				
			||||||
@@ -458,10 +447,6 @@ class PowerDnsProvider(PowerDnsBaseProvider):
 | 
				
			|||||||
            - 1.2.3.5.
 | 
					            - 1.2.3.5.
 | 
				
			||||||
        # The nameserver record TTL when managed, (optional, default 600)
 | 
					        # The nameserver record TTL when managed, (optional, default 600)
 | 
				
			||||||
        nameserver_ttl: 600
 | 
					        nameserver_ttl: 600
 | 
				
			||||||
        # The SOA-EDIT-API value to set for newly created zones (optional)
 | 
					 | 
				
			||||||
        # Defaults to INCEPTION-INCREMENT. PowerDNS >=4.3.x users should use
 | 
					 | 
				
			||||||
        # 'DEFAULT' instead, as INCEPTION-INCREMENT is no longer a valid value.
 | 
					 | 
				
			||||||
        soa_edit_api: INCEPTION-INCREMENT
 | 
					 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, id, host, api_key, port=8081, nameserver_values=None,
 | 
					    def __init__(self, id, host, api_key, port=8081, nameserver_values=None,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,7 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
            mock.get(ANY, status_code=401, text='Unauthorized')
 | 
					            mock.get(ANY, status_code=401, text='Unauthorized')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with self.assertRaises(Exception) as ctx:
 | 
					            with self.assertRaises(Exception) as ctx:
 | 
				
			||||||
                provider.detect_version()
 | 
					                provider.powerdns_version
 | 
				
			||||||
            self.assertTrue('unauthorized' in text_type(ctx.exception))
 | 
					            self.assertTrue('unauthorized' in text_type(ctx.exception))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Api not found
 | 
					        # Api not found
 | 
				
			||||||
@@ -58,23 +58,20 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
            mock.get(ANY, status_code=404, text='Not Found')
 | 
					            mock.get(ANY, status_code=404, text='Not Found')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with self.assertRaises(Exception) as ctx:
 | 
					            with self.assertRaises(Exception) as ctx:
 | 
				
			||||||
                provider.detect_version()
 | 
					                provider.powerdns_version
 | 
				
			||||||
            self.assertTrue('404' in text_type(ctx.exception))
 | 
					            self.assertTrue('404' in text_type(ctx.exception))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test version detection
 | 
					        # Test version detection
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
					            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
				
			||||||
                     status_code=200, json={'version': "4.1.10"})
 | 
					                     status_code=200, json={'version': "4.1.10"})
 | 
				
			||||||
 | 
					            self.assertEquals(provider.powerdns_version, [4, 1, 10])
 | 
				
			||||||
            provider.detect_version()
 | 
					 | 
				
			||||||
            self.assertEquals(provider.version_detected, '4.1.10')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test version detection for second time (should stay at 4.1.10)
 | 
					        # Test version detection for second time (should stay at 4.1.10)
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
					            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
				
			||||||
                     status_code=200, json={'version': "4.2.0"})
 | 
					                     status_code=200, json={'version': "4.2.0"})
 | 
				
			||||||
            provider.detect_version()
 | 
					            self.assertEquals(provider.powerdns_version, [4, 1, 10])
 | 
				
			||||||
            self.assertEquals(provider.version_detected, '4.1.10')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test version detection
 | 
					        # Test version detection
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
@@ -82,20 +79,19 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
                     status_code=200, json={'version': "4.2.0"})
 | 
					                     status_code=200, json={'version': "4.2.0"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Reset version, so detection will try again
 | 
					            # Reset version, so detection will try again
 | 
				
			||||||
            provider.version_detected = ''
 | 
					            provider._powerdns_version = None
 | 
				
			||||||
            provider.detect_version()
 | 
					            self.assertNotEquals(provider.powerdns_version, [4, 1, 10])
 | 
				
			||||||
            self.assertNotEquals(provider.version_detected, '4.1.10')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_provider_version_config(self):
 | 
					    def test_provider_version_config(self):
 | 
				
			||||||
        provider = PowerDnsProvider('test', 'non.existent', 'api-key',
 | 
					        provider = PowerDnsProvider('test', 'non.existent', 'api-key',
 | 
				
			||||||
                                    nameserver_values=['8.8.8.8.',
 | 
					                                    nameserver_values=['8.8.8.8.',
 | 
				
			||||||
                                                       '9.9.9.9.'])
 | 
					                                                       '9.9.9.9.'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        provider.check_status_not_found = None
 | 
					 | 
				
			||||||
        provider.soa_edit_api = 'something else'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Test version 4.1.0
 | 
					        # Test version 4.1.0
 | 
				
			||||||
        provider.configure_for_version("4.1.0")
 | 
					        provider._powerdns_version = None
 | 
				
			||||||
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
 | 
					            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
				
			||||||
 | 
					                     status_code=200, json={'version': "4.1.10"})
 | 
				
			||||||
            self.assertEquals(provider.soa_edit_api, 'INCEPTION-INCREMENT')
 | 
					            self.assertEquals(provider.soa_edit_api, 'INCEPTION-INCREMENT')
 | 
				
			||||||
            self.assertFalse(
 | 
					            self.assertFalse(
 | 
				
			||||||
                provider.check_status_not_found,
 | 
					                provider.check_status_not_found,
 | 
				
			||||||
@@ -103,14 +99,20 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
                'for version 4.1.x and below')
 | 
					                'for version 4.1.x and below')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test version 4.2.0
 | 
					        # Test version 4.2.0
 | 
				
			||||||
        provider.configure_for_version("4.2.0")
 | 
					        provider._powerdns_version = None
 | 
				
			||||||
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
 | 
					            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
				
			||||||
 | 
					                     status_code=200, json={'version': "4.2.0"})
 | 
				
			||||||
            self.assertEquals(provider.soa_edit_api, 'INCEPTION-INCREMENT')
 | 
					            self.assertEquals(provider.soa_edit_api, 'INCEPTION-INCREMENT')
 | 
				
			||||||
            self.assertTrue(
 | 
					            self.assertTrue(
 | 
				
			||||||
                provider.check_status_not_found,
 | 
					                provider.check_status_not_found,
 | 
				
			||||||
                'check_status_not_found should be true for version 4.2.x')
 | 
					                'check_status_not_found should be true for version 4.2.x')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test version 4.3.0
 | 
					        # Test version 4.3.0
 | 
				
			||||||
        provider.configure_for_version("4.3.0")
 | 
					        provider._powerdns_version = None
 | 
				
			||||||
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
 | 
					            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
				
			||||||
 | 
					                     status_code=200, json={'version': "4.3.0"})
 | 
				
			||||||
            self.assertEquals(provider.soa_edit_api, 'DEFAULT')
 | 
					            self.assertEquals(provider.soa_edit_api, 'DEFAULT')
 | 
				
			||||||
            self.assertTrue(
 | 
					            self.assertTrue(
 | 
				
			||||||
                provider.check_status_not_found,
 | 
					                provider.check_status_not_found,
 | 
				
			||||||
@@ -125,9 +127,7 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
					            mock.get('http://non.existent:8081/api/v1/servers/localhost',
 | 
				
			||||||
                     status_code=200, json={'version': "4.1.10"})
 | 
					                     status_code=200, json={'version': "4.1.10"})
 | 
				
			||||||
 | 
					            self.assertEquals(provider.powerdns_version, [4, 1, 10])
 | 
				
			||||||
            provider.detect_version()
 | 
					 | 
				
			||||||
            self.assertEquals(provider.version_detected, '4.1.10')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Bad auth
 | 
					        # Bad auth
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
@@ -157,13 +157,14 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Non-existent zone in PowerDNS >=4.2.0 doesn't populate anything
 | 
					        # Non-existent zone in PowerDNS >=4.2.0 doesn't populate anything
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        provider._powerdns_version = [4, 2, 0]
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
            mock.get(ANY, status_code=404, text='Not Found')
 | 
					            mock.get(ANY, status_code=404, text='Not Found')
 | 
				
			||||||
            provider.configure_for_version("4.2.0")
 | 
					 | 
				
			||||||
            zone = Zone('unit.tests.', [])
 | 
					            zone = Zone('unit.tests.', [])
 | 
				
			||||||
            provider.populate(zone)
 | 
					            provider.populate(zone)
 | 
				
			||||||
            self.assertEquals(set(), zone.records)
 | 
					            self.assertEquals(set(), zone.records)
 | 
				
			||||||
            provider.configure_for_version("4.1.0")
 | 
					
 | 
				
			||||||
 | 
					        provider._powerdns_version = [4, 1, 0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The rest of this is messy/complicated b/c it's dealing with mocking
 | 
					        # The rest of this is messy/complicated b/c it's dealing with mocking
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -219,8 +220,8 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
            self.assertEquals(expected_n, provider.apply(plan))
 | 
					            self.assertEquals(expected_n, provider.apply(plan))
 | 
				
			||||||
            self.assertFalse(plan.exists)
 | 
					            self.assertFalse(plan.exists)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        provider._powerdns_version = [4, 2, 0]
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
            provider.configure_for_version('4.2.0')
 | 
					 | 
				
			||||||
            # get 404's, unknown zone
 | 
					            # get 404's, unknown zone
 | 
				
			||||||
            mock.get(ANY, status_code=404, text='')
 | 
					            mock.get(ANY, status_code=404, text='')
 | 
				
			||||||
            # patch 404's, unknown zone
 | 
					            # patch 404's, unknown zone
 | 
				
			||||||
@@ -232,8 +233,8 @@ class TestPowerDnsProvider(TestCase):
 | 
				
			|||||||
            self.assertEquals(expected_n, len(plan.changes))
 | 
					            self.assertEquals(expected_n, len(plan.changes))
 | 
				
			||||||
            self.assertEquals(expected_n, provider.apply(plan))
 | 
					            self.assertEquals(expected_n, provider.apply(plan))
 | 
				
			||||||
            self.assertFalse(plan.exists)
 | 
					            self.assertFalse(plan.exists)
 | 
				
			||||||
            provider.configure_for_version('4.1.0')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        provider._powerdns_version = [4, 1, 0]
 | 
				
			||||||
        with requests_mock() as mock:
 | 
					        with requests_mock() as mock:
 | 
				
			||||||
            # get 422's, unknown zone
 | 
					            # get 422's, unknown zone
 | 
				
			||||||
            mock.get(ANY, status_code=422, text=dumps(not_found))
 | 
					            mock.get(ANY, status_code=422, text=dumps(not_found))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user