diff --git a/changelogs/fragments/749-argspec.yml b/changelogs/fragments/749-argspec.yml new file mode 100644 index 000000000..f90f58009 --- /dev/null +++ b/changelogs/fragments/749-argspec.yml @@ -0,0 +1,2 @@ +deprecated_features: + - "crypto.module_backends.common module utils - the ``crypto.module_backends.common`` module utils is deprecated and will be removed from community.crypto 3.0.0. Use the improved ``argspec`` module util instead (https://github.com/ansible-collections/community.crypto/pull/749)." diff --git a/plugins/module_utils/acme/acme.py b/plugins/module_utils/acme/acme.py index 044f85080..d89d77083 100644 --- a/plugins/module_utils/acme/acme.py +++ b/plugins/module_utils/acme/acme.py @@ -21,6 +21,8 @@ from ansible.module_utils.urls import fetch_url from ansible.module_utils.six import PY3 +from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec + from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import ( OpenSSLCLIBackend, ) @@ -439,6 +441,28 @@ def get_default_argspec(with_account=True): return argspec +def create_default_argspec(with_account=True, require_account_key=True): + ''' + Provides default argument spec for the options documented in the acme doc fragment. + ''' + result = ArgumentSpec( + get_default_argspec(with_account=with_account), + ) + if with_account: + if require_account_key: + result.update( + required_one_of=[ + ['account_key_src', 'account_key_content'], + ], + ) + result.update( + mutually_exclusive=[ + ['account_key_src', 'account_key_content'], + ], + ) + return result + + def create_backend(module, needs_acme_v2): if not HAS_IPADDRESS: module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR) diff --git a/plugins/module_utils/argspec.py b/plugins/module_utils/argspec.py new file mode 100644 index 000000000..d6ba9c243 --- /dev/null +++ b/plugins/module_utils/argspec.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.basic import AnsibleModule + + +def _ensure_list(value): + if value is None: + return [] + return list(value) + + +class ArgumentSpec: + def __init__(self, argument_spec=None, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None): + self.argument_spec = argument_spec or {} + self.mutually_exclusive = _ensure_list(mutually_exclusive) + self.required_together = _ensure_list(required_together) + self.required_one_of = _ensure_list(required_one_of) + self.required_if = _ensure_list(required_if) + self.required_by = required_by or {} + + def update_argspec(self, **kwargs): + self.argument_spec.update(kwargs) + return self + + def update(self, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None): + if mutually_exclusive: + self.mutually_exclusive.extend(mutually_exclusive) + if required_together: + self.required_together.extend(required_together) + if required_one_of: + self.required_one_of.extend(required_one_of) + if required_if: + self.required_if.extend(required_if) + if required_by: + for k, v in required_by.items(): + if k in self.required_by: + v = list(self.required_by[k]) + list(v) + self.required_by[k] = v + return self + + def merge(self, other): + self.update_argspec(other.argument_spec) + self.update( + mutually_exclusive=other.mutually_exclusive, + required_together=other.required_together, + required_one_of=other.required_one_of, + required_if=other.required_if, + required_by=other.required_by, + ) + return self + + def create_ansible_module_helper(self, clazz, args, **kwargs): + return clazz( + *args, + argument_spec=self.argument_spec, + mutually_exclusive=self.mutually_exclusive, + required_together=self.required_together, + required_one_of=self.required_one_of, + required_if=self.required_if, + required_by=self.required_by, + **kwargs) + + def create_ansible_module(self, **kwargs): + return self.create_ansible_module_helper(AnsibleModule, (), **kwargs) + + +__all__ = ('ArgumentSpec', ) diff --git a/plugins/module_utils/crypto/module_backends/certificate.py b/plugins/module_utils/crypto/module_backends/certificate.py index 7bc93d934..595748fb9 100644 --- a/plugins/module_utils/crypto/module_backends/certificate.py +++ b/plugins/module_utils/crypto/module_backends/certificate.py @@ -15,9 +15,9 @@ from ansible.module_utils import six from ansible.module_utils.basic import missing_required_lib -from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion +from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec -from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec +from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( OpenSSLObjectError, diff --git a/plugins/module_utils/crypto/module_backends/common.py b/plugins/module_utils/crypto/module_backends/common.py index 67f87dd0c..6616249c4 100644 --- a/plugins/module_utils/crypto/module_backends/common.py +++ b/plugins/module_utils/crypto/module_backends/common.py @@ -10,26 +10,19 @@ from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec as _ArgumentSpec -class ArgumentSpec: - def __init__(self, argument_spec, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None): - self.argument_spec = argument_spec - self.mutually_exclusive = mutually_exclusive or [] - self.required_together = required_together or [] - self.required_one_of = required_one_of or [] - self.required_if = required_if or [] - self.required_by = required_by or {} +class ArgumentSpec(_ArgumentSpec): def create_ansible_module_helper(self, clazz, args, **kwargs): - return clazz( - *args, - argument_spec=self.argument_spec, - mutually_exclusive=self.mutually_exclusive, - required_together=self.required_together, - required_one_of=self.required_one_of, - required_if=self.required_if, - required_by=self.required_by, - **kwargs) - - def create_ansible_module(self, **kwargs): - return self.create_ansible_module_helper(AnsibleModule, (), **kwargs) + result = super(ArgumentSpec, self).create_ansible_module_helper(clazz, args, **kwargs) + result.deprecate( + "The crypto.module_backends.common module utils is deprecated and will be removed from community.crypto 3.0.0." + " Use the argspec module utils from community.crypto instead.", + version='3.0.0', + collection_name='community.crypto', + ) + return result + + +__all__ = ('AnsibleModule', 'ArgumentSpec') diff --git a/plugins/module_utils/crypto/module_backends/csr.py b/plugins/module_utils/crypto/module_backends/csr.py index 4ab14e527..6ce7e2438 100644 --- a/plugins/module_utils/crypto/module_backends/csr.py +++ b/plugins/module_utils/crypto/module_backends/csr.py @@ -17,6 +17,8 @@ from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.common.text.converters import to_native, to_text +from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec + from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( @@ -49,8 +51,6 @@ get_csr_info, ) -from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec - MINIMAL_CRYPTOGRAPHY_VERSION = '1.3' diff --git a/plugins/module_utils/crypto/module_backends/privatekey.py b/plugins/module_utils/crypto/module_backends/privatekey.py index dc13107b7..36d50ae3c 100644 --- a/plugins/module_utils/crypto/module_backends/privatekey.py +++ b/plugins/module_utils/crypto/module_backends/privatekey.py @@ -17,6 +17,8 @@ from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.common.text.converters import to_bytes +from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec + from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( @@ -42,8 +44,6 @@ get_privatekey_info, ) -from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec - MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3' diff --git a/plugins/module_utils/crypto/module_backends/privatekey_convert.py b/plugins/module_utils/crypto/module_backends/privatekey_convert.py index fdcc901e0..4a1aca600 100644 --- a/plugins/module_utils/crypto/module_backends/privatekey_convert.py +++ b/plugins/module_utils/crypto/module_backends/privatekey_convert.py @@ -15,12 +15,14 @@ from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.common.text.converters import to_bytes -from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion +from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec from ansible_collections.community.crypto.plugins.module_utils.io import ( load_file, ) +from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion + from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( CRYPTOGRAPHY_HAS_X25519, CRYPTOGRAPHY_HAS_X448, @@ -37,8 +39,6 @@ identify_private_key_format, ) -from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec - MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3' diff --git a/plugins/modules/acme_account.py b/plugins/modules/acme_account.py index a808c1962..960bad313 100644 --- a/plugins/modules/acme_account.py +++ b/plugins/modules/acme_account.py @@ -170,11 +170,9 @@ import base64 -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -189,8 +187,8 @@ def main(): - argument_spec = get_default_argspec() - argument_spec.update(dict( + argument_spec = create_default_argspec() + argument_spec.update_argspec( terms_agreed=dict(type='bool', default=False), state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']), allow_creation=dict(type='bool', default=True), @@ -203,14 +201,9 @@ def main(): alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']), key=dict(type='str', required=True, no_log=True), )) - )) - module = AnsibleModule( - argument_spec=argument_spec, - required_one_of=( - ['account_key_src', 'account_key_content'], - ), + ) + argument_spec.update( mutually_exclusive=( - ['account_key_src', 'account_key_content'], ['new_account_key_src', 'new_account_key_content'], ), required_if=( @@ -218,8 +211,8 @@ def main(): # new_account_key_src and new_account_key_content are specified ['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True], ), - supports_check_mode=True, ) + module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) if module.params['external_account_binding']: diff --git a/plugins/modules/acme_account_info.py b/plugins/modules/acme_account_info.py index fa999c2ac..33313fe75 100644 --- a/plugins/modules/acme_account_info.py +++ b/plugins/modules/acme_account_info.py @@ -214,11 +214,9 @@ version_added: 1.5.0 ''' -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -271,20 +269,11 @@ def get_order(client, order_url): def main(): - argument_spec = get_default_argspec() - argument_spec.update(dict( + argument_spec = create_default_argspec() + argument_spec.update_argspec( retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']), - )) - module = AnsibleModule( - argument_spec=argument_spec, - required_one_of=( - ['account_key_src', 'account_key_content'], - ), - mutually_exclusive=( - ['account_key_src', 'account_key_content'], - ), - supports_check_mode=True, ) + module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) try: diff --git a/plugins/modules/acme_ari_info.py b/plugins/modules/acme_ari_info.py index 7d0253625..7783236f0 100644 --- a/plugins/modules/acme_ari_info.py +++ b/plugins/modules/acme_ari_info.py @@ -98,11 +98,9 @@ sample: '2024-04-29T01:17:10.236921+00:00' ''' -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -110,21 +108,20 @@ def main(): - argument_spec = get_default_argspec(with_account=False) - argument_spec.update(dict( + argument_spec = create_default_argspec(with_account=False) + argument_spec.update_argspec( certificate_path=dict(type='path'), certificate_content=dict(type='str'), - )) - module = AnsibleModule( - argument_spec=argument_spec, + ) + argument_spec.update( required_one_of=( ['certificate_path', 'certificate_content'], ), mutually_exclusive=( ['certificate_path', 'certificate_content'], ), - supports_check_mode=True, ) + module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) try: diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index d40d6e095..0272dd80c 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -592,11 +592,9 @@ import os -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -922,8 +920,8 @@ def deactivate_authzs(self): def main(): - argument_spec = get_default_argspec() - argument_spec.update(dict( + argument_spec = create_default_argspec() + argument_spec.update_argspec( modify_account=dict(type='bool', default=True), account_email=dict(type='str'), agreement=dict(type='str'), @@ -947,20 +945,17 @@ def main(): authority_key_identifier=dict(type='str'), )), include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'), - )) - module = AnsibleModule( - argument_spec=argument_spec, + ) + argument_spec.update( required_one_of=( - ['account_key_src', 'account_key_content'], ['dest', 'fullchain_dest'], ['csr', 'csr_content'], ), mutually_exclusive=( - ['account_key_src', 'account_key_content'], ['csr', 'csr_content'], ), - supports_check_mode=True, ) + module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, False) try: diff --git a/plugins/modules/acme_certificate_deactivate_authz.py b/plugins/modules/acme_certificate_deactivate_authz.py index ffed04983..133f777d6 100644 --- a/plugins/modules/acme_certificate_deactivate_authz.py +++ b/plugins/modules/acme_certificate_deactivate_authz.py @@ -54,11 +54,9 @@ RETURN = '''#''' -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -76,20 +74,11 @@ def main(): - argument_spec = get_default_argspec() - argument_spec.update(dict( + argument_spec = create_default_argspec() + argument_spec.update_argspec( order_uri=dict(type='str', required=True), - )) - module = AnsibleModule( - argument_spec=argument_spec, - required_one_of=( - ['account_key_src', 'account_key_content'], - ), - mutually_exclusive=( - ['account_key_src', 'account_key_content'], - ), - supports_check_mode=True, ) + module = argument_spec.create_ansible_module(supports_check_mode=True) if module.params['acme_version'] == 1: module.fail_json('The module does not support acme_version=1') diff --git a/plugins/modules/acme_certificate_renewal_info.py b/plugins/modules/acme_certificate_renewal_info.py index c8a936870..1e2b16918 100644 --- a/plugins/modules/acme_certificate_renewal_info.py +++ b/plugins/modules/acme_certificate_renewal_info.py @@ -131,11 +131,9 @@ import os import random -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -145,8 +143,8 @@ def main(): - argument_spec = get_default_argspec(with_account=False) - argument_spec.update(dict( + argument_spec = create_default_argspec(with_account=False) + argument_spec.update_argspec( certificate_path=dict(type='path'), certificate_content=dict(type='str'), use_ari=dict(type='bool', default=True), @@ -154,14 +152,13 @@ def main(): remaining_days=dict(type='int'), remaining_percentage=dict(type='float'), now=dict(type='str'), - )) - module = AnsibleModule( - argument_spec=argument_spec, + ) + argument_spec.update( mutually_exclusive=( ['certificate_path', 'certificate_content'], ), - supports_check_mode=True, ) + module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) result = dict( @@ -223,13 +220,11 @@ def complete(should_renew, **kwargs): ), ) - # TODO check remaining_days if module.params['remaining_days'] is not None: remaining_days = (cert_info.not_valid_after - now).days if remaining_days < module.params['remaining_days']: complete(True, msg='The certificate expires in {0} days'.format(remaining_days)) - # TODO check remaining_percentage if module.params['remaining_percentage'] is not None: timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage']) if timestamp < now: diff --git a/plugins/modules/acme_certificate_revoke.py b/plugins/modules/acme_certificate_revoke.py index 4a68cf341..2661a1525 100644 --- a/plugins/modules/acme_certificate_revoke.py +++ b/plugins/modules/acme_certificate_revoke.py @@ -128,11 +128,9 @@ RETURN = '''#''' -from ansible.module_utils.basic import AnsibleModule - from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -153,24 +151,23 @@ def main(): - argument_spec = get_default_argspec() - argument_spec.update(dict( + argument_spec = create_default_argspec(require_account_key=False) + argument_spec.update_argspec( private_key_src=dict(type='path'), private_key_content=dict(type='str', no_log=True), private_key_passphrase=dict(type='str', no_log=True), certificate=dict(type='path', required=True), revoke_reason=dict(type='int'), - )) - module = AnsibleModule( - argument_spec=argument_spec, + ) + argument_spec.update( required_one_of=( ['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'], ), mutually_exclusive=( ['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'], ), - supports_check_mode=False, ) + module = argument_spec.create_ansible_module() backend = create_backend(module, False) try: diff --git a/plugins/modules/acme_inspect.py b/plugins/modules/acme_inspect.py index 2a4c5b935..c7ee49765 100644 --- a/plugins/modules/acme_inspect.py +++ b/plugins/modules/acme_inspect.py @@ -248,12 +248,11 @@ - ... ''' -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, - get_default_argspec, + create_default_argspec, ACMEClient, ) @@ -264,18 +263,14 @@ def main(): - argument_spec = get_default_argspec() - argument_spec.update(dict( + argument_spec = create_default_argspec(require_account_key=False) + argument_spec.update_argspec( url=dict(type='str'), method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'), content=dict(type='str'), fail_on_acme_error=dict(type='bool', default=True), - )) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=( - ['account_key_src', 'account_key_content'], - ), + ) + argument_spec.update( required_if=( ['method', 'get', ['url']], ['method', 'post', ['url', 'content']], @@ -283,6 +278,7 @@ def main(): ['method', 'post', ['account_key_src', 'account_key_content'], True], ), ) + module = argument_spec.create_ansible_module() backend = create_backend(module, False) result = dict()