We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
bitwarden_secrets_manager: Handle rate limits
### Summary

I'm not finding any official documentation on it yet but Bitwarden's Secret Manager seems to have a rate limit of 5 requests per second. When the rate limit is hit, the lookup fails with an error: 429 Too Many Requests; Slow down! Too many requests. Try again in 1s.


### Issue Type

Bug Report

### Component Name

bitwarden_secret_manager

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.16.1]
  config file = /mnt/ansible/ansible.cfg
  configured module search path = ['/home/matta/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.11/site-packages/ansible
  ansible collection location = /mnt/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.11.9 (main, Apr 14 2024, 13:40:00) [GCC 13.2.1 20231014] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general

# /mnt/ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 8.5.0

# /usr/lib/python3.11/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 7.5.1
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
COLLECTIONS_PATHS(/mnt/ansible/ansible.cfg) = ['/mnt/ansible/collections']
CONFIG_FILE() = /mnt/ansible/ansible.cfg
DEFAULT_FORKS(/mnt/ansible/ansible.cfg) = 10
DEFAULT_HOST_LIST(/mnt/ansible/ansible.cfg) = ['/mnt/ansible/inventory']
DEFAULT_MANAGED_STR(/mnt/ansible/ansible.cfg) = This file is managed by Ansible. Do not modify directly!%n
template: {file}
date: %Y-%m-%d %H:%M:%S
user: {uid}
host: {host}
DISPLAY_SKIPPED_HOSTS(/mnt/ansible/ansible.cfg) = False
EDITOR(env: EDITOR) = vim
INTERPRETER_PYTHON(/mnt/ansible/ansible.cfg) = auto_silent
PAGER(env: PAGER) = less
```


### OS / Environment

Alpine Linux 3.19

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---
- name: Bitwarden Secrets Manager Rate Limit Reproduction
  hosts:
    - xen01
    - xen02
    - xen03
    - xen04
    - xen05
    - xen06
  become: false
  gather_facts: false
  tasks:
    - debug:
        var: "{{ lookup('community.general.bitwarden_secrets_manager', '<secret id here>').value }}"
```


### Expected Results

I would expect the module to handle the 429 error with a back-off and retry until it succeeds

### Actual Results

```console (paste below)
PLAY [Bitwarden Secrets Manager Rate Limit Reproduction] ******************************************************************************************************************************************************************************************************************
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************
fatal: [xen01]: FAILED! => {"msg": "Error: \n   0: Received error message from server: [429 Too Many Requests] {\"message\":\"Slow down! Too many requests. Try again in 1s.\",\"validationErrors\":null,\"exceptionMessage\":null,\"exceptionStackTrace\":null,\"innerExceptionMessage\":null,\"object\":\"error\"}\n\nLocation:\n   /home/matta/alpine-package-repository/main/bws/src/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bws-0.4.0/src/main.rs:334\n\nBacktrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.\nRun with RUST_BACKTRACE=full to include source snippets.\n"}
ok: [xen03] => {
    "this-is-a-test-secret": "{{this-is-a-test-secret}}"
}
ok: [xen04] => {
    "this-is-a-test-secret": "{{this-is-a-test-secret}}"
}
ok: [xen05] => {
    "this-is-a-test-secret": "{{this-is-a-test-secret}}"
}
ok: [xen06] => {
    "this-is-a-test-secret": "{{this-is-a-test-secret}}"
}
ok: [xen02] => {
    "this-is-a-test-secret": "{{this-is-a-test-secret}}"
}

PLAY RECAP ****************************************************************************************************************************************************************************************************************************************************************
xen01                      : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
xen02                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
xen03                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
xen04                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
xen05                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
xen06                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `plugins/lookup/bitwarden_secrets_manager.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2023, jantari (https://github.com/jantari)
3 # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
4 # SPDX-License-Identifier: GPL-3.0-or-later
5 from __future__ import (absolute_import, division, print_function)
6 
7 __metaclass__ = type
8 
9 DOCUMENTATION = """
10     name: bitwarden_secrets_manager
11     author:
12       - jantari (@jantari)
13     requirements:
14       - bws (command line utility)
15     short_description: Retrieve secrets from Bitwarden Secrets Manager
16     version_added: 7.2.0
17     description:
18       - Retrieve secrets from Bitwarden Secrets Manager.
19     options:
20       _terms:
21         description: Secret ID(s) to fetch values for.
22         required: true
23         type: list
24         elements: str
25       bws_access_token:
26         description: The BWS access token to use for this lookup.
27         env:
28           - name: BWS_ACCESS_TOKEN
29         required: true
30         type: str
31 """
32 
33 EXAMPLES = """
34 - name: Get a secret relying on the BWS_ACCESS_TOKEN environment variable for authentication
35   ansible.builtin.debug:
36     msg: >-
37       {{ lookup("community.general.bitwarden_secrets_manager", "2bc23e48-4932-40de-a047-5524b7ddc972") }}
38 
39 - name: Get a secret passing an explicit access token for authentication
40   ansible.builtin.debug:
41     msg: >-
42       {{
43         lookup(
44           "community.general.bitwarden_secrets_manager",
45           "2bc23e48-4932-40de-a047-5524b7ddc972",
46           bws_access_token="9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg=="
47         )
48       }}
49 
50 - name: Get two different secrets each using a different access token for authentication
51   ansible.builtin.debug:
52     msg:
53       - '{{ lookup("community.general.bitwarden_secrets_manager", "2bc23e48-4932-40de-a047-5524b7ddc972", bws_access_token=token1) }}'
54       - '{{ lookup("community.general.bitwarden_secrets_manager", "9d89af4c-eb5d-41f5-bb0f-4ae81215c768", bws_access_token=token2) }}'
55   vars:
56     token1: "9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg=="
57     token2: "1.69b72797-6ea9-4687-a11e-848e41a30ae6.YW5zaWJsZSBpcyBncmVhdD8K:YW5zaWJsZSBpcyBncmVhdAo="
58 
59 - name: Get just the value of a secret
60   ansible.builtin.debug:
61     msg: >-
62       {{ lookup("community.general.bitwarden_secrets_manager", "2bc23e48-4932-40de-a047-5524b7ddc972").value }}
63 """
64 
65 RETURN = """
66   _raw:
67     description: List containing one or more secrets.
68     type: list
69     elements: dict
70 """
71 
72 from subprocess import Popen, PIPE
73 
74 from ansible.errors import AnsibleLookupError
75 from ansible.module_utils.common.text.converters import to_text
76 from ansible.parsing.ajson import AnsibleJSONDecoder
77 from ansible.plugins.lookup import LookupBase
78 
79 
80 class BitwardenSecretsManagerException(AnsibleLookupError):
81     pass
82 
83 
84 class BitwardenSecretsManager(object):
85     def __init__(self, path='bws'):
86         self._cli_path = path
87 
88     @property
89     def cli_path(self):
90         return self._cli_path
91 
92     def _run(self, args, stdin=None):
93         p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
94         out, err = p.communicate(stdin)
95         rc = p.wait()
96         return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict'), rc
97 
98     def get_secret(self, secret_id, bws_access_token):
99         """Get and return the secret with the given secret_id.
100         """
101 
102         # Prepare set of params for Bitwarden Secrets Manager CLI
103         # Color output was not always disabled correctly with the default 'auto' setting so explicitly disable it.
104         params = [
105             '--color', 'no',
106             '--access-token', bws_access_token,
107             'get', 'secret', secret_id
108         ]
109 
110         out, err, rc = self._run(params)
111         if rc != 0:
112             raise BitwardenSecretsManagerException(to_text(err))
113 
114         return AnsibleJSONDecoder().raw_decode(out)[0]
115 
116 
117 class LookupModule(LookupBase):
118     def run(self, terms, variables=None, **kwargs):
119         self.set_options(var_options=variables, direct=kwargs)
120         bws_access_token = self.get_option('bws_access_token')
121 
122         return [_bitwarden_secrets_manager.get_secret(term, bws_access_token) for term in terms]
123 
124 
125 _bitwarden_secrets_manager = BitwardenSecretsManager()
126
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/plugins/lookup/bitwarden_secrets_manager.py b/plugins/lookup/bitwarden_secrets_manager.py
--- a/plugins/lookup/bitwarden_secrets_manager.py
+++ b/plugins/lookup/bitwarden_secrets_manager.py
@@ -70,6 +70,7 @@
 """
 
 from subprocess import Popen, PIPE
+from time import sleep
 
 from ansible.errors import AnsibleLookupError
 from ansible.module_utils.common.text.converters import to_text
@@ -84,11 +85,29 @@
 class BitwardenSecretsManager(object):
     def __init__(self, path='bws'):
         self._cli_path = path
+        self._max_retries = 3
+        self._retry_delay = 1
 
     @property
     def cli_path(self):
         return self._cli_path
 
+    def _run_with_retry(self, args, stdin=None, retries=0):
+        out, err, rc = self._run(args, stdin)
+
+        if rc != 0:
+            if retries >= self._max_retries:
+                raise BitwardenSecretsManagerException("Max retries exceeded. Unable to retrieve secret.")
+
+            if "Too many requests" in err:
+                delay = self._retry_delay * (2 ** retries)
+                sleep(delay)
+                return self._run_with_retry(args, stdin, retries + 1)
+            else:
+                raise BitwardenSecretsManagerException(f"Command failed with return code {rc}: {err}")
+
+        return out, err, rc
+
     def _run(self, args, stdin=None):
         p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
         out, err = p.communicate(stdin)
@@ -107,7 +126,7 @@
             'get', 'secret', secret_id
         ]
 
-        out, err, rc = self._run(params)
+        out, err, rc = self._run_with_retry(params)
         if rc != 0:
             raise BitwardenSecretsManagerException(to_text(err))

{"golden_diff": "diff --git a/plugins/lookup/bitwarden_secrets_manager.py b/plugins/lookup/bitwarden_secrets_manager.py\n--- a/plugins/lookup/bitwarden_secrets_manager.py\n+++ b/plugins/lookup/bitwarden_secrets_manager.py\n@@ -70,6 +70,7 @@\n \"\"\"\n \n from subprocess import Popen, PIPE\n+from time import sleep\n \n from ansible.errors import AnsibleLookupError\n from ansible.module_utils.common.text.converters import to_text\n@@ -84,11 +85,29 @@\n class BitwardenSecretsManager(object):\n     def __init__(self, path='bws'):\n         self._cli_path = path\n+        self._max_retries = 3\n+        self._retry_delay = 1\n \n     @property\n     def cli_path(self):\n         return self._cli_path\n \n+    def _run_with_retry(self, args, stdin=None, retries=0):\n+        out, err, rc = self._run(args, stdin)\n+\n+        if rc != 0:\n+            if retries >= self._max_retries:\n+                raise BitwardenSecretsManagerException(\"Max retries exceeded. Unable to retrieve secret.\")\n+\n+            if \"Too many requests\" in err:\n+                delay = self._retry_delay * (2 ** retries)\n+                sleep(delay)\n+                return self._run_with_retry(args, stdin, retries + 1)\n+            else:\n+                raise BitwardenSecretsManagerException(f\"Command failed with return code {rc}: {err}\")\n+\n+        return out, err, rc\n+\n     def _run(self, args, stdin=None):\n         p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)\n         out, err = p.communicate(stdin)\n@@ -107,7 +126,7 @@\n             'get', 'secret', secret_id\n         ]\n \n-        out, err, rc = self._run(params)\n+        out, err, rc = self._run_with_retry(params)\n         if rc != 0:\n             raise BitwardenSecretsManagerException(to_text(err))\n", "issue": "bitwarden_secrets_manager: Handle rate limits\n### Summary\n\nI'm not finding any official documentation on it yet but Bitwarden's Secret Manager seems to have a rate limit of 5 requests per second. When the rate limit is hit, the lookup fails with an error: 429 Too Many Requests; Slow down! Too many requests. Try again in 1s.\r\n\n\n### Issue Type\n\nBug Report\n\n### Component Name\n\nbitwarden_secret_manager\n\n### Ansible Version\n\n```console (paste below)\r\n$ ansible --version\r\nansible [core 2.16.1]\r\n  config file = /mnt/ansible/ansible.cfg\r\n  configured module search path = ['/home/matta/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /usr/lib/python3.11/site-packages/ansible\r\n  ansible collection location = /mnt/ansible/collections\r\n  executable location = /usr/bin/ansible\r\n  python version = 3.11.9 (main, Apr 14 2024, 13:40:00) [GCC 13.2.1 20231014] (/usr/bin/python3)\r\n  jinja version = 3.1.2\r\n  libyaml = True\r\n```\r\n\n\n### Community.general Version\n\n```console (paste below)\r\n$ ansible-galaxy collection list community.general\r\n\r\n# /mnt/ansible/collections/ansible_collections\r\nCollection        Version\r\n----------------- -------\r\ncommunity.general 8.5.0\r\n\r\n# /usr/lib/python3.11/site-packages/ansible_collections\r\nCollection        Version\r\n----------------- -------\r\ncommunity.general 7.5.1\r\n```\r\n\n\n### Configuration\n\n```console (paste below)\r\n$ ansible-config dump --only-changed\r\nCOLLECTIONS_PATHS(/mnt/ansible/ansible.cfg) = ['/mnt/ansible/collections']\r\nCONFIG_FILE() = /mnt/ansible/ansible.cfg\r\nDEFAULT_FORKS(/mnt/ansible/ansible.cfg) = 10\r\nDEFAULT_HOST_LIST(/mnt/ansible/ansible.cfg) = ['/mnt/ansible/inventory']\r\nDEFAULT_MANAGED_STR(/mnt/ansible/ansible.cfg) = This file is managed by Ansible. Do not modify directly!%n\r\ntemplate: {file}\r\ndate: %Y-%m-%d %H:%M:%S\r\nuser: {uid}\r\nhost: {host}\r\nDISPLAY_SKIPPED_HOSTS(/mnt/ansible/ansible.cfg) = False\r\nEDITOR(env: EDITOR) = vim\r\nINTERPRETER_PYTHON(/mnt/ansible/ansible.cfg) = auto_silent\r\nPAGER(env: PAGER) = less\r\n```\r\n\n\n### OS / Environment\n\nAlpine Linux 3.19\n\n### Steps to Reproduce\n\n<!--- Paste example playbooks or commands between quotes below -->\r\n```yaml (paste below)\r\n---\r\n- name: Bitwarden Secrets Manager Rate Limit Reproduction\r\n  hosts:\r\n    - xen01\r\n    - xen02\r\n    - xen03\r\n    - xen04\r\n    - xen05\r\n    - xen06\r\n  become: false\r\n  gather_facts: false\r\n  tasks:\r\n    - debug:\r\n        var: \"{{ lookup('community.general.bitwarden_secrets_manager', '<secret id here>').value }}\"\r\n```\r\n\n\n### Expected Results\n\nI would expect the module to handle the 429 error with a back-off and retry until it succeeds\n\n### Actual Results\n\n```console (paste below)\r\nPLAY [Bitwarden Secrets Manager Rate Limit Reproduction] ******************************************************************************************************************************************************************************************************************\r\nTASK [debug] **************************************************************************************************************************************************************************************************************************************************************\r\nfatal: [xen01]: FAILED! => {\"msg\": \"Error: \\n   0: Received error message from server: [429 Too Many Requests] {\\\"message\\\":\\\"Slow down! Too many requests. Try again in 1s.\\\",\\\"validationErrors\\\":null,\\\"exceptionMessage\\\":null,\\\"exceptionStackTrace\\\":null,\\\"innerExceptionMessage\\\":null,\\\"object\\\":\\\"error\\\"}\\n\\nLocation:\\n   /home/matta/alpine-package-repository/main/bws/src/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bws-0.4.0/src/main.rs:334\\n\\nBacktrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.\\nRun with RUST_BACKTRACE=full to include source snippets.\\n\"}\r\nok: [xen03] => {\r\n    \"this-is-a-test-secret\": \"{{this-is-a-test-secret}}\"\r\n}\r\nok: [xen04] => {\r\n    \"this-is-a-test-secret\": \"{{this-is-a-test-secret}}\"\r\n}\r\nok: [xen05] => {\r\n    \"this-is-a-test-secret\": \"{{this-is-a-test-secret}}\"\r\n}\r\nok: [xen06] => {\r\n    \"this-is-a-test-secret\": \"{{this-is-a-test-secret}}\"\r\n}\r\nok: [xen02] => {\r\n    \"this-is-a-test-secret\": \"{{this-is-a-test-secret}}\"\r\n}\r\n\r\nPLAY RECAP ****************************************************************************************************************************************************************************************************************************************************************\r\nxen01                      : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0\r\nxen02                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0\r\nxen03                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0\r\nxen04                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0\r\nxen05                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0\r\nxen06                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0\r\n```\r\n\n\n### Code of Conduct\n\n- [X] I agree to follow the Ansible Code of Conduct\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) 2023, jantari (https://github.com/jantari)\n# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)\n# SPDX-License-Identifier: GPL-3.0-or-later\nfrom __future__ import (absolute_import, division, print_function)\n\n__metaclass__ = type\n\nDOCUMENTATION = \"\"\"\n    name: bitwarden_secrets_manager\n    author:\n      - jantari (@jantari)\n    requirements:\n      - bws (command line utility)\n    short_description: Retrieve secrets from Bitwarden Secrets Manager\n    version_added: 7.2.0\n    description:\n      - Retrieve secrets from Bitwarden Secrets Manager.\n    options:\n      _terms:\n        description: Secret ID(s) to fetch values for.\n        required: true\n        type: list\n        elements: str\n      bws_access_token:\n        description: The BWS access token to use for this lookup.\n        env:\n          - name: BWS_ACCESS_TOKEN\n        required: true\n        type: str\n\"\"\"\n\nEXAMPLES = \"\"\"\n- name: Get a secret relying on the BWS_ACCESS_TOKEN environment variable for authentication\n  ansible.builtin.debug:\n    msg: >-\n      {{ lookup(\"community.general.bitwarden_secrets_manager\", \"2bc23e48-4932-40de-a047-5524b7ddc972\") }}\n\n- name: Get a secret passing an explicit access token for authentication\n  ansible.builtin.debug:\n    msg: >-\n      {{\n        lookup(\n          \"community.general.bitwarden_secrets_manager\",\n          \"2bc23e48-4932-40de-a047-5524b7ddc972\",\n          bws_access_token=\"9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg==\"\n        )\n      }}\n\n- name: Get two different secrets each using a different access token for authentication\n  ansible.builtin.debug:\n    msg:\n      - '{{ lookup(\"community.general.bitwarden_secrets_manager\", \"2bc23e48-4932-40de-a047-5524b7ddc972\", bws_access_token=token1) }}'\n      - '{{ lookup(\"community.general.bitwarden_secrets_manager\", \"9d89af4c-eb5d-41f5-bb0f-4ae81215c768\", bws_access_token=token2) }}'\n  vars:\n    token1: \"9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg==\"\n    token2: \"1.69b72797-6ea9-4687-a11e-848e41a30ae6.YW5zaWJsZSBpcyBncmVhdD8K:YW5zaWJsZSBpcyBncmVhdAo=\"\n\n- name: Get just the value of a secret\n  ansible.builtin.debug:\n    msg: >-\n      {{ lookup(\"community.general.bitwarden_secrets_manager\", \"2bc23e48-4932-40de-a047-5524b7ddc972\").value }}\n\"\"\"\n\nRETURN = \"\"\"\n  _raw:\n    description: List containing one or more secrets.\n    type: list\n    elements: dict\n\"\"\"\n\nfrom subprocess import Popen, PIPE\n\nfrom ansible.errors import AnsibleLookupError\nfrom ansible.module_utils.common.text.converters import to_text\nfrom ansible.parsing.ajson import AnsibleJSONDecoder\nfrom ansible.plugins.lookup import LookupBase\n\n\nclass BitwardenSecretsManagerException(AnsibleLookupError):\n    pass\n\n\nclass BitwardenSecretsManager(object):\n    def __init__(self, path='bws'):\n        self._cli_path = path\n\n    @property\n    def cli_path(self):\n        return self._cli_path\n\n    def _run(self, args, stdin=None):\n        p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)\n        out, err = p.communicate(stdin)\n        rc = p.wait()\n        return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict'), rc\n\n    def get_secret(self, secret_id, bws_access_token):\n        \"\"\"Get and return the secret with the given secret_id.\n        \"\"\"\n\n        # Prepare set of params for Bitwarden Secrets Manager CLI\n        # Color output was not always disabled correctly with the default 'auto' setting so explicitly disable it.\n        params = [\n            '--color', 'no',\n            '--access-token', bws_access_token,\n            'get', 'secret', secret_id\n        ]\n\n        out, err, rc = self._run(params)\n        if rc != 0:\n            raise BitwardenSecretsManagerException(to_text(err))\n\n        return AnsibleJSONDecoder().raw_decode(out)[0]\n\n\nclass LookupModule(LookupBase):\n    def run(self, terms, variables=None, **kwargs):\n        self.set_options(var_options=variables, direct=kwargs)\n        bws_access_token = self.get_option('bws_access_token')\n\n        return [_bitwarden_secrets_manager.get_secret(term, bws_access_token) for term in terms]\n\n\n_bitwarden_secrets_manager = BitwardenSecretsManager()\n", "path": "plugins/lookup/bitwarden_secrets_manager.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) 2023, jantari (https://github.com/jantari)\n# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)\n# SPDX-License-Identifier: GPL-3.0-or-later\nfrom __future__ import (absolute_import, division, print_function)\n\n__metaclass__ = type\n\nDOCUMENTATION = \"\"\"\n    name: bitwarden_secrets_manager\n    author:\n      - jantari (@jantari)\n    requirements:\n      - bws (command line utility)\n    short_description: Retrieve secrets from Bitwarden Secrets Manager\n    version_added: 7.2.0\n    description:\n      - Retrieve secrets from Bitwarden Secrets Manager.\n    options:\n      _terms:\n        description: Secret ID(s) to fetch values for.\n        required: true\n        type: list\n        elements: str\n      bws_access_token:\n        description: The BWS access token to use for this lookup.\n        env:\n          - name: BWS_ACCESS_TOKEN\n        required: true\n        type: str\n\"\"\"\n\nEXAMPLES = \"\"\"\n- name: Get a secret relying on the BWS_ACCESS_TOKEN environment variable for authentication\n  ansible.builtin.debug:\n    msg: >-\n      {{ lookup(\"community.general.bitwarden_secrets_manager\", \"2bc23e48-4932-40de-a047-5524b7ddc972\") }}\n\n- name: Get a secret passing an explicit access token for authentication\n  ansible.builtin.debug:\n    msg: >-\n      {{\n        lookup(\n          \"community.general.bitwarden_secrets_manager\",\n          \"2bc23e48-4932-40de-a047-5524b7ddc972\",\n          bws_access_token=\"9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg==\"\n        )\n      }}\n\n- name: Get two different secrets each using a different access token for authentication\n  ansible.builtin.debug:\n    msg:\n      - '{{ lookup(\"community.general.bitwarden_secrets_manager\", \"2bc23e48-4932-40de-a047-5524b7ddc972\", bws_access_token=token1) }}'\n      - '{{ lookup(\"community.general.bitwarden_secrets_manager\", \"9d89af4c-eb5d-41f5-bb0f-4ae81215c768\", bws_access_token=token2) }}'\n  vars:\n    token1: \"9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg==\"\n    token2: \"1.69b72797-6ea9-4687-a11e-848e41a30ae6.YW5zaWJsZSBpcyBncmVhdD8K:YW5zaWJsZSBpcyBncmVhdAo=\"\n\n- name: Get just the value of a secret\n  ansible.builtin.debug:\n    msg: >-\n      {{ lookup(\"community.general.bitwarden_secrets_manager\", \"2bc23e48-4932-40de-a047-5524b7ddc972\").value }}\n\"\"\"\n\nRETURN = \"\"\"\n  _raw:\n    description: List containing one or more secrets.\n    type: list\n    elements: dict\n\"\"\"\n\nfrom subprocess import Popen, PIPE\nfrom time import sleep\n\nfrom ansible.errors import AnsibleLookupError\nfrom ansible.module_utils.common.text.converters import to_text\nfrom ansible.parsing.ajson import AnsibleJSONDecoder\nfrom ansible.plugins.lookup import LookupBase\n\n\nclass BitwardenSecretsManagerException(AnsibleLookupError):\n    pass\n\n\nclass BitwardenSecretsManager(object):\n    def __init__(self, path='bws'):\n        self._cli_path = path\n        self._max_retries = 3\n        self._retry_delay = 1\n\n    @property\n    def cli_path(self):\n        return self._cli_path\n\n    def _run_with_retry(self, args, stdin=None, retries=0):\n        out, err, rc = self._run(args, stdin)\n\n        if rc != 0:\n            if retries >= self._max_retries:\n                raise BitwardenSecretsManagerException(\"Max retries exceeded. Unable to retrieve secret.\")\n\n            if \"Too many requests\" in err:\n                delay = self._retry_delay * (2 ** retries)\n                sleep(delay)\n                return self._run_with_retry(args, stdin, retries + 1)\n            else:\n                raise BitwardenSecretsManagerException(f\"Command failed with return code {rc}: {err}\")\n\n        return out, err, rc\n\n    def _run(self, args, stdin=None):\n        p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)\n        out, err = p.communicate(stdin)\n        rc = p.wait()\n        return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict'), rc\n\n    def get_secret(self, secret_id, bws_access_token):\n        \"\"\"Get and return the secret with the given secret_id.\n        \"\"\"\n\n        # Prepare set of params for Bitwarden Secrets Manager CLI\n        # Color output was not always disabled correctly with the default 'auto' setting so explicitly disable it.\n        params = [\n            '--color', 'no',\n            '--access-token', bws_access_token,\n            'get', 'secret', secret_id\n        ]\n\n        out, err, rc = self._run_with_retry(params)\n        if rc != 0:\n            raise BitwardenSecretsManagerException(to_text(err))\n\n        return AnsibleJSONDecoder().raw_decode(out)[0]\n\n\nclass LookupModule(LookupBase):\n    def run(self, terms, variables=None, **kwargs):\n        self.set_options(var_options=variables, direct=kwargs)\n        bws_access_token = self.get_option('bws_access_token')\n\n        return [_bitwarden_secrets_manager.get_secret(term, bws_access_token) for term in terms]\n\n\n_bitwarden_secrets_manager = BitwardenSecretsManager()\n", "path": "plugins/lookup/bitwarden_secrets_manager.py"}]}