Skip to content

Commit 0bb7430

Browse files
authored
Merge pull request #121 from rlaakkol/add-auth-retry-option
Add option to retry on auth errors
2 parents 51c34b6 + fa22cc7 commit 0bb7430

File tree

4 files changed

+70
-6
lines changed

4 files changed

+70
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## v0.0.8 - 20??-??-?? - ???
2+
3+
* Add support for optionally retrying requests that hit 403 errors
4+
15
## v0.0.7 - 2024-08-20 - DS always come second
26

37
* Create DS records after their sibling NS records to appease Cloudflare's

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ providers:
5757
# Optional. Default: 4. Number of times to retry if a 429 response
5858
# is received.
5959
#retry_count: 4
60+
# Optional. Default: 0. Number of times to retry if a 403 response
61+
# is received.
62+
#auth_error_retry_count: 0
6063
# Optional. Default: 300. Number of seconds to wait before retrying.
6164
#retry_period: 300
6265
# Optional. Default: 50. Number of zones per page.

octodns_cloudflare/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def __init__(
9191
pagerules=True,
9292
retry_count=4,
9393
retry_period=300,
94+
auth_error_retry_count=0,
9495
zones_per_page=50,
9596
records_per_page=100,
9697
min_ttl=120,
@@ -124,6 +125,7 @@ def __init__(
124125
self.pagerules = pagerules
125126
self.retry_count = retry_count
126127
self.retry_period = retry_period
128+
self.auth_error_retry_count = auth_error_retry_count
127129
self.zones_per_page = zones_per_page
128130
self.records_per_page = records_per_page
129131
self.min_ttl = min_ttl
@@ -140,6 +142,7 @@ def __init__(
140142

141143
def _try_request(self, *args, **kwargs):
142144
tries = self.retry_count
145+
auth_tries = self.auth_error_retry_count
143146
while True: # We'll raise to break after our tries expire
144147
try:
145148
return self._request(*args, **kwargs)
@@ -154,6 +157,17 @@ def _try_request(self, *args, **kwargs):
154157
tries,
155158
)
156159
sleep(self.retry_period)
160+
except CloudflareAuthenticationError:
161+
if auth_tries <= 0:
162+
raise
163+
auth_tries -= 1
164+
self.log.warning(
165+
'authentication error encountered, pausing '
166+
'for %ds and trying again, %d remaining',
167+
self.retry_period,
168+
auth_tries,
169+
)
170+
sleep(self.retry_period)
157171

158172
def _request(self, method, path, params=None, data=None):
159173
self.log.debug('_request: method=%s, path=%s', method, path)

tests/test_octodns_provider_cloudflare.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
from octodns.record import Create, Delete, Record, Update
1818
from octodns.zone import Zone
1919

20-
from octodns_cloudflare import CloudflareProvider, CloudflareRateLimitError
20+
from octodns_cloudflare import (
21+
CloudflareAuthenticationError,
22+
CloudflareProvider,
23+
CloudflareRateLimitError,
24+
)
2125

2226

2327
def set_record_proxied_flag(record, proxied):
@@ -2177,7 +2181,11 @@ def test_emailless_auth(self):
21772181

21782182
def test_retry_behavior(self):
21792183
provider = CloudflareProvider(
2180-
'test', token='token 123', email='email 234', retry_period=0
2184+
'test',
2185+
token='token 123',
2186+
email='email 234',
2187+
retry_period=0,
2188+
auth_error_retry_count=2, # Add auth retry config
21812189
)
21822190
result = {
21832191
"success": True,
@@ -2198,7 +2206,7 @@ def test_retry_behavior(self):
21982206
[call('GET', '/zones', params={'page': 1, 'per_page': 50})]
21992207
)
22002208

2201-
# One retry required
2209+
# One rate limit retry required
22022210
provider._zones = None
22032211
provider._request.reset_mock()
22042212
provider._request.side_effect = [CloudflareRateLimitError('{}'), result]
@@ -2207,20 +2215,32 @@ def test_retry_behavior(self):
22072215
[call('GET', '/zones', params={'page': 1, 'per_page': 50})]
22082216
)
22092217

2210-
# Two retries required
2218+
# One auth retry required
2219+
provider._zones = None
2220+
provider._request.reset_mock()
2221+
provider._request.side_effect = [
2222+
CloudflareAuthenticationError('{}'),
2223+
result,
2224+
]
2225+
self.assertEqual([], provider.zone_records(zone))
2226+
provider._request.assert_has_calls(
2227+
[call('GET', '/zones', params={'page': 1, 'per_page': 50})]
2228+
)
2229+
2230+
# Two retries required - mixed rate limit and auth errors
22112231
provider._zones = None
22122232
provider._request.reset_mock()
22132233
provider._request.side_effect = [
22142234
CloudflareRateLimitError('{}'),
2215-
CloudflareRateLimitError('{}'),
2235+
CloudflareAuthenticationError('{}'),
22162236
result,
22172237
]
22182238
self.assertEqual([], provider.zone_records(zone))
22192239
provider._request.assert_has_calls(
22202240
[call('GET', '/zones', params={'page': 1, 'per_page': 50})]
22212241
)
22222242

2223-
# # Exhaust our retries
2243+
# Exhaust rate limit retries
22242244
provider._zones = None
22252245
provider._request.reset_mock()
22262246
provider._request.side_effect = [
@@ -2234,6 +2254,29 @@ def test_retry_behavior(self):
22342254
provider.zone_records(zone)
22352255
self.assertEqual('last', str(ctx.exception))
22362256

2257+
# Exhaust auth retries
2258+
provider._zones = None
2259+
provider._request.reset_mock()
2260+
provider._request.side_effect = [
2261+
CloudflareAuthenticationError({"errors": [{"message": "first"}]}),
2262+
CloudflareAuthenticationError({"errors": [{"message": "second"}]}),
2263+
CloudflareAuthenticationError({"errors": [{"message": "last"}]}),
2264+
]
2265+
with self.assertRaises(CloudflareAuthenticationError) as ctx:
2266+
provider.zone_records(zone)
2267+
self.assertEqual('last', str(ctx.exception))
2268+
2269+
# Test with auth retries disabled (default behavior)
2270+
provider = CloudflareProvider(
2271+
'test', token='token 123', email='email 234', retry_period=0
2272+
)
2273+
provider._request = Mock()
2274+
provider._zones = None
2275+
provider._request.side_effect = [CloudflareAuthenticationError('{}')]
2276+
with self.assertRaises(CloudflareAuthenticationError):
2277+
provider.zone_records(zone)
2278+
self.assertEqual(1, provider._request.call_count)
2279+
22372280
def test_ttl_mapping(self):
22382281
provider = CloudflareProvider('test', 'email', 'token')
22392282

0 commit comments

Comments
 (0)