We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
LuaDNS _list_records fails due to missing "id" in API response
I'm using certbot with the luadns plugin which in turn uses lexicon.
I've been experiencing the error reported here: https://community.letsencrypt.org/t/luadns-renew-error/89634

```
2019-08-29 06:32:43,517:DEBUG:urllib3.connectionpool:https://api.luadns.com:443 "GET /v1/zones/[XXXXXX]/records HTTP/1.1" 200 None
2019-08-29 06:32:43,520:ERROR:certbot.error_handler:Encountered exception during recovery: 
Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python2.7/site-packages/certbot/error_handler.py", line 124, in _call_registered
    self.funcs[-1]()
  File "/home/ubuntu/.local/lib/python2.7/site-packages/certbot/auth_handler.py", line 220, in _cleanup_challenges
    self.auth.cleanup(achalls)
  File "/home/ubuntu/.local/lib/python2.7/site-packages/certbot/plugins/dns_common.py", line 77, in cleanup
    self._cleanup(domain, validation_domain_name, validation)
  File "/usr/local/lib/python2.7/dist-packages/certbot_dns_luadns/dns_luadns.py", line 55, in _cleanup
    self._get_luadns_client().del_txt_record(domain, validation_name, validation)
  File "/home/ubuntu/.local/lib/python2.7/site-packages/certbot/plugins/dns_common_lexicon.py", line 65, in del_txt_record
    self.provider.delete_record(type='TXT', name=record_name, content=record_content)
  File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/base.py", line 118, in delete_record
    return self._delete_record(identifier=identifier, rtype=rtype, name=name, content=content)
  File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/luadns.py", line 111, in _delete_record
    records = self._list_records(rtype, name, content)
  File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/luadns.py", line 71, in _list_records
    'id': record['id']
KeyError: 'id'
```
I've replaced the zone id with [XXXXXX].

Code line that errors: https://github.com/AnalogJ/lexicon/blob/master/lexicon/providers/luadns.py#L71

Running the call directly towards the LuaDNS api shows that my REDIRECT record does not return an "id" -> no wonder it fails.

```
...
[
    {
        "id": XXXXXXXXX,
        "name": "sub.some.domain.",
        "type": "A",
        "content": "111.111.111.111",
        "ttl": 300,
        "zone_id": XXXXX,
        "generated": false,
        "created_at": "2019-08-27T05:59:34.219211Z",
        "updated_at": "2019-08-27T05:59:34.219213Z"
    },
    {
        "name": "some.domain.",
        "type": "REDIRECT",
        "content": "1 https://sub.some.domain",
        "ttl": 300,
        "zone_id": XXXXX,
        "generated": false,
        "created_at": "2019-08-29T07:05:09.757404499Z",
        "updated_at": "2019-08-29T07:05:09.757406053Z"
    }
]
...
```

My question is: How do I go forward with this? (for now, I've just deleted the REDIRECT record and everything works fine)
1) Send PR to lexicon to ignore records that does not have id? ( if 'id' in record )
2) Reach out to LuaDNS for API fix?

