We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
GoDaddy provider should recognize domaincontrol.com as its nameserver
For the auto provider, it should recognize that domains managed by GoDaddy often have nameservers under the *.domaincontrol.com namespace.  You can verify this is GoDaddy via whois; and I also tested this by adding 'domaincontrol.com' to the recognized nameservers with the following patch.

```
--- providers/godaddy.py.orig	2020-01-09 08:58:26.160360574 +0000
+++ providers/godaddy.py	2020-01-10 19:27:29.292030195 +0000
@@ -14,5 +14,5 @@
 LOGGER = logging.getLogger(__name__)
 
-NAMESERVER_DOMAINS = ['godaddy.com']
+NAMESERVER_DOMAINS = ['godaddy.com','domaincontrol.com']
```

And the current whois excerpt:

```
$ whois domaincontrol.com
Domain Name: DOMAINCONTROL.COM
...
Updated Date: 2018-08-07T19:25:37Z
...
Registrant Organization: Go Daddy Operating Company, LLC
Registrant State/Province: Arizona
Registrant Country: US
```



--- 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/godaddy.py`
Content:
```
1 """Module provider for Godaddy"""
2 from __future__ import absolute_import
3 import hashlib
4 import json
5 import logging
6 
7 import requests
8 from requests.adapters import HTTPAdapter
9 from urllib3.util.retry import Retry
10 
11 from lexicon.providers.base import Provider as BaseProvider
12 
13 
14 LOGGER = logging.getLogger(__name__)
15 
16 NAMESERVER_DOMAINS = ['godaddy.com']
17 
18 
19 def provider_parser(subparser):
20     """Generate a subparser for Godaddy"""
21     subparser.add_argument(
22         '--auth-key', help='specify the key to access the API')
23     subparser.add_argument(
24         '--auth-secret', help='specify the secret to access the API')
25 
26 
27 class Provider(BaseProvider):
28     """
29     Implements the DNS GoDaddy provider.
30     Some general remarks about this provider, because it uses a weirdly designed API.
31     Indeed, there is no direct way to insert, update or delete a specific record.
32     Furthermore, there is no unique identifier for a record.
33     Instead GoDaddy use a replace approach: for a given set of records one
34     can replace this set with a new set sent through API.
35     For the sake of simplicity and consistency across the provider edit methods,
36     the set will be always all records in the DNS zone.
37     With this approach:
38         - adding a record consists in appending a record to the obtained set and call
39           replace with the updated set,
40         - updating a record consists in modifying a record in the obtained set and call
41           replace with the updated set,
42         - deleting a record consists in removing a record in the obtained set and call
43           replace with the updated set.
44     In parallel, as said before, there is no unique identifier.
45     This provider then implement a pseudo-identifier, to allow an easy update or delete
46     using the '--identifier' lexicon parameter.
47     But you need to call the 'list' command just before executing and update/delete action,
48     because identifier value is tied to the content of the record, and will change anytime
49     something is changed in the record.
50     """
51 
52     def __init__(self, config):
53         super(Provider, self).__init__(config)
54         self.domain_id = None
55         self.api_endpoint = 'https://api.godaddy.com/v1'
56 
57     def _authenticate(self):
58         domain = self.domain
59 
60         result = self._get('/domains/{0}'.format(domain))
61         self.domain_id = result['domainId']
62 
63     def _list_records(self, rtype=None, name=None, content=None):
64         domain = self.domain
65 
66         url = '/domains/{0}/records'.format(domain)
67         if rtype:
68             url += '/{0}'.format(rtype)
69         if name:
70             url += '/{0}'.format(self._relative_name(name))
71 
72         raws = self._get(url)
73 
74         records = []
75         for raw in raws:
76             records.append({
77                 'id': Provider._identifier(raw),
78                 'type': raw['type'],
79                 'name': self._full_name(raw['name']),
80                 'ttl': raw['ttl'],
81                 'content': raw['data']
82             })
83 
84         if content:
85             records = [
86                 record for record in records if record['data'] == content]
87 
88         LOGGER.debug('list_records: %s', records)
89 
90         return records
91 
92     def _create_record(self, rtype, name, content):
93         domain = self.domain
94         relative_name = self._relative_name(name)
95         ttl = self._get_lexicon_option('ttl')
96 
97         # Retrieve existing data in DNS zone.
98         records = self._get('/domains/{0}/records'.format(domain))
99 
100         # Check if a record already matches given parameters
101         for record in records:
102             if (record['type'] == rtype and self._relative_name(record['name']) == relative_name
103                     and record['data'] == content):
104                 LOGGER.debug(
105                     'create_record (ignored, duplicate): %s %s %s', rtype, name, content)
106                 return True
107 
108         # Append a new entry corresponding to given parameters.
109         data = {'type': rtype, 'name': relative_name, 'data': content}
110         if ttl:
111             data['ttl'] = ttl
112 
113         records.append(data)
114 
115         # Synchronize data with inserted record into DNS zone.
116         self._put('/domains/{0}/records'.format(domain), records)
117 
118         LOGGER.debug('create_record: %s %s %s', rtype, name, content)
119 
120         return True
121 
122     def _update_record(self, identifier, rtype=None, name=None, content=None):
123         # No identifier is used with GoDaddy.
124         # We can rely either:
125         #   - only on rtype/name to get the relevant records, both of them are required
126         #     or we will could update to much records ...,
127         #   - or by the pseudo-identifier provided
128         # Furthermore for rtype/name approach, we cannot update all matching records, as it
129         # would lead o an error (two entries of same rtype + name cannot have the same content).
130         # So for rtype/name approach, we search first matching record for rtype/name on which
131         # content is different, and we update it before synchronizing the DNS zone.
132         if not identifier and not rtype:
133             raise Exception('ERROR: rtype is required')
134         if not identifier and not name:
135             raise Exception('ERROR: name is required')
136 
137         domain = self.domain
138         relative_name = None
139         if name:
140             relative_name = self._relative_name(name)
141 
142         # Retrieve existing data in DNS zone.
143         records = self._get('/domains/{0}/records'.format(domain))
144 
145         # Get the record to update:
146         #   - either explicitly by its identifier,
147         #   - or the first matching by its rtype+name where content does not match
148         #     (first match, see first method comment for explanation).
149         for record in records:
150             if ((identifier and Provider._identifier(record) == identifier) or  # pylint: disable=too-many-boolean-expressions
151                     (not identifier and record['type'] == rtype
152                      and self._relative_name(record['name']) == relative_name
153                      and record['data'] != content)):
154                 record['data'] = content
155                 break
156 
157         # Synchronize data with updated records into DNS zone.
158         self._put('/domains/{0}/records'.format(domain), records)
159 
160         LOGGER.debug('update_record: %s %s %s', rtype, name, content)
161 
162         return True
163 
164     def _delete_record(self, identifier=None, rtype=None, name=None, content=None):
165         # For the LOL. GoDaddy does not accept an empty array
166         # when updating a particular set of records.
167         # It means that you cannot request to remove all records
168         # matching a particular rtype and/or name.
169         # Instead, we get ALL records in the DNS zone, update the set,
170         # and replace EVERYTHING in the DNS zone.
171         # You will always have at minimal NS/SRV entries in the array,
172         # otherwise your DNS zone is broken, and updating the zone is the least of your problem ...
173         domain = self.domain
174 
175         # Retrieve all records in the DNS zone
176         records = self._get('/domains/{0}/records'.format(domain))
177 
178         relative_name = None
179         if name:
180             relative_name = self._relative_name(name)
181 
182         # Filter out all records which matches the pattern (either identifier
183         # or some combination of rtype/name/content).
184         filtered_records = []
185         if identifier:
186             filtered_records = [
187                 record for record in records if Provider._identifier(record) != identifier]
188         else:
189             for record in records:
190                 if ((not rtype and not relative_name and not content)  # pylint: disable=too-many-boolean-expressions
191                         or (rtype and not relative_name and not content and record['type'] != rtype)
192                         or (not rtype and relative_name and not content
193                             and self._relative_name(record['name']) != relative_name)
194                         or (not rtype and not relative_name and content
195                             and record['data'] != content)
196                         or (rtype and relative_name and not content
197                             and (record['type'] != rtype
198                                  or self._relative_name(record['name']) != relative_name))
199                         or (rtype and not relative_name and content
200                             and (record['type'] != rtype or record['data'] != content))
201                         or (not rtype and relative_name and content
202                             and (self._relative_name(record['name']) != relative_name
203                                  or record['data'] != content))
204                         or (rtype and relative_name and content
205                             and (record['type'] != rtype
206                                  or self._relative_name(record['name']) != relative_name
207                                  or record['data'] != content))):
208                     filtered_records.append(record)
209 
210         # Synchronize data with expurged entries into DNS zone.
211         self._put('/domains/{0}/records'.format(domain), filtered_records)
212 
213         LOGGER.debug('delete_records: %s %s %s', rtype, name, content)
214 
215         return True
216 
217     # GoDaddy provides no identifier for a record, which is a problem
218     # where identifiers can be used (delete and update).
219     # To circumvent this, we implement a pseudo-identifier,which is basically
220     # a hash of type+name+content of a given record.
221     # It is far from perfect, as the identifier will change each time
222     # we change something in the record ...
223     # But at least, one can use 'lexicon godaddy list ...' then
224     # 'lexicon godaddy update --identifier ...' to modify specific record.
225     # However, 'lexicon godaddy list ...' should be called each time DNS
226     # zone had been changed to calculate new identifiers.
227     @staticmethod
228     def _identifier(record):
229         sha256 = hashlib.sha256()
230         sha256.update(('type=' + record.get('type', '') + ',').encode('utf-8'))
231         sha256.update(('name=' + record.get('name', '') + ',').encode('utf-8'))
232         sha256.update(('data=' + record.get('data', '') + ',').encode('utf-8'))
233         return sha256.hexdigest()[0:7]
234 
235     def _request(self, action='GET', url='/', data=None, query_params=None):
236         if not data:
237             data = {}
238         if not query_params:
239             query_params = {}
240 
241         # When editing DNS zone, API is unavailable for few seconds
242         # (until modifications are propagated).
243         # In this case, call to API will return 409 HTTP error.
244         # We use the Retry extension to retry the requests until
245         # we get a processable reponse (402 HTTP status, or an HTTP error != 409)
246         retries = Retry(
247             total=10,
248             backoff_factor=0.5,
249             status_forcelist=[409],
250             method_whitelist=frozenset(
251                 ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'])
252         )
253 
254         session = requests.Session()
255         session.mount('https://', HTTPAdapter(max_retries=retries))
256 
257         result = session.request(action, self.api_endpoint + url,
258                                  params=query_params,
259                                  data=json.dumps(data),
260                                  headers={
261                                      'Content-Type': 'application/json',
262                                      'Accept': 'application/json',
263                                      # GoDaddy use a key/secret pair to authenticate
264                                      'Authorization': 'sso-key {0}:{1}'.format(
265                                          self._get_provider_option(
266                                              'auth_key'),
267                                          self._get_provider_option('auth_secret'))
268                                  })
269 
270         result.raise_for_status()
271 
272         try:
273             # Return the JSON body response if exists.
274             return result.json()
275         except ValueError:
276             # For some requests command (eg. PUT), GoDaddy will not
277             # return any JSON, just an HTTP status without body.
278             return None
279
```
--- 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/godaddy.py b/lexicon/providers/godaddy.py
--- a/lexicon/providers/godaddy.py
+++ b/lexicon/providers/godaddy.py
@@ -13,7 +13,7 @@
 
 LOGGER = logging.getLogger(__name__)
 
-NAMESERVER_DOMAINS = ['godaddy.com']
+NAMESERVER_DOMAINS = ['godaddy.com', 'domaincontrol.com']
 
 
 def provider_parser(subparser):

{"golden_diff": "diff --git a/lexicon/providers/godaddy.py b/lexicon/providers/godaddy.py\n--- a/lexicon/providers/godaddy.py\n+++ b/lexicon/providers/godaddy.py\n@@ -13,7 +13,7 @@\n \n LOGGER = logging.getLogger(__name__)\n \n-NAMESERVER_DOMAINS = ['godaddy.com']\n+NAMESERVER_DOMAINS = ['godaddy.com', 'domaincontrol.com']\n \n \n def provider_parser(subparser):\n", "issue": "GoDaddy provider should recognize domaincontrol.com as its nameserver\nFor the auto provider, it should recognize that domains managed by GoDaddy often have nameservers under the *.domaincontrol.com namespace.  You can verify this is GoDaddy via whois; and I also tested this by adding 'domaincontrol.com' to the recognized nameservers with the following patch.\r\n\r\n```\r\n--- providers/godaddy.py.orig\t2020-01-09 08:58:26.160360574 +0000\r\n+++ providers/godaddy.py\t2020-01-10 19:27:29.292030195 +0000\r\n@@ -14,5 +14,5 @@\r\n LOGGER = logging.getLogger(__name__)\r\n \r\n-NAMESERVER_DOMAINS = ['godaddy.com']\r\n+NAMESERVER_DOMAINS = ['godaddy.com','domaincontrol.com']\r\n```\r\n\r\nAnd the current whois excerpt:\r\n\r\n```\r\n$ whois domaincontrol.com\r\nDomain Name: DOMAINCONTROL.COM\r\n...\r\nUpdated Date: 2018-08-07T19:25:37Z\r\n...\r\nRegistrant Organization: Go Daddy Operating Company, LLC\r\nRegistrant State/Province: Arizona\r\nRegistrant Country: US\r\n```\r\n\r\n\n", "before_files": [{"content": "\"\"\"Module provider for Godaddy\"\"\"\nfrom __future__ import absolute_import\nimport hashlib\nimport json\nimport logging\n\nimport requests\nfrom requests.adapters import HTTPAdapter\nfrom urllib3.util.retry import Retry\n\nfrom lexicon.providers.base import Provider as BaseProvider\n\n\nLOGGER = logging.getLogger(__name__)\n\nNAMESERVER_DOMAINS = ['godaddy.com']\n\n\ndef provider_parser(subparser):\n    \"\"\"Generate a subparser for Godaddy\"\"\"\n    subparser.add_argument(\n        '--auth-key', help='specify the key to access the API')\n    subparser.add_argument(\n        '--auth-secret', help='specify the secret to access the API')\n\n\nclass Provider(BaseProvider):\n    \"\"\"\n    Implements the DNS GoDaddy provider.\n    Some general remarks about this provider, because it uses a weirdly designed API.\n    Indeed, there is no direct way to insert, update or delete a specific record.\n    Furthermore, there is no unique identifier for a record.\n    Instead GoDaddy use a replace approach: for a given set of records one\n    can replace this set with a new set sent through API.\n    For the sake of simplicity and consistency across the provider edit methods,\n    the set will be always all records in the DNS zone.\n    With this approach:\n        - adding a record consists in appending a record to the obtained set and call\n          replace with the updated set,\n        - updating a record consists in modifying a record in the obtained set and call\n          replace with the updated set,\n        - deleting a record consists in removing a record in the obtained set and call\n          replace with the updated set.\n    In parallel, as said before, there is no unique identifier.\n    This provider then implement a pseudo-identifier, to allow an easy update or delete\n    using the '--identifier' lexicon parameter.\n    But you need to call the 'list' command just before executing and update/delete action,\n    because identifier value is tied to the content of the record, and will change anytime\n    something is changed in the record.\n    \"\"\"\n\n    def __init__(self, config):\n        super(Provider, self).__init__(config)\n        self.domain_id = None\n        self.api_endpoint = 'https://api.godaddy.com/v1'\n\n    def _authenticate(self):\n        domain = self.domain\n\n        result = self._get('/domains/{0}'.format(domain))\n        self.domain_id = result['domainId']\n\n    def _list_records(self, rtype=None, name=None, content=None):\n        domain = self.domain\n\n        url = '/domains/{0}/records'.format(domain)\n        if rtype:\n            url += '/{0}'.format(rtype)\n        if name:\n            url += '/{0}'.format(self._relative_name(name))\n\n        raws = self._get(url)\n\n        records = []\n        for raw in raws:\n            records.append({\n                'id': Provider._identifier(raw),\n                'type': raw['type'],\n                'name': self._full_name(raw['name']),\n                'ttl': raw['ttl'],\n                'content': raw['data']\n            })\n\n        if content:\n            records = [\n                record for record in records if record['data'] == content]\n\n        LOGGER.debug('list_records: %s', records)\n\n        return records\n\n    def _create_record(self, rtype, name, content):\n        domain = self.domain\n        relative_name = self._relative_name(name)\n        ttl = self._get_lexicon_option('ttl')\n\n        # Retrieve existing data in DNS zone.\n        records = self._get('/domains/{0}/records'.format(domain))\n\n        # Check if a record already matches given parameters\n        for record in records:\n            if (record['type'] == rtype and self._relative_name(record['name']) == relative_name\n                    and record['data'] == content):\n                LOGGER.debug(\n                    'create_record (ignored, duplicate): %s %s %s', rtype, name, content)\n                return True\n\n        # Append a new entry corresponding to given parameters.\n        data = {'type': rtype, 'name': relative_name, 'data': content}\n        if ttl:\n            data['ttl'] = ttl\n\n        records.append(data)\n\n        # Synchronize data with inserted record into DNS zone.\n        self._put('/domains/{0}/records'.format(domain), records)\n\n        LOGGER.debug('create_record: %s %s %s', rtype, name, content)\n\n        return True\n\n    def _update_record(self, identifier, rtype=None, name=None, content=None):\n        # No identifier is used with GoDaddy.\n        # We can rely either:\n        #   - only on rtype/name to get the relevant records, both of them are required\n        #     or we will could update to much records ...,\n        #   - or by the pseudo-identifier provided\n        # Furthermore for rtype/name approach, we cannot update all matching records, as it\n        # would lead o an error (two entries of same rtype + name cannot have the same content).\n        # So for rtype/name approach, we search first matching record for rtype/name on which\n        # content is different, and we update it before synchronizing the DNS zone.\n        if not identifier and not rtype:\n            raise Exception('ERROR: rtype is required')\n        if not identifier and not name:\n            raise Exception('ERROR: name is required')\n\n        domain = self.domain\n        relative_name = None\n        if name:\n            relative_name = self._relative_name(name)\n\n        # Retrieve existing data in DNS zone.\n        records = self._get('/domains/{0}/records'.format(domain))\n\n        # Get the record to update:\n        #   - either explicitly by its identifier,\n        #   - or the first matching by its rtype+name where content does not match\n        #     (first match, see first method comment for explanation).\n        for record in records:\n            if ((identifier and Provider._identifier(record) == identifier) or  # pylint: disable=too-many-boolean-expressions\n                    (not identifier and record['type'] == rtype\n                     and self._relative_name(record['name']) == relative_name\n                     and record['data'] != content)):\n                record['data'] = content\n                break\n\n        # Synchronize data with updated records into DNS zone.\n        self._put('/domains/{0}/records'.format(domain), records)\n\n        LOGGER.debug('update_record: %s %s %s', rtype, name, content)\n\n        return True\n\n    def _delete_record(self, identifier=None, rtype=None, name=None, content=None):\n        # For the LOL. GoDaddy does not accept an empty array\n        # when updating a particular set of records.\n        # It means that you cannot request to remove all records\n        # matching a particular rtype and/or name.\n        # Instead, we get ALL records in the DNS zone, update the set,\n        # and replace EVERYTHING in the DNS zone.\n        # You will always have at minimal NS/SRV entries in the array,\n        # otherwise your DNS zone is broken, and updating the zone is the least of your problem ...\n        domain = self.domain\n\n        # Retrieve all records in the DNS zone\n        records = self._get('/domains/{0}/records'.format(domain))\n\n        relative_name = None\n        if name:\n            relative_name = self._relative_name(name)\n\n        # Filter out all records which matches the pattern (either identifier\n        # or some combination of rtype/name/content).\n        filtered_records = []\n        if identifier:\n            filtered_records = [\n                record for record in records if Provider._identifier(record) != identifier]\n        else:\n            for record in records:\n                if ((not rtype and not relative_name and not content)  # pylint: disable=too-many-boolean-expressions\n                        or (rtype and not relative_name and not content and record['type'] != rtype)\n                        or (not rtype and relative_name and not content\n                            and self._relative_name(record['name']) != relative_name)\n                        or (not rtype and not relative_name and content\n                            and record['data'] != content)\n                        or (rtype and relative_name and not content\n                            and (record['type'] != rtype\n                                 or self._relative_name(record['name']) != relative_name))\n                        or (rtype and not relative_name and content\n                            and (record['type'] != rtype or record['data'] != content))\n                        or (not rtype and relative_name and content\n                            and (self._relative_name(record['name']) != relative_name\n                                 or record['data'] != content))\n                        or (rtype and relative_name and content\n                            and (record['type'] != rtype\n                                 or self._relative_name(record['name']) != relative_name\n                                 or record['data'] != content))):\n                    filtered_records.append(record)\n\n        # Synchronize data with expurged entries into DNS zone.\n        self._put('/domains/{0}/records'.format(domain), filtered_records)\n\n        LOGGER.debug('delete_records: %s %s %s', rtype, name, content)\n\n        return True\n\n    # GoDaddy provides no identifier for a record, which is a problem\n    # where identifiers can be used (delete and update).\n    # To circumvent this, we implement a pseudo-identifier,which is basically\n    # a hash of type+name+content of a given record.\n    # It is far from perfect, as the identifier will change each time\n    # we change something in the record ...\n    # But at least, one can use 'lexicon godaddy list ...' then\n    # 'lexicon godaddy update --identifier ...' to modify specific record.\n    # However, 'lexicon godaddy list ...' should be called each time DNS\n    # zone had been changed to calculate new identifiers.\n    @staticmethod\n    def _identifier(record):\n        sha256 = hashlib.sha256()\n        sha256.update(('type=' + record.get('type', '') + ',').encode('utf-8'))\n        sha256.update(('name=' + record.get('name', '') + ',').encode('utf-8'))\n        sha256.update(('data=' + record.get('data', '') + ',').encode('utf-8'))\n        return sha256.hexdigest()[0:7]\n\n    def _request(self, action='GET', url='/', data=None, query_params=None):\n        if not data:\n            data = {}\n        if not query_params:\n            query_params = {}\n\n        # When editing DNS zone, API is unavailable for few seconds\n        # (until modifications are propagated).\n        # In this case, call to API will return 409 HTTP error.\n        # We use the Retry extension to retry the requests until\n        # we get a processable reponse (402 HTTP status, or an HTTP error != 409)\n        retries = Retry(\n            total=10,\n            backoff_factor=0.5,\n            status_forcelist=[409],\n            method_whitelist=frozenset(\n                ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'])\n        )\n\n        session = requests.Session()\n        session.mount('https://', HTTPAdapter(max_retries=retries))\n\n        result = session.request(action, self.api_endpoint + url,\n                                 params=query_params,\n                                 data=json.dumps(data),\n                                 headers={\n                                     'Content-Type': 'application/json',\n                                     'Accept': 'application/json',\n                                     # GoDaddy use a key/secret pair to authenticate\n                                     'Authorization': 'sso-key {0}:{1}'.format(\n                                         self._get_provider_option(\n                                             'auth_key'),\n                                         self._get_provider_option('auth_secret'))\n                                 })\n\n        result.raise_for_status()\n\n        try:\n            # Return the JSON body response if exists.\n            return result.json()\n        except ValueError:\n            # For some requests command (eg. PUT), GoDaddy will not\n            # return any JSON, just an HTTP status without body.\n            return None\n", "path": "lexicon/providers/godaddy.py"}], "after_files": [{"content": "\"\"\"Module provider for Godaddy\"\"\"\nfrom __future__ import absolute_import\nimport hashlib\nimport json\nimport logging\n\nimport requests\nfrom requests.adapters import HTTPAdapter\nfrom urllib3.util.retry import Retry\n\nfrom lexicon.providers.base import Provider as BaseProvider\n\n\nLOGGER = logging.getLogger(__name__)\n\nNAMESERVER_DOMAINS = ['godaddy.com', 'domaincontrol.com']\n\n\ndef provider_parser(subparser):\n    \"\"\"Generate a subparser for Godaddy\"\"\"\n    subparser.add_argument(\n        '--auth-key', help='specify the key to access the API')\n    subparser.add_argument(\n        '--auth-secret', help='specify the secret to access the API')\n\n\nclass Provider(BaseProvider):\n    \"\"\"\n    Implements the DNS GoDaddy provider.\n    Some general remarks about this provider, because it uses a weirdly designed API.\n    Indeed, there is no direct way to insert, update or delete a specific record.\n    Furthermore, there is no unique identifier for a record.\n    Instead GoDaddy use a replace approach: for a given set of records one\n    can replace this set with a new set sent through API.\n    For the sake of simplicity and consistency across the provider edit methods,\n    the set will be always all records in the DNS zone.\n    With this approach:\n        - adding a record consists in appending a record to the obtained set and call\n          replace with the updated set,\n        - updating a record consists in modifying a record in the obtained set and call\n          replace with the updated set,\n        - deleting a record consists in removing a record in the obtained set and call\n          replace with the updated set.\n    In parallel, as said before, there is no unique identifier.\n    This provider then implement a pseudo-identifier, to allow an easy update or delete\n    using the '--identifier' lexicon parameter.\n    But you need to call the 'list' command just before executing and update/delete action,\n    because identifier value is tied to the content of the record, and will change anytime\n    something is changed in the record.\n    \"\"\"\n\n    def __init__(self, config):\n        super(Provider, self).__init__(config)\n        self.domain_id = None\n        self.api_endpoint = 'https://api.godaddy.com/v1'\n\n    def _authenticate(self):\n        domain = self.domain\n\n        result = self._get('/domains/{0}'.format(domain))\n        self.domain_id = result['domainId']\n\n    def _list_records(self, rtype=None, name=None, content=None):\n        domain = self.domain\n\n        url = '/domains/{0}/records'.format(domain)\n        if rtype:\n            url += '/{0}'.format(rtype)\n        if name:\n            url += '/{0}'.format(self._relative_name(name))\n\n        raws = self._get(url)\n\n        records = []\n        for raw in raws:\n            records.append({\n                'id': Provider._identifier(raw),\n                'type': raw['type'],\n                'name': self._full_name(raw['name']),\n                'ttl': raw['ttl'],\n                'content': raw['data']\n            })\n\n        if content:\n            records = [\n                record for record in records if record['data'] == content]\n\n        LOGGER.debug('list_records: %s', records)\n\n        return records\n\n    def _create_record(self, rtype, name, content):\n        domain = self.domain\n        relative_name = self._relative_name(name)\n        ttl = self._get_lexicon_option('ttl')\n\n        # Retrieve existing data in DNS zone.\n        records = self._get('/domains/{0}/records'.format(domain))\n\n        # Check if a record already matches given parameters\n        for record in records:\n            if (record['type'] == rtype and self._relative_name(record['name']) == relative_name\n                    and record['data'] == content):\n                LOGGER.debug(\n                    'create_record (ignored, duplicate): %s %s %s', rtype, name, content)\n                return True\n\n        # Append a new entry corresponding to given parameters.\n        data = {'type': rtype, 'name': relative_name, 'data': content}\n        if ttl:\n            data['ttl'] = ttl\n\n        records.append(data)\n\n        # Synchronize data with inserted record into DNS zone.\n        self._put('/domains/{0}/records'.format(domain), records)\n\n        LOGGER.debug('create_record: %s %s %s', rtype, name, content)\n\n        return True\n\n    def _update_record(self, identifier, rtype=None, name=None, content=None):\n        # No identifier is used with GoDaddy.\n        # We can rely either:\n        #   - only on rtype/name to get the relevant records, both of them are required\n        #     or we will could update to much records ...,\n        #   - or by the pseudo-identifier provided\n        # Furthermore for rtype/name approach, we cannot update all matching records, as it\n        # would lead o an error (two entries of same rtype + name cannot have the same content).\n        # So for rtype/name approach, we search first matching record for rtype/name on which\n        # content is different, and we update it before synchronizing the DNS zone.\n        if not identifier and not rtype:\n            raise Exception('ERROR: rtype is required')\n        if not identifier and not name:\n            raise Exception('ERROR: name is required')\n\n        domain = self.domain\n        relative_name = None\n        if name:\n            relative_name = self._relative_name(name)\n\n        # Retrieve existing data in DNS zone.\n        records = self._get('/domains/{0}/records'.format(domain))\n\n        # Get the record to update:\n        #   - either explicitly by its identifier,\n        #   - or the first matching by its rtype+name where content does not match\n        #     (first match, see first method comment for explanation).\n        for record in records:\n            if ((identifier and Provider._identifier(record) == identifier) or  # pylint: disable=too-many-boolean-expressions\n                    (not identifier and record['type'] == rtype\n                     and self._relative_name(record['name']) == relative_name\n                     and record['data'] != content)):\n                record['data'] = content\n                break\n\n        # Synchronize data with updated records into DNS zone.\n        self._put('/domains/{0}/records'.format(domain), records)\n\n        LOGGER.debug('update_record: %s %s %s', rtype, name, content)\n\n        return True\n\n    def _delete_record(self, identifier=None, rtype=None, name=None, content=None):\n        # For the LOL. GoDaddy does not accept an empty array\n        # when updating a particular set of records.\n        # It means that you cannot request to remove all records\n        # matching a particular rtype and/or name.\n        # Instead, we get ALL records in the DNS zone, update the set,\n        # and replace EVERYTHING in the DNS zone.\n        # You will always have at minimal NS/SRV entries in the array,\n        # otherwise your DNS zone is broken, and updating the zone is the least of your problem ...\n        domain = self.domain\n\n        # Retrieve all records in the DNS zone\n        records = self._get('/domains/{0}/records'.format(domain))\n\n        relative_name = None\n        if name:\n            relative_name = self._relative_name(name)\n\n        # Filter out all records which matches the pattern (either identifier\n        # or some combination of rtype/name/content).\n        filtered_records = []\n        if identifier:\n            filtered_records = [\n                record for record in records if Provider._identifier(record) != identifier]\n        else:\n            for record in records:\n                if ((not rtype and not relative_name and not content)  # pylint: disable=too-many-boolean-expressions\n                        or (rtype and not relative_name and not content and record['type'] != rtype)\n                        or (not rtype and relative_name and not content\n                            and self._relative_name(record['name']) != relative_name)\n                        or (not rtype and not relative_name and content\n                            and record['data'] != content)\n                        or (rtype and relative_name and not content\n                            and (record['type'] != rtype\n                                 or self._relative_name(record['name']) != relative_name))\n                        or (rtype and not relative_name and content\n                            and (record['type'] != rtype or record['data'] != content))\n                        or (not rtype and relative_name and content\n                            and (self._relative_name(record['name']) != relative_name\n                                 or record['data'] != content))\n                        or (rtype and relative_name and content\n                            and (record['type'] != rtype\n                                 or self._relative_name(record['name']) != relative_name\n                                 or record['data'] != content))):\n                    filtered_records.append(record)\n\n        # Synchronize data with expurged entries into DNS zone.\n        self._put('/domains/{0}/records'.format(domain), filtered_records)\n\n        LOGGER.debug('delete_records: %s %s %s', rtype, name, content)\n\n        return True\n\n    # GoDaddy provides no identifier for a record, which is a problem\n    # where identifiers can be used (delete and update).\n    # To circumvent this, we implement a pseudo-identifier,which is basically\n    # a hash of type+name+content of a given record.\n    # It is far from perfect, as the identifier will change each time\n    # we change something in the record ...\n    # But at least, one can use 'lexicon godaddy list ...' then\n    # 'lexicon godaddy update --identifier ...' to modify specific record.\n    # However, 'lexicon godaddy list ...' should be called each time DNS\n    # zone had been changed to calculate new identifiers.\n    @staticmethod\n    def _identifier(record):\n        sha256 = hashlib.sha256()\n        sha256.update(('type=' + record.get('type', '') + ',').encode('utf-8'))\n        sha256.update(('name=' + record.get('name', '') + ',').encode('utf-8'))\n        sha256.update(('data=' + record.get('data', '') + ',').encode('utf-8'))\n        return sha256.hexdigest()[0:7]\n\n    def _request(self, action='GET', url='/', data=None, query_params=None):\n        if not data:\n            data = {}\n        if not query_params:\n            query_params = {}\n\n        # When editing DNS zone, API is unavailable for few seconds\n        # (until modifications are propagated).\n        # In this case, call to API will return 409 HTTP error.\n        # We use the Retry extension to retry the requests until\n        # we get a processable reponse (402 HTTP status, or an HTTP error != 409)\n        retries = Retry(\n            total=10,\n            backoff_factor=0.5,\n            status_forcelist=[409],\n            method_whitelist=frozenset(\n                ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'])\n        )\n\n        session = requests.Session()\n        session.mount('https://', HTTPAdapter(max_retries=retries))\n\n        result = session.request(action, self.api_endpoint + url,\n                                 params=query_params,\n                                 data=json.dumps(data),\n                                 headers={\n                                     'Content-Type': 'application/json',\n                                     'Accept': 'application/json',\n                                     # GoDaddy use a key/secret pair to authenticate\n                                     'Authorization': 'sso-key {0}:{1}'.format(\n                                         self._get_provider_option(\n                                             'auth_key'),\n                                         self._get_provider_option('auth_secret'))\n                                 })\n\n        result.raise_for_status()\n\n        try:\n            # Return the JSON body response if exists.\n            return result.json()\n        except ValueError:\n            # For some requests command (eg. PUT), GoDaddy will not\n            # return any JSON, just an HTTP status without body.\n            return None\n", "path": "lexicon/providers/godaddy.py"}]}