acme_account_info: retrieve orders (#59697)

* Add retrieve_orders option.

* Run acme_certificate tests also for acme_account_info; use acme_account_info to get list of orders.

* Doing some quoting.

* Improve returned description.
This commit is contained in:
Felix Fontein 2019-08-26 18:16:43 +02:00 committed by GitHub
parent 14974f5fc2
commit 039123ec6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 220 additions and 2 deletions

View file

@ -28,6 +28,21 @@ notes:
- "The M(acme_account) module allows to modify, create and delete ACME accounts." - "The M(acme_account) module allows to modify, create and delete ACME accounts."
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage - "This module was called C(acme_account_facts) before Ansible 2.8. The usage
did not change." did not change."
options:
retrieve_orders:
description:
- "Whether to retrieve the list of order URLs or order objects, if provided
by the ACME server."
- "A value of C(ignore) will not fetch the list of orders."
- "Currently, Let's Encrypt does not return orders, so the C(orders) result
will always be empty."
type: str
choices:
- ignore
- url_list
- object_list
default: ignore
version_added: "2.9"
seealso: seealso:
- module: acme_account - module: acme_account
description: Allows to create, modify or delete an ACME account. description: Allows to create, modify or delete an ACME account.
@ -90,7 +105,10 @@ account:
choices: ['valid', 'deactivated', 'revoked'] choices: ['valid', 'deactivated', 'revoked']
sample: valid sample: valid
orders: orders:
description: a URL where a list of orders can be retrieved for this account description:
- A URL where a list of orders can be retrieved for this account.
- Use the I(retrieve_orders) option to query this URL and retrieve the
complete list of orders.
returned: always returned: always
type: str type: str
sample: https://example.ca/account/1/orders sample: https://example.ca/account/1/orders
@ -99,15 +117,126 @@ account:
returned: always returned: always
type: str type: str
sample: https://example.ca/account/1/orders sample: https://example.ca/account/1/orders
orders:
description:
- "The list of orders."
- "If I(retrieve_orders) is C(url_list), this will be a list of URLs."
- "If I(retrieve_orders) is C(object_list), this will be a list of objects."
type: list
returned: if account exists, I(retrieve_orders) is not C(ignore), and server supports order listing
contains:
status:
description: The order's status.
type: str
choices:
- pending
- ready
- processing
- valid
- invalid
expires:
description:
- When the order expires.
- Timestamp should be formatted as described in RFC3339.
- Only required to be included in result when I(status) is C(pending) or C(valid).
type: str
returned: when server gives expiry date
identifiers:
description:
- List of identifiers this order is for.
type: list
contains:
type:
description: Type of identifier. C(dns) or C(ip).
type: str
value:
description: Name of identifier. Hostname or IP address.
type: str
wildcard:
description: "Whether I(value) is actually a wildcard. The wildcard
prefix C(*.) is not included in I(value) if this is C(true)."
type: bool
returned: required to be included if the identifier is wildcarded
notBefore:
description:
- The requested value of the C(notBefore) field in the certificate.
- Date should be formatted as described in RFC3339.
- Server is not required to return this.
type: str
returned: when server returns this
notAfter:
description:
- The requested value of the C(notAfter) field in the certificate.
- Date should be formatted as described in RFC3339.
- Server is not required to return this.
type: str
returned: when server returns this
error:
description:
- In case an error occured during processing, this contains information about the error.
- The field is structured as a problem document (RFC7807).
type: complex
returned: when an error occurred
authorizations:
description:
- A list of URLs for authorizations for this order.
type: list
finalize:
description:
- A URL used for finalizing an ACME order.
type: str
certificate:
description:
- The URL for retrieving the certificate.
type: str
returned: when certificate was issued
''' '''
from ansible.module_utils.acme import ( from ansible.module_utils.acme import (
ModuleFailException, ACMEAccount, set_crypto_backend, ModuleFailException, ACMEAccount, set_crypto_backend, process_links,
) )
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
def get_orders_list(module, account, orders_url):
'''
Retrieves orders list (handles pagination).
'''
orders = []
while orders_url:
# Get part of orders list
res, info = account.get_request(orders_url, parse_json_result=True, fail_on_error=True)
if not res.get('orders'):
if orders:
module.warn('When retrieving orders list part {0}, got empty result list'.format(orders_url))
break
# Add order URLs to result list
orders.extend(res['orders'])
# Extract URL of next part of results list
new_orders_url = []
def f(link, relation):
if relation == 'next':
new_orders_url.append(link)
process_links(info, f)
new_orders_url.append(None)
previous_orders_url, orders_url = orders_url, new_orders_url.pop(0)
if orders_url == previous_orders_url:
# Prevent infinite loop
orders_url = None
return orders
def get_order(account, order_url):
'''
Retrieve order data.
'''
return account.get_request(order_url, parse_json_result=True, fail_on_error=True)[0]
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
@ -118,6 +247,7 @@ def main():
acme_version=dict(type='int', default=1, choices=[1, 2]), acme_version=dict(type='int', default=1, choices=[1, 2]),
validate_certs=dict(type='bool', default=True), validate_certs=dict(type='bool', default=True),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']), select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']),
), ),
required_one_of=( required_one_of=(
['account_key_src', 'account_key_content'], ['account_key_src', 'account_key_content'],
@ -159,6 +289,13 @@ def main():
account_data['contact'] = [] account_data['contact'] = []
account_data['public_account_key'] = account.key_data['jwk'] account_data['public_account_key'] = account.key_data['jwk']
result['account'] = account_data result['account'] = account_data
# Retrieve orders list
if account_data.get('orders') and module.params['retrieve_orders'] != 'ignore':
orders = get_orders_list(module, account, account_data['orders'])
if module.params['retrieve_orders'] == 'url_list':
result['orders'] = orders
else:
result['orders'] = [get_order(account, order) for order in orders]
module.exit_json(**result) module.exit_json(**result)
except ModuleFailException as e: except ModuleFailException as e:
e.do_fail(module) e.do_fail(module)

View file

@ -1,2 +1,3 @@
shippable/cloud/group1 shippable/cloud/group1
cloud/acme cloud/acme
acme_account_info

View file

@ -299,3 +299,49 @@
- name: Dumping cert 8 - name: Dumping cert 8
command: openssl x509 -in "{{ output_dir }}/cert-8.pem" -noout -text command: openssl x509 -in "{{ output_dir }}/cert-8.pem" -noout -text
register: cert_8_text register: cert_8_text
## GET ACCOUNT ORDERS #########################################################################
- name: Don't retrieve orders
acme_account_info:
select_crypto_backend: "{{ select_crypto_backend }}"
account_key_src: "{{ output_dir }}/account-ec256.pem"
acme_version: 2
acme_directory: https://{{ acme_host }}:14000/dir
validate_certs: no
retrieve_orders: ignore
register: account_orders_not
- name: Retrieve orders as URL list (1/2)
acme_account_info:
select_crypto_backend: "{{ select_crypto_backend }}"
account_key_src: "{{ output_dir }}/account-ec256.pem"
acme_version: 2
acme_directory: https://{{ acme_host }}:14000/dir
validate_certs: no
retrieve_orders: url_list
register: account_orders_urls
- name: Retrieve orders as URL list (2/2)
acme_account_info:
select_crypto_backend: "{{ select_crypto_backend }}"
account_key_src: "{{ output_dir }}/account-ec384.pem"
acme_version: 2
acme_directory: https://{{ acme_host }}:14000/dir
validate_certs: no
retrieve_orders: url_list
register: account_orders_urls2
- name: Retrieve orders as object list (1/2)
acme_account_info:
select_crypto_backend: "{{ select_crypto_backend }}"
account_key_src: "{{ output_dir }}/account-ec256.pem"
acme_version: 2
acme_directory: https://{{ acme_host }}:14000/dir
validate_certs: no
retrieve_orders: object_list
register: account_orders_full
- name: Retrieve orders as object list (2/2)
acme_account_info:
select_crypto_backend: "{{ select_crypto_backend }}"
account_key_src: "{{ output_dir }}/account-ec384.pem"
acme_version: 2
acme_directory: https://{{ acme_host }}:14000/dir
validate_certs: no
retrieve_orders: object_list
register: account_orders_full2

View file

@ -83,3 +83,37 @@
assert: assert:
that: that:
- "'DNS:example.org' in cert_6_text.stdout" - "'DNS:example.org' in cert_6_text.stdout"
- name: Validate that orders were not retrieved
assert:
that:
- "'account' in account_orders_not"
- "'orders' not in account_orders_not"
- name: Validate that orders were retrieved as list of URLs (1/2)
assert:
that:
- "'account' in account_orders_urls"
- "'orders' in account_orders_urls"
- "account_orders_urls.orders[0] is string"
- name: Validate that orders were retrieved as list of URLs (2/2)
assert:
that:
- "'account' in account_orders_urls2"
- "'orders' in account_orders_urls2"
- "account_orders_urls2.orders[0] is string"
- name: Validate that orders were retrieved as list of objects (1/2)
assert:
that:
- "'account' in account_orders_full"
- "'orders' in account_orders_full"
- "account_orders_full.orders[0].status is string"
- name: Validate that orders were retrieved as list of objects (2/2)
assert:
that:
- "'account' in account_orders_full2"
- "'orders' in account_orders_full2"
- "account_orders_full2.orders[0].status is string"