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.
@@ -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.2'
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
- logger.debug(f"Request URL: {url}")
79
+ self.log.debug(f"Request URL: {url}")
79
80
  response = self._session.get(url)
80
- logger.debug(f"Request Response: {response.text}")
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=***", 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
- "value": records[0]["record"].replace(';', '\\;') + ".",
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
- self.log.info("populate: no existing zone, trying to create it")
373
- response = self._client.zone_create(zone.name[:-1], 'master')
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.info(
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
- for value in existing.values:
580
- if existing._type == 'NAPTR' and record['type'] == 'NAPTR':
581
- if (
582
- existing.name == record['host']
583
- and value.order == int(record['order'])
584
- and value.preference == int(record['pref'])
585
- and value.flags == record['flag']
586
- ):
587
- record_ids.append(record_id)
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
- if (
590
- existing.name == record['host']
591
- and value.fingerprint_type == int(record['fp_type'])
592
- and value.algorithm == int(record['algorithm'])
593
- and value.fingerprint == record['record']
594
- ):
595
- record_ids.append(record_id)
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
- if (
598
- existing.name == record['host']
599
- and value.priority == int(record['priority'])
600
- and value.weight == int(record['weight'])
601
- and value.port == record['port']
602
- and value.target == record['record']
603
- ):
604
- record_ids.append(record_id)
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
- if (
607
- existing.name == record['host']
608
- and value.flags == record['caa_flag']
609
- and value.tag == record['caa_type']
610
- and value.value == record['caa_value']
611
- ):
612
- record_ids.append(record_id)
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
- if (
615
- existing.name == record['host']
616
- and value.preference == int(record['priority'])
617
- and value.exchange == record['record']
618
- ):
619
- record_ids.append(record_id)
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
- existing.name == record['host']
624
- and value.lat_degrees == record['lat_deg']
625
- and value.lat_minutes == record['lat_min']
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
- if (
642
- existing.name == record['host']
643
- and existing._type == record['type']
644
- and value == record['record']
645
- ):
646
- record_ids.append(record_id)
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
- zone = desired.name[:-1]
673
+ zone_name = desired.name[:-1]
664
674
  self.log.debug("_apply: zone=%s, len(changes)=%d", desired.name, len(changes))
665
675
 
666
- try:
667
- self._client.zone(zone)
668
- except ClouDNSClientNotFound:
669
- self.log.info("_apply: no existing zone, trying to create it")
670
- try:
671
- self._client.zone_create(zone, 'master')
672
- self.log.info("_apply: zone has been successfully created")
673
- except ClouDNSClientNotFound:
674
- e = ClouDNSClientUnknownDomainName(
675
- "Domain " + zone + " is not "
676
- "registered at ClouDNS. "
677
- "Please register or "
678
- "transfer it here "
679
- "to be able to manage its "
680
- "DNS zone."
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.2
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 (>=0.9.17)
14
- Requires-Dist: requests (>=2.27.0)
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: twine (>=3.4.2) ; extra == 'dev'
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
- #### requirements.txt/setup.py
45
-
46
- Pinning specific versions or SHAs is recommended to avoid unplanned upgrades.
43
+ ### Configuration
47
44
 
48
- ##### Versions
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
- # Start with the latest versions and don't just copy what's here
52
- octodns==0.9.14
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
- ### Configuration
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: octodns.provider.cloudns.ClouDNSProvider
62
- auth_id: <api_auth_id>
63
- auth_password: <api_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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: setuptools (75.3.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,