Any input would be great :)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lexicon/providers/luadns.py`
Content:
```
1 """Module provider for luadns"""
2 from __future__ import absolute_import
3 import json
4 import logging
5 
6 import requests
7 from lexicon.providers.base import Provider as BaseProvider
8 
9 
10 LOGGER = logging.getLogger(__name__)
11 
12 NAMESERVER_DOMAINS = ['luadns.com']
13 
14 
15 def provider_parser(subparser):
16     """Configure provider parser for luadns"""
17     subparser.add_argument(
18         "--auth-username", help="specify email address for authentication")
19     subparser.add_argument(
20         "--auth-token", help="specify token for authentication")
21 
22 
23 class Provider(BaseProvider):
24     """Provider class for luadns"""
25     def __init__(self, config):
26         super(Provider, self).__init__(config)
27         self.domain_id = None
28         self.api_endpoint = 'https://api.luadns.com/v1'
29 
30     def _authenticate(self):
31         payload = self._get('/zones')
32 
33         domain_info = next(
34             (domain for domain in payload if domain['name'] == self.domain), None)
35 
36         if not domain_info:
37             raise Exception('No domain found')
38 
39         self.domain_id = domain_info['id']
40 
41     # Create record. If record already exists with the same content, do nothing'
42 
43     def _create_record(self, rtype, name, content):
44         # check if record already exists
45         existing_records = self._list_records(rtype, name, content)
46         if len(existing_records) == 1:
47             return True
48 
49         self._post('/zones/{0}/records'.format(self.domain_id),
50                    {'type': rtype,
51                     'name': self._fqdn_name(name),
52                     'content': content,
53                     'ttl': self._get_lexicon_option('ttl')})
54 
55         LOGGER.debug('create_record: %s', True)
56         return True
57 
58     # List all records. Return an empty list if no records found
59     # type, name and content are used to filter records.
60     # If possible filter during the query, otherwise filter after response is received.
61     def _list_records(self, rtype=None, name=None, content=None):
62         payload = self._get('/zones/{0}/records'.format(self.domain_id))
63 
64         records = []
65         for record in payload:
66             processed_record = {
67                 'type': record['type'],
68                 'name': self._full_name(record['name']),
69                 'ttl': record['ttl'],
70                 'content': record['content'],
71                 'id': record['id']
72             }
73             records.append(processed_record)
74 
75         if rtype:
76             records = [record for record in records if record['type'] == rtype]
77         if name:
78             records = [record for record in records if record['name']
79                        == self._full_name(name)]
80         if content:
81             records = [
82                 record for record in records if record['content'] == content]
83 
84         LOGGER.debug('list_records: %s', records)
85         return records
86 
87     # Create or update a record.
88     def _update_record(self, identifier, rtype=None, name=None, content=None):
89 
90         data = {
91             'ttl': self._get_lexicon_option('ttl')
92         }
93         if rtype:
94             data['type'] = rtype
95         if name:
96             data['name'] = self._fqdn_name(name)
97         if content:
98             data['content'] = content
99 
100         self._put(
101             '/zones/{0}/records/{1}'.format(self.domain_id, identifier), data)
102 
103         LOGGER.debug('update_record: %s', True)
104         return True
105 
106     # Delete an existing record.
107     # If record does not exist, do nothing.
108     def _delete_record(self, identifier=None, rtype=None, name=None, content=None):
109         delete_record_id = []
110         if not identifier:
111             records = self._list_records(rtype, name, content)
112             delete_record_id = [record['id'] for record in records]
113         else:
114             delete_record_id.append(identifier)
115 
116         LOGGER.debug('delete_records: %s', delete_record_id)
117 
118         for record_id in delete_record_id:
119             self._delete(
120                 '/zones/{0}/records/{1}'.format(self.domain_id, record_id))
121 
122         LOGGER.debug('delete_record: %s', True)
123         return True
124 
125     # Helpers
126     def _request(self, action='GET', url='/', data=None, query_params=None):
127         if data is None:
128             data = {}
129         if query_params is None:
130             query_params = {}
131         response = requests.request(action, self.api_endpoint + url, params=query_params,
132                                     data=json.dumps(data),
133                                     auth=requests.auth.HTTPBasicAuth(self._get_provider_option(
134                                         'auth_username'), self._get_provider_option('auth_token')),
135                                     headers={
136                                         'Content-Type': 'application/json',
137                                         'Accept': 'application/json'
138                                     })
139         # if the request fails for any reason, throw an error.
140         response.raise_for_status()
141         return response.json()
142
```
--- 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/lexicon/providers/luadns.py b/lexicon/providers/luadns.py
--- a/lexicon/providers/luadns.py
+++ b/lexicon/providers/luadns.py
@@ -63,14 +63,15 @@
 
         records = []
         for record in payload:
-            processed_record = {
-                'type': record['type'],
-                'name': self._full_name(record['name']),
-                'ttl': record['ttl'],
-                'content': record['content'],
-                'id': record['id']
-            }
-            records.append(processed_record)
+            if 'id' in record:
+                processed_record = {
+                    'id': record['id'],
+                    'type': record['type'],
+                    'name': self._full_name(record['name']),
+                    'ttl': record['ttl'],
+                    'content': record['content']
+                }
+                records.append(processed_record)
 
         if rtype:
             records = [record for record in records if record['type'] == rtype]

{"golden_diff": "diff --git a/lexicon/providers/luadns.py b/lexicon/providers/luadns.py\n--- a/lexicon/providers/luadns.py\n+++ b/lexicon/providers/luadns.py\n@@ -63,14 +63,15 @@\n \n         records = []\n         for record in payload:\n-            processed_record = {\n-                'type': record['type'],\n-                'name': self._full_name(record['name']),\n-                'ttl': record['ttl'],\n-                'content': record['content'],\n-                'id': record['id']\n-            }\n-            records.append(processed_record)\n+            if 'id' in record:\n+                processed_record = {\n+                    'id': record['id'],\n+                    'type': record['type'],\n+                    'name': self._full_name(record['name']),\n+                    'ttl': record['ttl'],\n+                    'content': record['content']\n+                }\n+                records.append(processed_record)\n \n         if rtype:\n             records = [record for record in records if record['type'] == rtype]\n", "issue": "LuaDNS _list_records fails due to missing \"id\" in API response\nI'm using certbot with the luadns plugin which in turn uses lexicon.\r\nI've been experiencing the error reported here: https://community.letsencrypt.org/t/luadns-renew-error/89634\r\n\r\n```\r\n2019-08-29 06:32:43,517:DEBUG:urllib3.connectionpool:https://api.luadns.com:443 \"GET /v1/zones/[XXXXXX]/records HTTP/1.1\" 200 None\r\n2019-08-29 06:32:43,520:ERROR:certbot.error_handler:Encountered exception during recovery: \r\nTraceback (most recent call last):\r\n  File \"/home/ubuntu/.local/lib/python2.7/site-packages/certbot/error_handler.py\", line 124, in _call_registered\r\n    self.funcs[-1]()\r\n  File \"/home/ubuntu/.local/lib/python2.7/site-packages/certbot/auth_handler.py\", line 220, in _cleanup_challenges\r\n    self.auth.cleanup(achalls)\r\n  File \"/home/ubuntu/.local/lib/python2.7/site-packages/certbot/plugins/dns_common.py\", line 77, in cleanup\r\n    self._cleanup(domain, validation_domain_name, validation)\r\n  File \"/usr/local/lib/python2.7/dist-packages/certbot_dns_luadns/dns_luadns.py\", line 55, in _cleanup\r\n    self._get_luadns_client().del_txt_record(domain, validation_name, validation)\r\n  File \"/home/ubuntu/.local/lib/python2.7/site-packages/certbot/plugins/dns_common_lexicon.py\", line 65, in del_txt_record\r\n    self.provider.delete_record(type='TXT', name=record_name, content=record_content)\r\n  File \"/usr/local/lib/python2.7/dist-packages/lexicon/providers/base.py\", line 118, in delete_record\r\n    return self._delete_record(identifier=identifier, rtype=rtype, name=name, content=content)\r\n  File \"/usr/local/lib/python2.7/dist-packages/lexicon/providers/luadns.py\", line 111, in _delete_record\r\n    records = self._list_records(rtype, name, content)\r\n  File \"/usr/local/lib/python2.7/dist-packages/lexicon/providers/luadns.py\", line 71, in _list_records\r\n    'id': record['id']\r\nKeyError: 'id'\r\n```\r\nI've replaced the zone id with [XXXXXX].\r\n\r\nCode line that errors: https://github.com/AnalogJ/lexicon/blob/master/lexicon/providers/luadns.py#L71\r\n\r\nRunning the call directly towards the LuaDNS api shows that my REDIRECT record does not return an \"id\" -> no wonder it fails.\r\n\r\n```\r\n...\r\n[\r\n    {\r\n        \"id\": XXXXXXXXX,\r\n        \"name\": \"sub.some.domain.\",\r\n        \"type\": \"A\",\r\n        \"content\": \"111.111.111.111\",\r\n        \"ttl\": 300,\r\n        \"zone_id\": XXXXX,\r\n        \"generated\": false,\r\n        \"created_at\": \"2019-08-27T05:59:34.219211Z\",\r\n        \"updated_at\": \"2019-08-27T05:59:34.219213Z\"\r\n    },\r\n    {\r\n        \"name\": \"some.domain.\",\r\n        \"type\": \"REDIRECT\",\r\n        \"content\": \"1 https://sub.some.domain\",\r\n        \"ttl\": 300,\r\n        \"zone_id\": XXXXX,\r\n        \"generated\": false,\r\n        \"created_at\": \"2019-08-29T07:05:09.757404499Z\",\r\n        \"updated_at\": \"2019-08-29T07:05:09.757406053Z\"\r\n    }\r\n]\r\n...\r\n```\r\n\r\nMy question is: How do I go forward with this? (for now, I've just deleted the REDIRECT record and everything works fine)\r\n1) Send PR to lexicon to ignore records that does not have id? ( if 'id' in record )\r\n2) Reach out to LuaDNS for API fix?\r\n\r\nAny input would be great :)\n", "before_files": [{"content": "\"\"\"Module provider for luadns\"\"\"\nfrom __future__ import absolute_import\nimport json\nimport logging\n\nimport requests\nfrom lexicon.providers.base import Provider as BaseProvider\n\n\nLOGGER = logging.getLogger(__name__)\n\nNAMESERVER_DOMAINS = ['luadns.com']\n\n\ndef provider_parser(subparser):\n    \"\"\"Configure provider parser for luadns\"\"\"\n    subparser.add_argument(\n        \"--auth-username\", help=\"specify email address for authentication\")\n    subparser.add_argument(\n        \"--auth-token\", help=\"specify token for authentication\")\n\n\nclass Provider(BaseProvider):\n    \"\"\"Provider class for luadns\"\"\"\n    def __init__(self, config):\n        super(Provider, self).__init__(config)\n        self.domain_id = None\n        self.api_endpoint = 'https://api.luadns.com/v1'\n\n    def _authenticate(self):\n        payload = self._get('/zones')\n\n        domain_info = next(\n            (domain for domain in payload if domain['name'] == self.domain), None)\n\n        if not domain_info:\n            raise Exception('No domain found')\n\n        self.domain_id = domain_info['id']\n\n    # Create record. If record already exists with the same content, do nothing'\n\n    def _create_record(self, rtype, name, content):\n        # check if record already exists\n        existing_records = self._list_records(rtype, name, content)\n        if len(existing_records) == 1:\n            return True\n\n        self._post('/zones/{0}/records'.format(self.domain_id),\n                   {'type': rtype,\n                    'name': self._fqdn_name(name),\n                    'content': content,\n                    'ttl': self._get_lexicon_option('ttl')})\n\n        LOGGER.debug('create_record: %s', True)\n        return True\n\n    # List all records. Return an empty list if no records found\n    # type, name and content are used to filter records.\n    # If possible filter during the query, otherwise filter after response is received.\n    def _list_records(self, rtype=None, name=None, content=None):\n        payload = self._get('/zones/{0}/records'.format(self.domain_id))\n\n        records = []\n        for record in payload:\n            processed_record = {\n                'type': record['type'],\n                'name': self._full_name(record['name']),\n                'ttl': record['ttl'],\n                'content': record['content'],\n                'id': record['id']\n            }\n            records.append(processed_record)\n\n        if rtype:\n            records = [record for record in records if record['type'] == rtype]\n        if name:\n            records = [record for record in records if record['name']\n                       == self._full_name(name)]\n        if content:\n            records = [\n                record for record in records if record['content'] == content]\n\n        LOGGER.debug('list_records: %s', records)\n        return records\n\n    # Create or update a record.\n    def _update_record(self, identifier, rtype=None, name=None, content=None):\n\n        data = {\n            'ttl': self._get_lexicon_option('ttl')\n        }\n        if rtype:\n            data['type'] = rtype\n        if name:\n            data['name'] = self._fqdn_name(name)\n        if content:\n            data['content'] = content\n\n        self._put(\n            '/zones/{0}/records/{1}'.format(self.domain_id, identifier), data)\n\n        LOGGER.debug('update_record: %s', True)\n        return True\n\n    # Delete an existing record.\n    # If record does not exist, do nothing.\n    def _delete_record(self, identifier=None, rtype=None, name=None, content=None):\n        delete_record_id = []\n        if not identifier:\n            records = self._list_records(rtype, name, content)\n            delete_record_id = [record['id'] for record in records]\n        else:\n            delete_record_id.append(identifier)\n\n        LOGGER.debug('delete_records: %s', delete_record_id)\n\n        for record_id in delete_record_id:\n            self._delete(\n                '/zones/{0}/records/{1}'.format(self.domain_id, record_id))\n\n        LOGGER.debug('delete_record: %s', True)\n        return True\n\n    # Helpers\n    def _request(self, action='GET', url='/', data=None, query_params=None):\n        if data is None:\n            data = {}\n        if query_params is None:\n            query_params = {}\n        response = requests.request(action, self.api_endpoint + url, params=query_params,\n                                    data=json.dumps(data),\n                                    auth=requests.auth.HTTPBasicAuth(self._get_provider_option(\n                                        'auth_username'), self._get_provider_option('auth_token')),\n                                    headers={\n                                        'Content-Type': 'application/json',\n                                        'Accept': 'application/json'\n                                    })\n        # if the request fails for any reason, throw an error.\n        response.raise_for_status()\n        return response.json()\n", "path": "lexicon/providers/luadns.py"}], "after_files": [{"content": "\"\"\"Module provider for luadns\"\"\"\nfrom __future__ import absolute_import\nimport json\nimport logging\n\nimport requests\nfrom lexicon.providers.base import Provider as BaseProvider\n\n\nLOGGER = logging.getLogger(__name__)\n\nNAMESERVER_DOMAINS = ['luadns.com']\n\n\ndef provider_parser(subparser):\n    \"\"\"Configure provider parser for luadns\"\"\"\n    subparser.add_argument(\n        \"--auth-username\", help=\"specify email address for authentication\")\n    subparser.add_argument(\n        \"--auth-token\", help=\"specify token for authentication\")\n\n\nclass Provider(BaseProvider):\n    \"\"\"Provider class for luadns\"\"\"\n    def __init__(self, config):\n        super(Provider, self).__init__(config)\n        self.domain_id = None\n        self.api_endpoint = 'https://api.luadns.com/v1'\n\n    def _authenticate(self):\n        payload = self._get('/zones')\n\n        domain_info = next(\n            (domain for domain in payload if domain['name'] == self.domain), None)\n\n        if not domain_info:\n            raise Exception('No domain found')\n\n        self.domain_id = domain_info['id']\n\n    # Create record. If record already exists with the same content, do nothing'\n\n    def _create_record(self, rtype, name, content):\n        # check if record already exists\n        existing_records = self._list_records(rtype, name, content)\n        if len(existing_records) == 1:\n            return True\n\n        self._post('/zones/{0}/records'.format(self.domain_id),\n                   {'type': rtype,\n                    'name': self._fqdn_name(name),\n                    'content': content,\n                    'ttl': self._get_lexicon_option('ttl')})\n\n        LOGGER.debug('create_record: %s', True)\n        return True\n\n    # List all records. Return an empty list if no records found\n    # type, name and content are used to filter records.\n    # If possible filter during the query, otherwise filter after response is received.\n    def _list_records(self, rtype=None, name=None, content=None):\n        payload = self._get('/zones/{0}/records'.format(self.domain_id))\n\n        records = []\n        for record in payload:\n            if 'id' in record:\n                processed_record = {\n                    'id': record['id'],\n                    'type': record['type'],\n                    'name': self._full_name(record['name']),\n                    'ttl': record['ttl'],\n                    'content': record['content']\n                }\n                records.append(processed_record)\n\n        if rtype:\n            records = [record for record in records if record['type'] == rtype]\n        if name:\n            records = [record for record in records if record['name']\n                       == self._full_name(name)]\n        if content:\n            records = [\n                record for record in records if record['content'] == content]\n\n        LOGGER.debug('list_records: %s', records)\n        return records\n\n    # Create or update a record.\n    def _update_record(self, identifier, rtype=None, name=None, content=None):\n\n        data = {\n            'ttl': self._get_lexicon_option('ttl')\n        }\n        if rtype:\n            data['type'] = rtype\n        if name:\n            data['name'] = self._fqdn_name(name)\n        if content:\n            data['content'] = content\n\n        self._put(\n            '/zones/{0}/records/{1}'.format(self.domain_id, identifier), data)\n\n        LOGGER.debug('update_record: %s', True)\n        return True\n\n    # Delete an existing record.\n    # If record does not exist, do nothing.\n    def _delete_record(self, identifier=None, rtype=None, name=None, content=None):\n        delete_record_id = []\n        if not identifier:\n            records = self._list_records(rtype, name, content)\n            delete_record_id = [record['id'] for record in records]\n        else:\n            delete_record_id.append(identifier)\n\n        LOGGER.debug('delete_records: %s', delete_record_id)\n\n        for record_id in delete_record_id:\n            self._delete(\n                '/zones/{0}/records/{1}'.format(self.domain_id, record_id))\n\n        LOGGER.debug('delete_record: %s', True)\n        return True\n\n    # Helpers\n    def _request(self, action='GET', url='/', data=None, query_params=None):\n        if data is None:\n            data = {}\n        if query_params is None:\n            query_params = {}\n        response = requests.request(action, self.api_endpoint + url, params=query_params,\n                                    data=json.dumps(data),\n                                    auth=requests.auth.HTTPBasicAuth(self._get_provider_option(\n                                        'auth_username'), self._get_provider_option('auth_token')),\n                                    headers={\n                                        'Content-Type': 'application/json',\n                                        'Accept': 'application/json'\n                                    })\n        # if the request fails for any reason, throw an error.\n        response.raise_for_status()\n        return response.json()\n", "path": "lexicon/providers/luadns.py"}]}