acme_certificate and acme_certificate_create_order: add order_creation_error_strategy and order_creation_max_retries options (#842)

* Provide error information.

* Add helper function for order creation retrying.

* Improve existing documentation.

* Document 'replaces' return value.

* Add order_creation_error_strategy and order_creation_max_retries options.

* Add changelog fragment.

* Fix authz deactivation for finalizing step.

* Fix profile handling on order creation.

* Improve existing tests.

* Add ARI and profile tests.

* Warn when 'replaces' is removed when retrying to create an order.
This commit is contained in:
Felix Fontein
2025-01-18 10:51:10 +01:00
committed by GitHub
parent b9fa5b5193
commit 214794d056
17 changed files with 632 additions and 56 deletions

View File

@@ -87,6 +87,55 @@ class Order(object):
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
return cls.from_json(client, result, info['location'])
@classmethod
def create_with_error_handling(
cls,
client,
identifiers,
error_strategy='auto',
error_max_retries=3,
replaces_cert_id=None,
profile=None,
message_callback=None,
):
"""
error_strategy can be one of the following strings:
* ``fail``: simply fail. (Same behavior as ``Order.create()``.)
* ``retry_without_replaces_cert_id``: if ``replaces_cert_id`` is not ``None``, set it to ``None`` and retry.
The only exception is an error of type ``urn:ietf:params:acme:error:alreadyReplaced``, that indicates that
the certificate was already replaced.
* ``auto``: try to be clever. Right now this is identical to ``retry_without_replaces_cert_id``, but that can
change at any time in the future.
* ``always``: always retry until ``error_max_retries`` has been reached.
"""
tries = 0
while True:
tries += 1
try:
return cls.create(client, identifiers, replaces_cert_id=replaces_cert_id, profile=profile)
except ACMEProtocolException as exc:
if tries <= error_max_retries + 1 and error_strategy != 'fail':
if error_strategy == 'always':
continue
if (
error_strategy in ('auto', 'retry_without_replaces_cert_id') and
replaces_cert_id is not None and
not (exc.error_code == 409 and exc.error_type == 'urn:ietf:params:acme:error:alreadyReplaced')
):
replaces_cert_id = None
if message_callback:
message_callback(
'Stop passing `replaces` due to error {code} {type} when creating ACME order'.format(
code=exc.error_code,
type=exc.error_type,
)
)
continue
raise
def refresh(self, client):
result, dummy = client.get_request(self.url)
changed = self.data != result