diff --git a/changelogs/fragments/178-storagebox.yml b/changelogs/fragments/178-storagebox.yml new file mode 100644 index 00000000..0cbcf29e --- /dev/null +++ b/changelogs/fragments/178-storagebox.yml @@ -0,0 +1,10 @@ +deprecated_features: + - "storagebox\\* modules - the ``hetzner_user`` and ``hetzner_pass`` options for these modules are deprecated; support will be removed in community.hrobot 3.0.0. Use ``hetzner_token`` instead (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox\\* modules - the ``hetzner_token`` option for these modules will be required from community.hrobot 3.0.0 on (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox\\* modules - membership in the ``community.hrobot.robot`` action group (module defaults group) is deprecated; the modules will be removed from the group in community.hrobot 3.0.0. Use ``community.hrobot.api`` instead (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox_info - the ``storageboxes[].login``, ``storageboxes[].disk_quota``, ``storageboxes[].disk_usage``, ``storageboxes[].disk_usage_data``, ``storageboxes[].disk_usage_snapshot``, ``storageboxes[].webdav``, ``storageboxes[].samba``, ``storageboxes[].ssh``, ``storageboxes[].external_reachability``, and ``storageboxes[].zfs`` return values are deprecated and will be removed from community.routeros. Check out the documentation to find out their new names according to the new API (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox_snapshot_info - the ``snapshots[].timestamp``, ``snapshots[].size``, ``snapshots[].filesystem_size``, ``snapshots[].automatic``, and ``snapshots[].comment`` return values are deprecated and will be removed from community.routeros. Check out the documentation to find out their new names according to the new API (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox_snapshot_plan - the ``plans[].month`` return value is deprecated, since it only returns ``null`` with the new API and cannot be set to any other value (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox_snapshot_plan_info - the ``plans[].month`` return value is deprecated, since it only returns ``null`` with the new API and cannot be set to any other value (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox_subaccount - the ``subaccount.homedirectory``, ``subaccount.samba``, ``subaccount.ssh``, ``subaccount.external_reachability``, ``subaccount.webdav``, ``subaccount.readonly``, ``subaccount.createtime``, and ``subaccount.comment`` return values are deprecated and will be removed from community.routeros. Check out the documentation to find out their new names according to the new API (https://github.com/ansible-collections/community.hrobot/pull/178)." + - "storagebox_subaccount_info - the ``subaccounts[].accountid``, ``subaccounts[].homedirectory``, ``subaccounts[].samba``, ``subaccounts[].ssh``, ``subaccounts[].external_reachability``, ``subaccounts[].webdav``, ``subaccounts[].readonly``, ``subaccounts[].createtime``, and ``subaccounts[].comment`` return values are deprecated and will be removed from community.routeros. Check out the documentation to find out their new names according to the new API (https://github.com/ansible-collections/community.hrobot/pull/178)." diff --git a/plugins/doc_fragments/api.py b/plugins/doc_fragments/api.py index 7af3559e..6449cdeb 100644 --- a/plugins/doc_fragments/api.py +++ b/plugins/doc_fragments/api.py @@ -47,3 +47,27 @@ class ModuleDocFragment(object): - If O(hetzner_password) is specified, O(hetzner_user) must also be specified, and O(hetzner_token) must not be specified. required: false """ + + # Only for transition period + _ROBOT_COMPAT_SHIM_DEPRECATION = r""" +options: + hetzner_token: + description: + - The API token for the Robot web-service user. + - One of O(hetzner_token) and O(hetzner_user) must be specified. + - This option will be required from community.hrobot 3.0.0 on. + required: false + hetzner_user: + description: + - The username for the Robot web-service user. + - One of O(hetzner_token) and O(hetzner_user) must be specified. + - If O(hetzner_user) is specified, O(hetzner_password) must also be specified, and O(hetzner_token) must not be specified. + - This option is deprecated for this module, and support will be removed in community.hrobot 3.0.0. + required: false + hetzner_password: + description: + - The password for the Robot web-service user. + - If O(hetzner_password) is specified, O(hetzner_user) must also be specified, and O(hetzner_token) must not be specified. + - This option is deprecated for this module, and support will be removed in community.hrobot 3.0.0. + required: false +""" diff --git a/plugins/doc_fragments/attributes.py b/plugins/doc_fragments/attributes.py index 7753f50b..affb1ee0 100644 --- a/plugins/doc_fragments/attributes.py +++ b/plugins/doc_fragments/attributes.py @@ -80,6 +80,20 @@ class ModuleDocFragment(object): - community.hrobot.robot ''' + # Only for transition period + _ACTIONGROUP_ROBOT_AND_API_DEPRECATION = r''' +options: {} +attributes: + action_group: + description: + - Use C(group/community.hrobot.robot) or C(group/community.hrobot.api) in C(module_defaults) to set defaults for this module. + - The C(group/community.hrobot.robot) group is B(deprecated) for this module; the module will be removed from the group in community.hrobot 3.0.0. + support: full + membership: + - community.hrobot.api + - community.hrobot.robot +''' + CONN = r""" options: {} attributes: diff --git a/plugins/module_utils/_tagging.py b/plugins/module_utils/_tagging.py new file mode 100644 index 00000000..39d59ce3 --- /dev/null +++ b/plugins/module_utils/_tagging.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025 Felix Fontein +# This code is licensed under the following two licenses: +# - Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) +# - 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: BSD-2-Clause OR GPL-3.0-or-later + +# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time. +# Do not use this from other collections or standalone plugins/modules! + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +try: + from ansible.module_utils.datatag import deprecate_value as _deprecate_value + HAS_DEPRECATE_VALUE = True +except ImportError: + HAS_DEPRECATE_VALUE = False + + +def deprecate_value(value, msg, version, help_text=None): + """ + Given a value, tag it as deprecated (with message, removal version, and optional help text). + + For ansible-core versions that do not support data tagging, simply returns the value as-is. + """ + if not HAS_DEPRECATE_VALUE: + return value + # Assign this to a variable to work around a bug in ansible-test's pylint check (https://github.com/ansible/ansible/issues/85614) + collection_name = "community.hrobot" + return _deprecate_value( + value, + msg, + collection_name=collection_name, + version=version, + help_text=help_text, + ) diff --git a/plugins/module_utils/robot.py b/plugins/module_utils/robot.py index 23c65327..0c1eb0ec 100644 --- a/plugins/module_utils/robot.py +++ b/plugins/module_utils/robot.py @@ -33,6 +33,12 @@ hetzner_password=dict(type='str', required=False, no_log=True), ) +_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED = dict( + hetzner_user=dict(type='str', required=False, removed_in_version="3.0.0", removed_from_collection="community.hrobot"), + hetzner_password=dict(type='str', required=False, no_log=True, removed_in_version="3.0.0", removed_from_collection="community.hrobot"), +) + + # The API endpoint is fixed. BASE_URL = "https://robot-ws.your-server.de" diff --git a/plugins/modules/storagebox.py b/plugins/modules/storagebox.py index 6d8f5b64..df59bd8a 100644 --- a/plugins/modules/storagebox.py +++ b/plugins/modules/storagebox.py @@ -18,11 +18,11 @@ description: - Modify a storage box's basic configuration. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot attributes: @@ -126,7 +126,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -199,7 +199,7 @@ def main(): zfs=dict(type='bool'), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -216,6 +216,11 @@ def main(): changes = {} if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API url = "{0}/storagebox/{1}".format(BASE_URL, storagebox_id) result, error = fetch_url_json(module, url, accept_errors=['STORAGEBOX_NOT_FOUND']) diff --git a/plugins/modules/storagebox_info.py b/plugins/modules/storagebox_info.py index 8e8298e2..5bd74201 100644 --- a/plugins/modules/storagebox_info.py +++ b/plugins/modules/storagebox_info.py @@ -18,11 +18,11 @@ description: - Query information on one or more storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot - community.hrobot.attributes.idempotent_not_modify_state @@ -88,6 +88,8 @@ description: - The storage box's login name. - Note that this is copied from RV(storageboxes[].username) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: u12345 returned: success @@ -200,6 +202,8 @@ description: - Total amount of MB available. - Note that this is copied from RV(storageboxes[].storage_box_type.size) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: 10240000 returned: when O(full_info=true), or O(hetzner_token) is specified @@ -207,6 +211,8 @@ description: - The amount of MB in use. - Note that this is copied from RV(storageboxes[].stats.size) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: 900 returned: when O(full_info=true), or O(hetzner_token) is specified @@ -214,6 +220,8 @@ description: - The amount of MB used by files. - Note that this is copied from RV(storageboxes[].stats.size_data) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: 500 returned: when O(full_info=true), or O(hetzner_token) is specified @@ -221,6 +229,8 @@ description: - The amount of MB used by snapshots. - Note that this is copied from RV(storageboxes[].stats.size_snapshots) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: 400 returned: when O(full_info=true), or O(hetzner_token) is specified @@ -228,6 +238,8 @@ description: - Whether WebDAV is active. - Note that this is copied from RV(storageboxes[].access_settings.webdav_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: when O(full_info=true), or O(hetzner_token) is specified @@ -235,6 +247,8 @@ description: - Whether SAMBA is active. - Note that this is copied from RV(storageboxes[].access_settings.samba_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: when O(full_info=true), or O(hetzner_token) is specified @@ -242,6 +256,8 @@ description: - Whether SSH is active. - Note that this is copied from RV(storageboxes[].access_settings.ssh_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: when O(full_info=true), or O(hetzner_token) is specified @@ -249,6 +265,8 @@ description: - Whether the storage box is reachable externally. - Note that this is copied from RV(storageboxes[].access_settings.reachable_externally) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: when O(full_info=true), or O(hetzner_token) is specified @@ -256,6 +274,8 @@ description: - Shows whether the ZFS directory is visible. - Note that this is copied from RV(storageboxes[].access_settings.zfs_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: false returned: when O(full_info=true), or O(hetzner_token) is specified @@ -521,7 +541,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -533,6 +553,10 @@ api_fetch_url_json_list, ) +from ansible_collections.community.hrobot.plugins.module_utils._tagging import ( + deprecate_value, +) + try: from urllib.parse import urlencode except ImportError: @@ -560,7 +584,11 @@ def add_hrobot_compat_shim(storagebox): value = storagebox for src in source: value = value[src] - result[dest] = value + result[dest] = deprecate_value( + value, + "The return value `{0}` is deprecated; use `{1}` instead.".format(dest, ".".join(source)), + version="3.0.0", + ) return result @@ -571,7 +599,7 @@ def main(): full_info=dict(type='bool', default=False), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -588,6 +616,11 @@ def main(): storageboxes = [] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API if storagebox_id is not None: storagebox_ids = [storagebox_id] diff --git a/plugins/modules/storagebox_set_password.py b/plugins/modules/storagebox_set_password.py index 019977f9..abd80bc6 100644 --- a/plugins/modules/storagebox_set_password.py +++ b/plugins/modules/storagebox_set_password.py @@ -17,11 +17,11 @@ description: - (Re)set the password for a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot @@ -85,7 +85,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -110,7 +110,7 @@ def main(): password=dict(type="str", no_log=True), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -123,6 +123,11 @@ def main(): password = module.params.get("password") if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API url = "{0}/storagebox/{1}/password".format(BASE_URL, id) accepted_errors = ["STORAGEBOX_NOT_FOUND", "STORAGEBOX_INVALID_PASSWORD"] diff --git a/plugins/modules/storagebox_snapshot.py b/plugins/modules/storagebox_snapshot.py index 7326bfd6..aadd6f70 100644 --- a/plugins/modules/storagebox_snapshot.py +++ b/plugins/modules/storagebox_snapshot.py @@ -18,11 +18,11 @@ description: - Create, update comment, or delete a snapshot of a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot attributes: @@ -108,7 +108,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -157,7 +157,7 @@ def main(): snapshot_comment=dict(type='str') ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) @@ -173,6 +173,11 @@ def main(): snapshot_comment = module.params['snapshot_comment'] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API # Create snapshot diff --git a/plugins/modules/storagebox_snapshot_info.py b/plugins/modules/storagebox_snapshot_info.py index a5a7906b..9b16455f 100644 --- a/plugins/modules/storagebox_snapshot_info.py +++ b/plugins/modules/storagebox_snapshot_info.py @@ -18,11 +18,11 @@ description: - Query the snapshots for a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot - community.hrobot.attributes.idempotent_not_modify_state @@ -71,6 +71,8 @@ description: - The timestamp of snapshot in UTC. - Note that this is copied from RV(snapshots[].created) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: "2025-01-21T13:40:38+00:00" returned: success @@ -78,6 +80,8 @@ description: - The Snapshot size in MB. - Note that this is copied from RV(snapshots[].stats.size) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: 400 returned: success @@ -85,6 +89,8 @@ description: - The size of the Storage Box at creation time of the snapshot in MB. - Note that this is computed from RV(snapshots[].stats.size_filesystem) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: 12345 returned: success @@ -92,6 +98,8 @@ description: - Whether the snapshot was created automatically. - Note that this is computed from RV(snapshots[].is_automatic) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: false returned: success @@ -99,6 +107,8 @@ description: - The comment for the snapshot. - Note that this is copied from RV(snapshots[].description) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: "This is a snapshot" returned: success @@ -167,7 +177,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -178,14 +188,38 @@ api_fetch_url_json, ) +from ansible_collections.community.hrobot.plugins.module_utils._tagging import ( + deprecate_value, +) + def adjust_legacy(snapshot): result = dict(snapshot) - result["timestamp"] = result["created"] - result["size"] = result["stats"]["size"] // (1024 * 1024) - result["filesystem_size"] = result["stats"]["size_filesystem"] // (1024 * 1024) - result["automatic"] = result["is_automatic"] - result["comment"] = result["description"] + result["timestamp"] = deprecate_value( + result["created"], + "The return value `timestamp` is deprecated; use `created` instead.", + version="3.0.0", + ) + result["size"] = deprecate_value( + result["stats"]["size"] // (1024 * 1024), + "The return value `size` is deprecated; use `stats.size / (1024*1024)` instead.", + version="3.0.0", + ) + result["filesystem_size"] = deprecate_value( + result["stats"]["size_filesystem"] // (1024 * 1024), + "The return value `filesystem_size` is deprecated; use `stats.size_filesystem / (1024*1024)` instead.", + version="3.0.0", + ) + result["automatic"] = deprecate_value( + result["is_automatic"], + "The return value `automatic` is deprecated; use `is_automatic` instead.", + version="3.0.0", + ) + result["comment"] = deprecate_value( + result["description"], + "The return value `comment` is deprecated; use `description` instead.", + version="3.0.0", + ) return result @@ -194,7 +228,7 @@ def main(): storagebox_id=dict(type='int', required=True), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -205,6 +239,11 @@ def main(): storagebox_id = module.params['storagebox_id'] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API url = "{0}/storagebox/{1}/snapshot".format(BASE_URL, storagebox_id) result, error = fetch_url_json(module, url, accept_errors=['STORAGEBOX_NOT_FOUND']) diff --git a/plugins/modules/storagebox_snapshot_plan.py b/plugins/modules/storagebox_snapshot_plan.py index 68b4a201..f83335dd 100644 --- a/plugins/modules/storagebox_snapshot_plan.py +++ b/plugins/modules/storagebox_snapshot_plan.py @@ -18,11 +18,11 @@ description: - Enable, modify, and disable the snapshot plans of a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot attributes: @@ -155,6 +155,8 @@ - The month of execution of the plan. V(1) is January, V(12) is December. - If set to V(null), the plan is run every month. - Always V(null) if O(hetzner_token) is provided. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: null returned: success @@ -171,7 +173,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -184,6 +186,10 @@ api_fetch_url_json, ) +from ansible_collections.community.hrobot.plugins.module_utils._tagging import ( + deprecate_value, +) + try: from urllib.parse import urlencode except ImportError: @@ -217,7 +223,7 @@ def extract(result): 'hour': None, 'day_of_week': None, 'day_of_month': None, - 'month': None, + 'month': deprecate_value(None, "The return value `month` is deprecated; it is always null.", version="3.0.0"), 'max_snapshots': None, } @@ -227,7 +233,7 @@ def extract(result): 'hour': sp['hour'], 'day_of_week': sp['day_of_week'], 'day_of_month': sp['day_of_month'], - 'month': None, + 'month': deprecate_value(None, "The return value `month` is deprecated; it is always null.", version="3.0.0"), 'max_snapshots': sp['max_snapshots'], } @@ -254,7 +260,7 @@ def main(): ), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -271,6 +277,11 @@ def main(): plan = plans[0] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API url = "{0}/storagebox/{1}/snapshotplan".format(BASE_URL, storagebox_id) result, error = fetch_url_json(module, url, accept_errors=['STORAGEBOX_NOT_FOUND']) diff --git a/plugins/modules/storagebox_snapshot_plan_info.py b/plugins/modules/storagebox_snapshot_plan_info.py index 04e8c462..50bc3a15 100644 --- a/plugins/modules/storagebox_snapshot_plan_info.py +++ b/plugins/modules/storagebox_snapshot_plan_info.py @@ -18,11 +18,11 @@ description: - Query the snapshot plans for a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot - community.hrobot.attributes.idempotent_not_modify_state @@ -101,6 +101,8 @@ - The month of execution of the plan. V(1) is January, V(12) is December. - If set to V(null), the plan is run every month. - Always V(null) if O(hetzner_token) is provided. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: int sample: null returned: success @@ -117,7 +119,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -128,6 +130,10 @@ api_fetch_url_json, ) +from ansible_collections.community.hrobot.plugins.module_utils._tagging import ( + deprecate_value, +) + def extract_legacy(result): sb = result['snapshotplan'] @@ -152,7 +158,7 @@ def extract(result): 'hour': None, 'day_of_week': None, 'day_of_month': None, - 'month': None, + 'month': deprecate_value(None, "The return value `month` is deprecated; it is always null.", version="3.0.0"), 'max_snapshots': None, } @@ -162,7 +168,7 @@ def extract(result): 'hour': sp['hour'], 'day_of_week': sp['day_of_week'], 'day_of_month': sp['day_of_month'], - 'month': None, + 'month': deprecate_value(None, "The return value `month` is deprecated; it is always null.", version="3.0.0"), 'max_snapshots': sp['max_snapshots'], } @@ -172,7 +178,7 @@ def main(): storagebox_id=dict(type='int', required=True), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -183,6 +189,11 @@ def main(): storagebox_id = module.params['storagebox_id'] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API url = "{0}/storagebox/{1}/snapshotplan".format(BASE_URL, storagebox_id) result, error = fetch_url_json(module, url, accept_errors=['STORAGEBOX_NOT_FOUND']) diff --git a/plugins/modules/storagebox_subaccount.py b/plugins/modules/storagebox_subaccount.py index 09705782..8c48252f 100644 --- a/plugins/modules/storagebox_subaccount.py +++ b/plugins/modules/storagebox_subaccount.py @@ -19,11 +19,11 @@ description: - Create, update, or delete a subaccount for a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot @@ -270,6 +270,10 @@ description: - The subaccount object returned by the API. - If O(hetzner_token) is provided, some extra fields are added to make this more compatible with the format returned by O(hetzner_user). + - B(This extra return values are deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using these return values. + These return values are RV(ignore:homedirectory), RV(ignore:samba), RV(ignore:ssh), RV(ignore:webdav), RV(ignore:external_reachability), + RV(ignore:readonly), RV(ignore:createtime), and RV(ignore:comment). type: dict returned: if O(state=present) """ @@ -280,7 +284,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -293,6 +297,10 @@ api_fetch_url_json, ) +from ansible_collections.community.hrobot.plugins.module_utils._tagging import ( + deprecate_value, +) + try: from urllib.parse import urlencode except ImportError: @@ -612,7 +620,11 @@ def adjust_legacy(subaccount): }.items(): value, exists = get_value_opt(subaccount, path) if exists: - result[key] = value + result[key] = deprecate_value( + value, + "The return value `{0}` is deprecated; use `{1}` instead.".format(key, ".".join(path)), + version="3.0.0", + ) return result @@ -638,7 +650,7 @@ def main(): idempotence=dict(type="str", choices=["username", "comment"], default="username"), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -665,6 +677,11 @@ def main(): account_identifier = subaccount[idempotence] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API existing_subaccounts = legacy_get_subaccounts(module, storagebox_id) diff --git a/plugins/modules/storagebox_subaccount_info.py b/plugins/modules/storagebox_subaccount_info.py index f3d3f9ea..e1f51ae6 100644 --- a/plugins/modules/storagebox_subaccount_info.py +++ b/plugins/modules/storagebox_subaccount_info.py @@ -19,11 +19,11 @@ description: - Query the subaccounts for a storage box. extends_documentation_fragment: - - community.hrobot.api._robot_compat_shim # must come before api and robot + - community.hrobot.api._robot_compat_shim_deprecation # must come before api and robot - community.hrobot.api - community.hrobot.robot - community.hrobot.attributes - - community.hrobot.attributes._actiongroup_robot_and_api # must come before the other two! + - community.hrobot.attributes._actiongroup_robot_and_api_deprecation # must come before the other two! - community.hrobot.attributes.actiongroup_api - community.hrobot.attributes.actiongroup_robot - community.hrobot.attributes.idempotent_not_modify_state @@ -72,9 +72,11 @@ description: - Username of the main user. - Not supported by the new Hetzner API. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: "u2342" - returned: success if O(hetzner_token) is not specified + returned: success and if O(hetzner_token) is not specified server: description: - Server on which the sub-account resides. @@ -85,6 +87,8 @@ description: - Homedirectory of the sub-account. - Note that this is copied from RV(subaccounts[].home_directory) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: "/home/u2342-sub1" returned: success @@ -92,6 +96,8 @@ description: - Status of Samba support. - Note that this is copied from RV(subaccounts[].access_settings.samba_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: success @@ -99,6 +105,8 @@ description: - Status of SSH support. - Note that this is copied from RV(subaccounts[].access_settings.ssh_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: success @@ -106,6 +114,8 @@ description: - Status of external reachability. - Note that this is copied from RV(subaccounts[].access_settings.reachable_externally) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: false returned: success @@ -113,6 +123,8 @@ description: - Status of WebDAV support. - Note that this is copied from RV(subaccounts[].access_settings.webdav_enabled) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: true returned: success @@ -120,6 +132,8 @@ description: - Indicates if the sub-account is in readonly mode. - Note that this is copied from RV(subaccounts[].access_settings.readonly) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: bool sample: false returned: success @@ -127,6 +141,8 @@ description: - Timestamp when the sub-account was created. - Note that this is copied from RV(subaccounts[].created) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: "2023-08-25T14:23:05Z" returned: success @@ -134,6 +150,8 @@ description: - Custom comment for the sub-account. - Note that this is copied from RV(subaccounts[].description) in case O(hetzner_token) is specified. + - B(This return value is deprecated and will be removed from community.hrobot 3.0.0.) + If you are using ansible-core 2.19 or newer, you will see a deprecation message when using this return value when using O(hetzner_token). type: str sample: "This is a subaccount" returned: success @@ -210,7 +228,7 @@ from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, ROBOT_DEFAULT_ARGUMENT_SPEC, - _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT, + _ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED, fetch_url_json, ) @@ -221,17 +239,53 @@ api_fetch_url_json, ) +from ansible_collections.community.hrobot.plugins.module_utils._tagging import ( + deprecate_value, +) + def adjust_legacy(subaccount): result = dict(subaccount) - result['homedirectory'] = subaccount['home_directory'] - result['samba'] = subaccount['access_settings']['samba_enabled'] - result['ssh'] = subaccount['access_settings']['ssh_enabled'] - result['webdav'] = subaccount['access_settings']['webdav_enabled'] - result['external_reachability'] = subaccount['access_settings']['reachable_externally'] - result['readonly'] = subaccount['access_settings']['readonly'] - result['createtime'] = subaccount['created'] - result['comment'] = subaccount['description'] + result['homedirectory'] = deprecate_value( + subaccount['home_directory'], + "The return value `homedirectory` is deprecated; use `home_directory` instead.", + version="3.0.0", + ) + result['samba'] = deprecate_value( + subaccount['access_settings']['samba_enabled'], + "The return value `samba` is deprecated; use `access_settings.samba_enabled` instead.", + version="3.0.0", + ) + result['ssh'] = deprecate_value( + subaccount['access_settings']['ssh_enabled'], + "The return value `ssh` is deprecated; use `access_settings.ssh_enabled` instead.", + version="3.0.0", + ) + result['webdav'] = deprecate_value( + subaccount['access_settings']['webdav_enabled'], + "The return value `webdav` is deprecated; use `access_settings.webdav_enabled` instead.", + version="3.0.0", + ) + result['external_reachability'] = deprecate_value( + subaccount['access_settings']['reachable_externally'], + "The return value `external_reachability` is deprecated; use `access_settings.reachable_externally` instead.", + version="3.0.0", + ) + result['readonly'] = deprecate_value( + subaccount['access_settings']['readonly'], + "The return value `readonly` is deprecated; use `access_settings.readonly` instead.", + version="3.0.0", + ) + result['createtime'] = deprecate_value( + subaccount['created'], + "The return value `createtime` is deprecated; use `created` instead.", + version="3.0.0", + ) + result['comment'] = deprecate_value( + subaccount['description'], + "The return value `comment` is deprecated; use `description` instead.", + version="3.0.0", + ) return result @@ -240,7 +294,7 @@ def main(): storagebox_id=dict(type="int", required=True), ) argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC) - argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT) + argument_spec.update(_ROBOT_DEFAULT_ARGUMENT_SPEC_COMPAT_DEPRECATED) argument_spec.update(API_DEFAULT_ARGUMENT_SPEC) argument_spec.update(_API_DEFAULT_ARGUMENT_SPEC_COMPAT) module = AnsibleModule( @@ -251,6 +305,11 @@ def main(): storagebox_id = module.params["storagebox_id"] if module.params["hetzner_user"] is not None: + module.deprecate( + "The hetzner_token parameter will be required from community.hrobot 3.0.0 on.", + collection_name="community.hrobot", + version="3.0.0", + ) # DEPRECATED: old API url = "{0}/storagebox/{1}/subaccount".format(BASE_URL, storagebox_id) diff --git a/tests/unit/plugins/modules/test_server_info.py b/tests/unit/plugins/modules/test_server_info.py index 9a9c715d..98774e9f 100644 --- a/tests/unit/plugins/modules/test_server_info.py +++ b/tests/unit/plugins/modules/test_server_info.py @@ -11,6 +11,8 @@ BaseTestModule, ) +from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import call, MagicMock + from ansible_collections.community.hrobot.plugins.module_utils.robot import BASE_URL from ansible_collections.community.hrobot.plugins.modules import server_info @@ -295,3 +297,172 @@ def test_server_name_none_error(self, mocker): ]) assert result['changed'] is False assert len(result['servers']) == 0 + + def test_server_number_rate_limit_fail(self, mocker): + result = self.run_module_failed(mocker, server_info, { + 'hetzner_user': 'test', + 'hetzner_password': 'hunter2', + 'server_number': 23, + 'rate_limit_retry_timeout': 0, + }, [ + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 5, + 'max_request': 1, + }, + }), + ]) + assert result['msg'] == ( + 'Request failed: 403 RATE_LIMIT_EXCEEDED (Rate limit exceeded).' + ' Maximum allowed requests: 1. Time interval in seconds: 5' + ) + + def test_server_number_rate_limit(self, mocker): + sleep_mock = MagicMock() + mocker.patch('time.sleep', sleep_mock) + result = self.run_module_success(mocker, server_info, { + 'hetzner_user': 'test', + 'hetzner_password': 'hunter2', + 'server_number': 23, + 'rate_limit_retry_timeout': -1, + }, [ + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 5, + 'max_request': 1, + }, + }), + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 3, + 'max_request': 1, + }, + }), + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 4, + 'max_request': 1, + }, + }), + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 5, + 'max_request': 1, + }, + }), + FetchUrlCall('GET', 200) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .result_json(SERVER_DETAIL_DATA[23]) + .expect_url('{0}/server/23'.format(BASE_URL)), + ]) + assert result['changed'] is False + assert len(result['servers']) == 1 + assert result['servers'][0] == SERVER_DETAIL_DATA[23]['server'] + sleep_mock.assert_has_calls([ + call(5), + call(3), + call(3), + call(3), + ]) + + def test_server_number_rate_limit_timeout(self, mocker): + elapsed = [123.4] + + def sleep(duration): + elapsed[0] += duration + print('sleep', duration, '->', elapsed[0]) + + def get_time(): + elapsed[0] += 0.03 + print('get', elapsed[0]) + return elapsed[0] + + mocker.patch('time.sleep', sleep) + mocker.patch('time.time', get_time) + result = self.run_module_failed(mocker, server_info, { + 'hetzner_user': 'test', + 'hetzner_password': 'hunter2', + 'server_number': 23, + 'rate_limit_retry_timeout': 7, + }, [ + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 5, + 'max_request': 1, + }, + }), + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 3, + 'max_request': 1, + }, + }), + FetchUrlCall('GET', 403) + .expect_basic_auth('test', 'hunter2') + .expect_force_basic_auth(True) + .expect_url('{0}/server/23'.format(BASE_URL)) + .result_json({ + 'error': { + 'status': 403, + 'code': 'RATE_LIMIT_EXCEEDED', + 'message': 'Rate limit exceeded', + 'interval': 4, + 'max_request': 1, + }, + }), + ]) + assert result['msg'] == ( + 'Request failed: 403 RATE_LIMIT_EXCEEDED (Rate limit exceeded).' + ' Maximum allowed requests: 1. Time interval in seconds: 4.' + ' Waited a total of 5.1 seconds for rate limit errors to go away' + )