octodns-cloudns 0.0.2__py3-none-any.whl → 0.0.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- octodns_cloudns/__init__.py +112 -105
- {octodns_cloudns-0.0.2.dist-info → octodns_cloudns-0.0.10.dist-info}/METADATA +23 -24
- octodns_cloudns-0.0.10.dist-info/RECORD +6 -0
- {octodns_cloudns-0.0.2.dist-info → octodns_cloudns-0.0.10.dist-info}/WHEEL +1 -1
- octodns_cloudns-0.0.2.dist-info/RECORD +0 -6
- {octodns_cloudns-0.0.2.dist-info → octodns_cloudns-0.0.10.dist-info}/LICENSE +0 -0
- {octodns_cloudns-0.0.2.dist-info → octodns_cloudns-0.0.10.dist-info}/top_level.txt +0 -0
octodns_cloudns/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ from octodns.record import Record
|
|
|
8
8
|
logging.basicConfig(level=logging.DEBUG)
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
|
-
__version__ = __VERSION__ = '0.0.
|
|
11
|
+
__version__ = __VERSION__ = '0.0.10'
|
|
12
12
|
|
|
13
13
|
class ClouDNSClientException(ProviderException):
|
|
14
14
|
pass
|
|
@@ -44,7 +44,8 @@ class ClouDNSClientGeoDNSNotSupported(ClouDNSClientException):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class ClouDNSClient(object):
|
|
47
|
-
def __init__(self, auth_id, auth_password, sub_auth=False):
|
|
47
|
+
def __init__(self, auth_id, auth_password, id, sub_auth=False):
|
|
48
|
+
self.log = getLogger(f"ClouDNSProvider[{id}]")
|
|
48
49
|
session = Session()
|
|
49
50
|
session.headers.update(
|
|
50
51
|
{
|
|
@@ -75,9 +76,9 @@ class ClouDNSClient(object):
|
|
|
75
76
|
|
|
76
77
|
def _raw_request(self, function, params=''):
|
|
77
78
|
url = self._urlbase.format(function, params)
|
|
78
|
-
|
|
79
|
+
self.log.debug(f"Request URL: {url}")
|
|
79
80
|
response = self._session.get(url)
|
|
80
|
-
|
|
81
|
+
self.log.debug(f"Request Response: {response.text}")
|
|
81
82
|
return response
|
|
82
83
|
|
|
83
84
|
def _handle_response(self, response):
|
|
@@ -117,7 +118,7 @@ class ClouDNSClient(object):
|
|
|
117
118
|
|
|
118
119
|
single_types = ['CNAME', 'A', 'AAAA', 'DNAME', 'ALIAS', 'NS', 'PTR', 'SPF', 'TXT']
|
|
119
120
|
if rrset_type in single_types:
|
|
120
|
-
params += '&record={}'.format(rrset_values[0].replace('\;', ';'))
|
|
121
|
+
params += '&record={}'.format(rrset_values[0].replace(r'\;', ';').replace('+', '%2B'))
|
|
121
122
|
|
|
122
123
|
if(geodns is True):
|
|
123
124
|
for location in rrset_locations:
|
|
@@ -241,11 +242,11 @@ class ClouDNSProvider(BaseProvider):
|
|
|
241
242
|
]
|
|
242
243
|
)
|
|
243
244
|
|
|
244
|
-
def __init__(self, id, auth_id, auth_password, *args, **kwargs):
|
|
245
|
+
def __init__(self, id, auth_id, auth_password, sub_auth=False, *args, **kwargs):
|
|
245
246
|
self.log = getLogger(f"ClouDNSProvider[{id}]")
|
|
246
|
-
self.log.debug("__init__: id=%s, auth_id
|
|
247
|
+
self.log.debug("__init__: id=%s, auth_id=***, auth_password=***, sub_auth=%s", id, sub_auth)
|
|
247
248
|
super().__init__(id, *args, **kwargs)
|
|
248
|
-
self._client = ClouDNSClient(auth_id, auth_password)
|
|
249
|
+
self._client = ClouDNSClient(auth_id, auth_password, id, sub_auth)
|
|
249
250
|
|
|
250
251
|
self._zone_records = {}
|
|
251
252
|
|
|
@@ -260,10 +261,14 @@ class ClouDNSProvider(BaseProvider):
|
|
|
260
261
|
return {
|
|
261
262
|
"ttl": records[0]["ttl"],
|
|
262
263
|
"type": _type,
|
|
263
|
-
"
|
|
264
|
+
"values": [
|
|
265
|
+
(record["record"].replace(';', '\\;').rstrip('.'))
|
|
266
|
+
for record in records
|
|
267
|
+
]
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
|
|
271
|
+
|
|
267
272
|
_data_for_A = _data_for_multiple
|
|
268
273
|
_data_for_AAAA = _data_for_multiple
|
|
269
274
|
_data_for_SPF = _data_for_multiple
|
|
@@ -367,27 +372,10 @@ class ClouDNSProvider(BaseProvider):
|
|
|
367
372
|
|
|
368
373
|
values = defaultdict(lambda: defaultdict(list))
|
|
369
374
|
records_data = self.zone_records(zone)
|
|
370
|
-
|
|
375
|
+
|
|
371
376
|
if 'status' in records_data and records_data['status'] == 'Failed':
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if 'id' in response and response['id'] == 'not_found':
|
|
375
|
-
e = ClouDNSClientUnknownDomainName(
|
|
376
|
-
'Missing domain name'
|
|
377
|
-
)
|
|
378
|
-
e.__cause__ = None
|
|
379
|
-
raise e
|
|
380
|
-
|
|
381
|
-
if (self.isGeoDNS(response['statusDescription'])):
|
|
382
|
-
response = self._client.zone_create(zone.name[:-1], 'geodns')
|
|
383
|
-
|
|
384
|
-
if(response['status'] == 'Failed'):
|
|
385
|
-
e = ClouDNSClientUnknownDomainName(f"{response['status']} : {response['statusDescription']}")
|
|
386
|
-
e.__cause__ = None
|
|
387
|
-
raise e
|
|
388
|
-
self.log.info("populate: zone has been successfully created")
|
|
389
|
-
records_data = self._client.zone_records(zone.name[:-1])
|
|
390
|
-
|
|
377
|
+
return []
|
|
378
|
+
|
|
391
379
|
for record_id, record in records_data.items():
|
|
392
380
|
_type = record["type"]
|
|
393
381
|
|
|
@@ -408,7 +396,7 @@ class ClouDNSProvider(BaseProvider):
|
|
|
408
396
|
)
|
|
409
397
|
zone.add_record(record, lenient=lenient)
|
|
410
398
|
exists = zone.name in self._zone_records
|
|
411
|
-
self.log.
|
|
399
|
+
self.log.debug(
|
|
412
400
|
"populate: found %s records, exists=%s",
|
|
413
401
|
len(zone.records) - before,
|
|
414
402
|
exists,
|
|
@@ -576,74 +564,96 @@ class ClouDNSProvider(BaseProvider):
|
|
|
576
564
|
zone = existing.zone
|
|
577
565
|
record_ids = []
|
|
578
566
|
for record_id, record in self.zone_records(zone).items():
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
567
|
+
if existing._type == 'NAPTR' and record['type'] == 'NAPTR':
|
|
568
|
+
for value in existing.values:
|
|
569
|
+
if (
|
|
570
|
+
existing.name == record['host']
|
|
571
|
+
and value.order == int(record['order'])
|
|
572
|
+
and value.preference == int(record['pref'])
|
|
573
|
+
and value.flags == record['flag']
|
|
574
|
+
):
|
|
575
|
+
record_ids.append(record_id)
|
|
588
576
|
elif existing._type == 'SSHFP' and record['type'] == 'SSHFP':
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
577
|
+
for value in existing.values:
|
|
578
|
+
if (
|
|
579
|
+
existing.name == record['host']
|
|
580
|
+
and value.fingerprint_type == int(record['fp_type'])
|
|
581
|
+
and value.algorithm == int(record['algorithm'])
|
|
582
|
+
and value.fingerprint == record['record']
|
|
583
|
+
):
|
|
584
|
+
record_ids.append(record_id)
|
|
596
585
|
elif existing._type == 'SRV' and record['type'] == 'SRV':
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
586
|
+
for value in existing.values:
|
|
587
|
+
if (
|
|
588
|
+
existing.name == record['host']
|
|
589
|
+
and value.priority == int(record['priority'])
|
|
590
|
+
and value.weight == int(record['weight'])
|
|
591
|
+
and value.port == record['port']
|
|
592
|
+
and value.target == record['record']
|
|
593
|
+
):
|
|
594
|
+
record_ids.append(record_id)
|
|
605
595
|
elif existing._type == 'CAA' and record['type'] == 'CAA':
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
596
|
+
for value in existing.values:
|
|
597
|
+
if (
|
|
598
|
+
existing.name == record['host']
|
|
599
|
+
and value.flags == record['caa_flag']
|
|
600
|
+
and value.tag == record['caa_type']
|
|
601
|
+
and value.value == record['caa_value']
|
|
602
|
+
):
|
|
603
|
+
record_ids.append(record_id)
|
|
613
604
|
elif existing._type == 'MX' and record['type'] == 'MX':
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
605
|
+
for value in existing.values:
|
|
606
|
+
if (
|
|
607
|
+
existing.name == record['host']
|
|
608
|
+
and value.preference == int(record['priority'])
|
|
609
|
+
and value.exchange == record['record']
|
|
610
|
+
):
|
|
611
|
+
record_ids.append(record_id)
|
|
620
612
|
|
|
621
613
|
elif existing._type == 'LOC' and record['type'] == 'LOC':
|
|
614
|
+
for value in existing.values:
|
|
615
|
+
if (
|
|
616
|
+
existing.name == record['host']
|
|
617
|
+
and value.lat_degrees == record['lat_deg']
|
|
618
|
+
and value.lat_minutes == record['lat_min']
|
|
619
|
+
and value.lat_seconds == record['lat_sec']
|
|
620
|
+
and value.lat_direction == record['lat_dir']
|
|
621
|
+
and value.long_degrees == record['long_deg']
|
|
622
|
+
and value.long_minutes == record['long_min']
|
|
623
|
+
and value.long_seconds == record['long_sec']
|
|
624
|
+
and value.long_direction == record['long_dir']
|
|
625
|
+
and value.altitude == record['altitude']
|
|
626
|
+
and value.size == record['size']
|
|
627
|
+
and value.precision_horz == record['h_precision']
|
|
628
|
+
and value.precision_vert == record['v_precision']
|
|
629
|
+
):
|
|
630
|
+
record_ids.append(record_id)
|
|
631
|
+
elif existing._type == 'CNAME' and record['type'] == 'CNAME':
|
|
622
632
|
if (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
and value.lat_seconds == record['lat_sec']
|
|
627
|
-
and value.lat_direction == record['lat_dir']
|
|
628
|
-
and value.long_degrees == record['long_deg']
|
|
629
|
-
and value.long_minutes == record['long_min']
|
|
630
|
-
and value.long_seconds == record['long_sec']
|
|
631
|
-
and value.long_direction == record['long_dir']
|
|
632
|
-
and value.altitude == record['altitude']
|
|
633
|
-
and value.size == record['size']
|
|
634
|
-
and value.precision_horz == record['h_precision']
|
|
635
|
-
and value.precision_vert == record['v_precision']
|
|
633
|
+
existing.name == record['host']
|
|
634
|
+
and existing._type == record['type']
|
|
635
|
+
and existing.value == record['record']+'.'
|
|
636
636
|
):
|
|
637
637
|
record_ids.append(record_id)
|
|
638
638
|
else:
|
|
639
639
|
if (record == 'Failed' or record == 'Missing domain-name'):
|
|
640
640
|
continue
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
641
|
+
|
|
642
|
+
if hasattr(existing, 'value'):
|
|
643
|
+
if (
|
|
644
|
+
existing.name == record['host']
|
|
645
|
+
and existing._type == record['type']
|
|
646
|
+
and existing.value == record['record']
|
|
647
|
+
):
|
|
648
|
+
record_ids.append(record_id)
|
|
649
|
+
elif hasattr(existing, 'values'):
|
|
650
|
+
for value in existing.values:
|
|
651
|
+
if (
|
|
652
|
+
existing.name == record['host']
|
|
653
|
+
and existing._type == record['type']
|
|
654
|
+
and value == record['record']
|
|
655
|
+
):
|
|
656
|
+
record_ids.append(record_id)
|
|
647
657
|
return record_ids
|
|
648
658
|
|
|
649
659
|
|
|
@@ -660,27 +670,24 @@ class ClouDNSProvider(BaseProvider):
|
|
|
660
670
|
desired = plan.desired
|
|
661
671
|
|
|
662
672
|
changes = plan.changes
|
|
663
|
-
|
|
673
|
+
zone_name = desired.name[:-1]
|
|
664
674
|
self.log.debug("_apply: zone=%s, len(changes)=%d", desired.name, len(changes))
|
|
665
675
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
)
|
|
682
|
-
e.__cause__ = None
|
|
683
|
-
raise e
|
|
676
|
+
# Does the zone actually exist?
|
|
677
|
+
zone = self._client.zone(zone_name)
|
|
678
|
+
if zone['status'] == 'Failed':
|
|
679
|
+
# All errors other than nonexistent zone.
|
|
680
|
+
if not zone['statusDescription'] == 'Missing domain-name':
|
|
681
|
+
raise ClouDNSClientException(zone)
|
|
682
|
+
|
|
683
|
+
# Check if subscription only allows GeoDNS zones.
|
|
684
|
+
geodns_only = self.isGeoDNS(zone['statusDescription'])
|
|
685
|
+
|
|
686
|
+
# Trying to create the zone.
|
|
687
|
+
zone = self._client.zone_create(zone_name, 'geodns' if geodns_only else 'master')
|
|
688
|
+
if zone['status'] == 'Failed':
|
|
689
|
+
raise ClouDNSClientException(zone)
|
|
690
|
+
self.log.info("_apply: zone has been successfully created")
|
|
684
691
|
|
|
685
692
|
for change in changes:
|
|
686
693
|
class_name = change.__class__.__name__
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: octodns-cloudns
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
4
4
|
Summary: ClouDNS API provider for octoDNS
|
|
5
5
|
Home-page: https://github.com/octodns/octodns-cloudns
|
|
6
6
|
Author: ClouDNS
|
|
7
7
|
Author-email: support@cloudns.net
|
|
8
8
|
License: MIT
|
|
9
|
-
Platform: UNKNOWN
|
|
10
9
|
Requires-Python: >=3.6
|
|
11
10
|
Description-Content-Type: text/markdown
|
|
12
11
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: octodns
|
|
14
|
-
Requires-Dist: requests
|
|
12
|
+
Requires-Dist: octodns >=0.9.17
|
|
13
|
+
Requires-Dist: requests >=2.27.0
|
|
15
14
|
Provides-Extra: dev
|
|
16
|
-
Requires-Dist: black (<24.0.0,>=23.1.0) ; extra == 'dev'
|
|
17
|
-
Requires-Dist: build (>=0.7.0) ; extra == 'dev'
|
|
18
|
-
Requires-Dist: isort (>=5.11.5) ; extra == 'dev'
|
|
19
|
-
Requires-Dist: pyflakes (>=2.2.0) ; extra == 'dev'
|
|
20
15
|
Requires-Dist: pytest ; extra == 'dev'
|
|
21
16
|
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
22
17
|
Requires-Dist: pytest-network ; extra == 'dev'
|
|
23
|
-
Requires-Dist: readme-renderer[md] (>=26.0) ; extra == 'dev'
|
|
24
18
|
Requires-Dist: requests-mock ; extra == 'dev'
|
|
25
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: black <24.0.0,>=23.1.0 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: build >=0.7.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: isort >=5.11.5 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: pyflakes >=2.2.0 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: readme-renderer[md] >=26.0 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: twine >=3.4.2 ; extra == 'dev'
|
|
26
25
|
Provides-Extra: test
|
|
27
26
|
Requires-Dist: pytest ; extra == 'test'
|
|
28
27
|
Requires-Dist: pytest-cov ; extra == 'test'
|
|
@@ -41,28 +40,30 @@ An [octoDNS](https://github.com/octodns/octodns/) provider that targets ClouDNS.
|
|
|
41
40
|
pip install octodns-cloudns
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Pinning specific versions or SHAs is recommended to avoid unplanned upgrades.
|
|
43
|
+
### Configuration
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
For more safety, we recommend you to use an API sub-user with limited permissions.
|
|
46
|
+
You can create it from [your ClouDNS account](https://www.cloudns.net/api-settings/)
|
|
47
|
+
and store your credentials in environment variables:
|
|
49
48
|
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
octodns-cloudns==0.0.1
|
|
49
|
+
```bash
|
|
50
|
+
export CLOUDNS_API_AUTH_ID=XXXXX
|
|
51
|
+
export CLOUDNS_API_AUTH_PASSWORD=XXXXX
|
|
54
52
|
```
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
Then add your ClouDNS account to your octoDNS configuration file:
|
|
57
55
|
|
|
58
56
|
```yaml
|
|
59
57
|
providers:
|
|
60
58
|
cloudns_account:
|
|
61
|
-
class:
|
|
62
|
-
auth_id:
|
|
63
|
-
auth_password:
|
|
59
|
+
class: octodns_cloudns.ClouDNSProvider
|
|
60
|
+
auth_id: env/CLOUDNS_API_AUTH_ID
|
|
61
|
+
auth_password: env/CLOUDNS_API_AUTH_PASSWORD
|
|
62
|
+
# "sub_auth" must be enabled if *only* you log in using a sub-user.
|
|
63
|
+
sub_auth: true
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
|
|
66
67
|
### Support Information
|
|
67
68
|
|
|
68
69
|
#### GeoDNS records
|
|
@@ -80,5 +81,3 @@ ClouDNSProvider does not support dynamic records.
|
|
|
80
81
|
### Development
|
|
81
82
|
|
|
82
83
|
See the [/script/](/script/) directory for some tools to help with the development process. They generally follow the [Script to rule them all](https://github.com/github/scripts-to-rule-them-all) pattern. Most useful is `./script/bootstrap` which will create a venv and install both the runtime and development related requirements. It will also hook up a pre-commit hook that covers most of what's run by CI.
|
|
83
|
-
|
|
84
|
-
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
octodns_cloudns/__init__.py,sha256=_cwQcvNvTYpMeqa13wQUE2xC95y1tdLKH1AAX_l1dRU,27446
|
|
2
|
+
octodns_cloudns-0.0.10.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
octodns_cloudns-0.0.10.dist-info/METADATA,sha256=smGjGw4WnOxQPlfLFL8WcRbB21iU3qhPAS5d2YgQHOs,2625
|
|
4
|
+
octodns_cloudns-0.0.10.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
5
|
+
octodns_cloudns-0.0.10.dist-info/top_level.txt,sha256=t-gGz4zcl1yUE6hyw-aaR6KggpKY25v5xwsSJw-zBdY,16
|
|
6
|
+
octodns_cloudns-0.0.10.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
octodns_cloudns/__init__.py,sha256=9QvUJe860IqvMCsXRtGlc2QpNzWbSoOJRDzA3XbVIw4,26782
|
|
2
|
-
octodns_cloudns-0.0.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
octodns_cloudns-0.0.2.dist-info/METADATA,sha256=tN1H4_8QZfF_r-gosJiibDCTq5LNmZQE-X_584aVZT0,2423
|
|
4
|
-
octodns_cloudns-0.0.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
5
|
-
octodns_cloudns-0.0.2.dist-info/top_level.txt,sha256=t-gGz4zcl1yUE6hyw-aaR6KggpKY25v5xwsSJw-zBdY,16
|
|
6
|
-
octodns_cloudns-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|