Repository: ansible-collections/community.general

Issue:
Gandi DNS module silently fails when using bearer tokens
### Summary

When I try to create a new DNS entry in an existing domain with the gandi module, it fails silently when using a bearer token instead of an API key only.

```yaml
    - name: Answer challenge
      community.general.gandi_livedns:
        domain: "{{ domain }}"
        type: TXT
        record: "meuh"
        values: "test"
        ttl: 3600
        api_key: "{{ dns_gandi_key }}"
        state: present
```

Error message is simply (in debug mode:
```bash
fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "api_key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "domain": "domain.com",
            "record": "meuh",
            "state": "present",
            "ttl": 3600,
            "type": "TXT",
            "values": [
                "test"
            ]
        }
    },
    "msg": "API Error : "
}
```

We should expect the task to work, or to fail with an explicit message.

### Issue Type

Bug Report

### Component Name

community.general.gandi_livedns

### Ansible Version

```console 
ansible [core 2.15.6]
  config file = None
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.9/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.2 (default, Feb 28 2021, 17:03:44) [GCC 10.2.1 20210110] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

```


### Community.general Version

```console
Collection        Version
----------------- -------
community.general 7.5.1

```


### Configuration

N/A


### OS / Environment

Debian 11, running on localhost for this task.

### Steps to Reproduce

See above. Do not forget to use a bearer token and not an API key.


### Expected Results

We should expect the task to work, or to fail with an explicit message.

### Actual Results

Failure.

It seems that at https://github.com/ansible-collections/community.general/blob/1b9d437be8d085b714f2c38208b30401e7dcf2d9/plugins/module_utils/gandi_livedns_api.py#L53 the module hard codes the type of key. However this type of key is deprecated. Also, no error message is provided in this case.

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct
Gandi DNS module silently fails when using bearer tokens
### Summary

When I try to create a new DNS entry in an existing domain with the gandi module, it fails silently when using a bearer token instead of an API key only.

```yaml
    - name: Answer challenge
      community.general.gandi_livedns:
        domain: "{{ domain }}"
        type: TXT
        record: "meuh"
        values: "test"
        ttl: 3600
        api_key: "{{ dns_gandi_key }}"
        state: present
```

Error message is simply (in debug mode:
```bash
fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "api_key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "domain": "domain.com",
            "record": "meuh",
            "state": "present",
            "ttl": 3600,
            "type": "TXT",
            "values": [
                "test"
            ]
        }
    },
    "msg": "API Error : "
}
```

We should expect the task to work, or to fail with an explicit message.

### Issue Type

Bug Report

### Component Name

community.general.gandi_livedns

### Ansible Version

```console 
ansible [core 2.15.6]
  config file = None
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.9/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.2 (default, Feb 28 2021, 17:03:44) [GCC 10.2.1 20210110] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

```


### Community.general Version

```console
Collection        Version
----------------- -------
community.general 7.5.1

```


### Configuration

N/A


### OS / Environment

Debian 11, running on localhost for this task.

### Steps to Reproduce

See above. Do not forget to use a bearer token and not an API key.


### Expected Results

We should expect the task to work, or to fail with an explicit message.

### Actual Results

Failure.

It seems that at https://github.com/ansible-collections/community.general/blob/1b9d437be8d085b714f2c38208b30401e7dcf2d9/plugins/module_utils/gandi_livedns_api.py#L53 the module hard codes the type of key. However this type of key is deprecated. Also, no error message is provided in this case.

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/gandi_livedns.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/gandi_livedns.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @gthiemonge
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
It fails the same way also when the ApiKey is no more valid

Adding the apikey is deprecated so the module is going to be useless shortly
Module works fine by replacing Apikey with Bearer on gandi_livedns.py line 53 and put your PAT into api_key -parameter. The correct fix could be replacing api_key parameter with pat but then you loose backward compatibility.
```
53c53
<         headers = {'Authorization': 'Apikey {0}'.format(self.api_key),
---
>         headers = {'Authorization': 'Bearer {0}'.format(self.api_key),
```


Making the key type configurable should solve the problem in a backwards-compatible way.
Files identified in the description:
* [`plugins/modules/gandi_livedns.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/gandi_livedns.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @gthiemonge
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
It fails the same way also when the ApiKey is no more valid

Adding the apikey is deprecated so the module is going to be useless shortly
Module works fine by replacing Apikey with Bearer on gandi_livedns.py line 53 and put your PAT into api_key -parameter. The correct fix could be replacing api_key parameter with pat but then you loose backward compatibility.
```
53c53
<         headers = {'Authorization': 'Apikey {0}'.format(self.api_key),
---
>         headers = {'Authorization': 'Bearer {0}'.format(self.api_key),
```


Making the key type configurable should solve the problem in a backwards-compatible way.

Resolving patch:
diff --git a/plugins/module_utils/gandi_livedns_api.py b/plugins/module_utils/gandi_livedns_api.py
--- a/plugins/module_utils/gandi_livedns_api.py
+++ b/plugins/module_utils/gandi_livedns_api.py
@@ -33,6 +33,7 @@ class GandiLiveDNSAPI(object):
     def __init__(self, module):
         self.module = module
         self.api_key = module.params['api_key']
+        self.personal_access_token = module.params['personal_access_token']
 
     def _build_error_message(self, module, info):
         s = ''
@@ -50,7 +51,12 @@ def _build_error_message(self, module, info):
         return s
 
     def _gandi_api_call(self, api_call, method='GET', payload=None, error_on_404=True):
-        headers = {'Authorization': 'Apikey {0}'.format(self.api_key),
+        authorization_header = (
+            'Bearer {0}'.format(self.personal_access_token)
+            if self.personal_access_token
+            else 'Apikey {0}'.format(self.api_key)
+        )
+        headers = {'Authorization': authorization_header,
                    'Content-Type': 'application/json'}
         data = None
         if payload:
diff --git a/plugins/modules/gandi_livedns.py b/plugins/modules/gandi_livedns.py
--- a/plugins/modules/gandi_livedns.py
+++ b/plugins/modules/gandi_livedns.py
@@ -25,11 +25,19 @@
   diff_mode:
     support: none
 options:
+  personal_access_token:
+    description:
+    - Scoped API token.
+    - One of O(personal_access_token) and O(api_key) must be specified.
+    type: str
+    version_added: 9.0.0
   api_key:
     description:
     - Account API token.
+    - Note that these type of keys are deprecated and might stop working at some point.
+      Use personal access tokens instead.
+    - One of O(personal_access_token) and O(api_key) must be specified.
     type: str
-    required: true
   record:
     description:
     - Record to add.
@@ -73,7 +81,7 @@
     values:
     - 127.0.0.1
     ttl: 7200
-    api_key: dummyapitoken
+    personal_access_token: dummytoken
   register: record
 
 - name: Create a mail CNAME record to www.my.com domain
@@ -84,7 +92,7 @@
     values:
     - www
     ttl: 7200
-    api_key: dummyapitoken
+    personal_access_token: dummytoken
     state: present
 
 - name: Change its TTL
@@ -95,7 +103,7 @@
     values:
     - www
     ttl: 10800
-    api_key: dummyapitoken
+    personal_access_token: dummytoken
     state: present
 
 - name: Delete the record
@@ -103,8 +111,18 @@
     domain: my.com
     type: CNAME
     record: mail
-    api_key: dummyapitoken
+    personal_access_token: dummytoken
     state: absent
+
+- name: Use a (deprecated) API Key
+  community.general.gandi_livedns:
+    domain: my.com
+    record: test
+    type: A
+    values:
+    - 127.0.0.1
+    ttl: 7200
+    api_key: dummyapikey
 '''
 
 RETURN = r'''
@@ -151,7 +169,8 @@
 def main():
     module = AnsibleModule(
         argument_spec=dict(
-            api_key=dict(type='str', required=True, no_log=True),
+            api_key=dict(type='str', no_log=True),
+            personal_access_token=dict(type='str', no_log=True),
             record=dict(type='str', required=True),
             state=dict(type='str', default='present', choices=['absent', 'present']),
             ttl=dict(type='int'),
@@ -163,6 +182,12 @@ def main():
         required_if=[
             ('state', 'present', ['values', 'ttl']),
         ],
+        mutually_exclusive=[
+            ('api_key', 'personal_access_token'),
+        ],
+        required_one_of=[
+            ('api_key', 'personal_access_token'),
+        ],
     )
 
     gandi_api = GandiLiveDNSAPI(module)


Test patch:
diff --git a/tests/integration/targets/gandi_livedns/tasks/create_record.yml b/tests/integration/targets/gandi_livedns/tasks/create_record.yml
--- a/tests/integration/targets/gandi_livedns/tasks/create_record.yml
+++ b/tests/integration/targets/gandi_livedns/tasks/create_record.yml
@@ -45,10 +45,10 @@
   assert:
     that:
     - result is changed
-    - result.record['values'] == {{ item['values'] }}
-    - result.record.record == "{{ item.record }}"
-    - result.record.type == "{{ item.type }}"
-    - result.record.ttl == {{ item.ttl }}
+    - result.record['values'] == item['values']
+    - result.record.record == item.record
+    - result.record.type == item.type
+    - result.record.ttl == item.ttl
 
 - name: test create a dns record idempotence
   community.general.gandi_livedns:
@@ -63,7 +63,16 @@
   assert:
     that:
     - result is not changed
-    - result.record['values'] == {{ item['values'] }}
-    - result.record.record == "{{ item.record }}"
-    - result.record.type == "{{ item.type }}"
-    - result.record.ttl == {{ item.ttl }}
+    - result.record['values'] == item['values']
+    - result.record.record == item.record
+    - result.record.type == item.type
+    - result.record.ttl == item.ttl
+
+- name: test create a DNS record with personal access token
+  community.general.gandi_livedns:
+    personal_access_token: "{{ gandi_personal_access_token }}"
+    record: "{{ item.record }}"
+    domain: "{{ gandi_livedns_domain_name }}"
+    values: "{{ item['values'] }}"
+    ttl: "{{ item.ttl }}"
+    type: "{{ item.type }}"
diff --git a/tests/integration/targets/gandi_livedns/tasks/update_record.yml b/tests/integration/targets/gandi_livedns/tasks/update_record.yml
--- a/tests/integration/targets/gandi_livedns/tasks/update_record.yml
+++ b/tests/integration/targets/gandi_livedns/tasks/update_record.yml
@@ -17,10 +17,10 @@
   assert:
     that:
     - result is changed
-    - result.record['values'] == {{ item.update_values | default(item['values']) }}
-    - result.record.record == "{{ item.record }}"
-    - result.record.type == "{{ item.type }}"
-    - result.record.ttl == {{ item.update_ttl | default(item.ttl) }}
+    - result.record['values'] == (item.update_values | default(item['values']))
+    - result.record.record == item.record
+    - result.record.type == item.type
+    - result.record.ttl == (item.update_ttl | default(item.ttl))
 
 - name: test update or add another dns record
   community.general.gandi_livedns:
@@ -35,10 +35,10 @@
   assert:
     that:
     - result is changed
-    - result.record['values'] == {{ item.update_values | default(item['values']) }}
-    - result.record.record == "{{ item.record }}"
-    - result.record.ttl == {{ item.update_ttl | default(item.ttl) }}
-    - result.record.type == "{{ item.type }}"
+    - result.record['values'] == (item.update_values | default(item['values']))
+    - result.record.record == item.record
+    - result.record.ttl == (item.update_ttl | default(item.ttl))
+    - result.record.type == item.type
 
 - name: test update or add another dns record idempotence
   community.general.gandi_livedns:
@@ -53,7 +53,7 @@
   assert:
     that:
     - result is not changed
-    - result.record['values'] == {{ item.update_values | default(item['values']) }}
-    - result.record.record == "{{ item.record }}"
-    - result.record.ttl == {{ item.update_ttl | default(item.ttl) }}
-    - result.record.type == "{{ item.type }}"
+    - result.record['values'] == (item.update_values | default(item['values']))
+    - result.record.record == item.record
+    - result.record.ttl == (item.update_ttl | default(item.ttl))
+    - result.record.type == item.type