cloudbridge 4.0.0__tar.gz → 4.1.0__tar.gz

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.
Files changed (68) hide show
  1. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/PKG-INFO +2 -1
  2. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/__init__.py +1 -1
  3. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/azure_client.py +57 -1
  4. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/provider.py +3 -1
  5. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/resources.py +116 -1
  6. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/services.py +198 -7
  7. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/subservices.py +7 -0
  8. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/PKG-INFO +2 -1
  9. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/requires.txt +1 -0
  10. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/pyproject.toml +1 -0
  11. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/LICENSE +0 -0
  12. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/README.rst +0 -0
  13. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/__init__.py +0 -0
  14. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/helpers.py +0 -0
  15. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/middleware.py +0 -0
  16. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/provider.py +0 -0
  17. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/resources.py +0 -0
  18. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/services.py +0 -0
  19. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/subservices.py +0 -0
  20. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/factory.py +0 -0
  21. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/__init__.py +0 -0
  22. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/exceptions.py +0 -0
  23. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/provider.py +0 -0
  24. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/resources.py +0 -0
  25. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/services.py +0 -0
  26. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/subservices.py +0 -0
  27. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/__init__.py +0 -0
  28. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/__init__.py +0 -0
  29. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/helpers.py +0 -0
  30. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/provider.py +0 -0
  31. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/resources.py +0 -0
  32. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/services.py +0 -0
  33. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/subservices.py +0 -0
  34. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/__init__.py +0 -0
  35. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/helpers.py +0 -0
  36. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/__init__.py +0 -0
  37. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/helpers.py +0 -0
  38. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/provider.py +0 -0
  39. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/resources.py +0 -0
  40. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/services.py +0 -0
  41. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/subservices.py +0 -0
  42. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/mock/__init__.py +0 -0
  43. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/mock/provider.py +0 -0
  44. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/__init__.py +0 -0
  45. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/helpers.py +0 -0
  46. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/provider.py +0 -0
  47. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/resources.py +0 -0
  48. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/services.py +0 -0
  49. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/subservices.py +0 -0
  50. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/SOURCES.txt +0 -0
  51. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/dependency_links.txt +0 -0
  52. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/top_level.txt +0 -0
  53. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/setup.cfg +0 -0
  54. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_base_helpers.py +0 -0
  55. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_block_store_service.py +0 -0
  56. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_cloud_factory.py +0 -0
  57. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_cloud_helpers.py +0 -0
  58. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_compute_service.py +0 -0
  59. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_dns_service.py +0 -0
  60. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_image_service.py +0 -0
  61. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_interface.py +0 -0
  62. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_middleware_system.py +0 -0
  63. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_network_service.py +0 -0
  64. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_object_life_cycle.py +0 -0
  65. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_object_store_service.py +0 -0
  66. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_region_service.py +0 -0
  67. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_security_service.py +0 -0
  68. {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_vm_types_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudbridge
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: A simple layer of abstraction over multiple cloud providers.
5
5
  Author-email: Galaxy and GVL Projects <help@genome.edu.au>
6
6
  License-Expression: MIT
@@ -34,6 +34,7 @@ Requires-Dist: azure-mgmt-devtestlabs<10.0.0,>=9.0.0; extra == "azure"
34
34
  Requires-Dist: azure-mgmt-resource<26.0.0,>=23.0.0; extra == "azure"
35
35
  Requires-Dist: azure-mgmt-subscription<4.0.0,>=3.0.0; extra == "azure"
36
36
  Requires-Dist: azure-mgmt-compute<39.0.0,>=34.0.0; extra == "azure"
37
+ Requires-Dist: azure-mgmt-dns<10.0.0,>=9.0.0; extra == "azure"
37
38
  Requires-Dist: azure-mgmt-network<31.0.0,>=28.0.0; extra == "azure"
38
39
  Requires-Dist: azure-mgmt-storage<25.0.0,>=22.0.0; extra == "azure"
39
40
  Requires-Dist: azure-storage-blob<13.0.0,>=12.20.0; extra == "azure"
@@ -2,7 +2,7 @@
2
2
  import logging
3
3
 
4
4
  # Current version of the library
5
- __version__ = '4.0.0'
5
+ __version__ = '4.1.0'
6
6
 
7
7
 
8
8
  def get_version():
@@ -18,6 +18,8 @@ from azure.mgmt.compute.models import (CreationData, Disk, DiskUpdate, Image,
18
18
  ImageUpdate, Snapshot, SnapshotUpdate,
19
19
  VirtualMachine, VirtualMachineUpdate)
20
20
  from azure.mgmt.devtestlabs.models import GalleryImageReference
21
+ from azure.mgmt.dns import DnsManagementClient
22
+ from azure.mgmt.dns.models import Zone
21
23
  from azure.mgmt.network import NetworkManagementClient
22
24
  from azure.mgmt.network.models import (NetworkInterface,
23
25
  NetworkSecurityGroup, PublicIPAddress,
@@ -180,6 +182,7 @@ class AzureClient(object):
180
182
  self._network_management_client = None
181
183
  self._subscription_client = None
182
184
  self._compute_client = None
185
+ self._dns_client = None
183
186
  self._access_key_result = None
184
187
  self._block_blob_service = None
185
188
  self._table_service_client = None
@@ -188,8 +191,19 @@ class AzureClient(object):
188
191
 
189
192
  log.debug("azure subscription : %s", self.subscription_id)
190
193
 
194
+ # Storage account creation transitions through Creating -> ResolvingDns
195
+ # -> Succeeded. ResolvingDns alone is typically 30-60s for the public
196
+ # blob endpoint; the original retry config (5 attempts, no wait) gave
197
+ # up in ~100ms and surfaced as WaitStateException on any cold start.
198
+ # Allow ~1.5 minutes total with a fixed backoff, and only retry on the
199
+ # not-yet-ready signal so genuine errors fail fast.
191
200
  @property
192
- @tenacity.retry(stop=tenacity.stop.stop_after_attempt(5), reraise=True)
201
+ @tenacity.retry(
202
+ stop=tenacity.stop.stop_after_attempt(9),
203
+ wait=tenacity.wait_fixed(10),
204
+ retry=tenacity.retry_if_exception_type(WaitStateException),
205
+ reraise=True,
206
+ )
193
207
  def access_key_result(self):
194
208
  if not self._access_key_result:
195
209
  storage_account = self.storage_account
@@ -267,6 +281,13 @@ class AzureClient(object):
267
281
  self._credentials, self.subscription_id)
268
282
  return self._network_management_client
269
283
 
284
+ @property
285
+ def dns_client(self):
286
+ if not self._dns_client:
287
+ self._dns_client = DnsManagementClient(
288
+ self._credentials, self.subscription_id)
289
+ return self._dns_client
290
+
270
291
  @property
271
292
  def blob_service(self):
272
293
  self._get_or_create_storage_account()
@@ -974,3 +995,38 @@ class AzureClient(object):
974
995
  self.network_management_client.route_tables.update_tags(
975
996
  self.resource_group, route_table_name,
976
997
  TagsObject(tags=tags))
998
+
999
+ # DNS operations
1000
+ def get_dns_zone(self, zone_name):
1001
+ return self.dns_client.zones.get(self.resource_group, zone_name)
1002
+
1003
+ def list_dns_zones(self):
1004
+ return list(self.dns_client.zones.list_by_resource_group(
1005
+ self.resource_group))
1006
+
1007
+ def create_dns_zone(self, zone_name, params):
1008
+ return self.dns_client.zones.create_or_update(
1009
+ self.resource_group, zone_name, Zone(**params))
1010
+
1011
+ def delete_dns_zone(self, zone_name):
1012
+ self.dns_client.zones.begin_delete(
1013
+ self.resource_group, zone_name).wait()
1014
+
1015
+ def get_dns_record(self, zone_name, relative_record_name, record_type):
1016
+ return self.dns_client.record_sets.get(
1017
+ self.resource_group, zone_name, relative_record_name, record_type)
1018
+
1019
+ def list_dns_records(self, zone_name):
1020
+ return list(self.dns_client.record_sets.list_all_by_dns_zone(
1021
+ self.resource_group, zone_name))
1022
+
1023
+ def create_dns_record(self, zone_name, relative_record_name,
1024
+ record_type, params):
1025
+ from azure.mgmt.dns.models import RecordSet
1026
+ return self.dns_client.record_sets.create_or_update(
1027
+ self.resource_group, zone_name, relative_record_name,
1028
+ record_type, RecordSet(**params))
1029
+
1030
+ def delete_dns_record(self, zone_name, relative_record_name, record_type):
1031
+ self.dns_client.record_sets.delete(
1032
+ self.resource_group, zone_name, relative_record_name, record_type)
@@ -15,6 +15,7 @@ from cloudbridge.interfaces.exceptions import ProviderConnectionException
15
15
  from cloudbridge.providers.azure.azure_client import AzureClient
16
16
 
17
17
  from .services import AzureComputeService
18
+ from .services import AzureDnsService
18
19
  from .services import AzureNetworkingService
19
20
  from .services import AzureSecurityService
20
21
  from .services import AzureStorageService
@@ -79,6 +80,7 @@ class AzureCloudProvider(BaseCloudProvider):
79
80
  self._storage = AzureStorageService(self)
80
81
  self._compute = AzureComputeService(self)
81
82
  self._networking = AzureNetworkingService(self)
83
+ self._dns = AzureDnsService(self)
82
84
 
83
85
  def __get_deprecated_username(self, default):
84
86
  username = self._get_config_value(
@@ -115,7 +117,7 @@ class AzureCloudProvider(BaseCloudProvider):
115
117
 
116
118
  @property
117
119
  def dns(self):
118
- raise NotImplementedError()
120
+ return self._dns
119
121
 
120
122
  @property
121
123
  def azure_client(self):
@@ -7,7 +7,8 @@ import logging
7
7
 
8
8
  import paramiko
9
9
  from cloudbridge.base.resources import (BaseAttachmentInfo, BaseBucket,
10
- BaseBucketObject, BaseFloatingIP,
10
+ BaseBucketObject, BaseDnsRecord,
11
+ BaseDnsZone, BaseFloatingIP,
11
12
  BaseInstance, BaseInternetGateway,
12
13
  BaseKeyPair, BaseLaunchConfig,
13
14
  BaseMachineImage, BaseNetwork,
@@ -30,6 +31,7 @@ from azure.mgmt.network.models import NetworkSecurityGroup
30
31
 
31
32
  from . import helpers as azure_helpers
32
33
  from .subservices import (AzureBucketObjectSubService,
34
+ AzureDnsRecordSubService,
33
35
  AzureFloatingIPSubService, AzureGatewaySubService,
34
36
  AzureSubnetSubService, AzureVMFirewallRuleSubService)
35
37
 
@@ -1554,3 +1556,116 @@ class AzureInternetGateway(BaseInternetGateway):
1554
1556
  @property
1555
1557
  def floating_ips(self):
1556
1558
  return self._fips_container
1559
+
1560
+
1561
+ # Map Azure record-set type suffix (e.g. 'Microsoft.Network/dnszones/A')
1562
+ # to cloudbridge DnsRecordType. Used to expose record data in a normalized form.
1563
+ _AZURE_RECORD_TYPE_ATTR = {
1564
+ 'A': 'a_records',
1565
+ 'AAAA': 'aaaa_records',
1566
+ 'CNAME': 'cname_record',
1567
+ 'MX': 'mx_records',
1568
+ 'NS': 'ns_records',
1569
+ 'PTR': 'ptr_records',
1570
+ 'SRV': 'srv_records',
1571
+ 'TXT': 'txt_records',
1572
+ }
1573
+
1574
+
1575
+ def _azure_record_type(raw_record):
1576
+ """Return the bare type (e.g. 'A') from an Azure RecordSet."""
1577
+ rec_type = raw_record.type or ''
1578
+ # Azure formats: 'Microsoft.Network/dnszones/A' or bare 'A'
1579
+ return rec_type.split('/')[-1] if '/' in rec_type else rec_type
1580
+
1581
+
1582
+ def _azure_record_data(raw_record):
1583
+ """Extract the data values from an Azure RecordSet as a list of strings."""
1584
+ rt = _azure_record_type(raw_record)
1585
+ attr = _AZURE_RECORD_TYPE_ATTR.get(rt)
1586
+ if not attr:
1587
+ return []
1588
+ value = getattr(raw_record, attr, None)
1589
+ if value is None:
1590
+ return []
1591
+ if rt == 'A':
1592
+ return [r.ipv4_address for r in value]
1593
+ if rt == 'AAAA':
1594
+ return [r.ipv6_address for r in value]
1595
+ if rt == 'CNAME':
1596
+ return [value.cname]
1597
+ if rt == 'MX':
1598
+ return ['{0} {1}'.format(r.preference, r.exchange) for r in value]
1599
+ if rt == 'NS':
1600
+ return [r.nsdname for r in value]
1601
+ if rt == 'PTR':
1602
+ return [r.ptrdname for r in value]
1603
+ if rt == 'SRV':
1604
+ return ['{0} {1} {2} {3}'.format(r.priority, r.weight, r.port,
1605
+ r.target) for r in value]
1606
+ if rt == 'TXT':
1607
+ # Each TXT record carries a list of strings; join with spaces by convention
1608
+ return [' '.join(r.value) if isinstance(r.value, list) else r.value
1609
+ for r in value]
1610
+ return []
1611
+
1612
+
1613
+ class AzureDnsZone(BaseDnsZone):
1614
+
1615
+ def __init__(self, provider, dns_zone):
1616
+ super(AzureDnsZone, self).__init__(provider)
1617
+ self._dns_zone = dns_zone
1618
+ self._dns_record_container = AzureDnsRecordSubService(provider, self)
1619
+
1620
+ @property
1621
+ def id(self):
1622
+ return self._dns_zone.name
1623
+
1624
+ @property
1625
+ def name(self):
1626
+ return self._dns_zone.name
1627
+
1628
+ @property
1629
+ def admin_email(self):
1630
+ tags = self._dns_zone.tags or {}
1631
+ return tags.get('admin_email')
1632
+
1633
+ @property
1634
+ def records(self):
1635
+ return self._dns_record_container
1636
+
1637
+
1638
+ class AzureDnsRecord(BaseDnsRecord):
1639
+
1640
+ def __init__(self, provider, dns_zone, dns_record):
1641
+ super(AzureDnsRecord, self).__init__(provider)
1642
+ self._dns_zone = dns_zone
1643
+ self._dns_rec = dns_record
1644
+
1645
+ @property
1646
+ def id(self):
1647
+ return self.name + ":" + self.type
1648
+
1649
+ @property
1650
+ def name(self):
1651
+ return self._dns_rec.name
1652
+
1653
+ @property
1654
+ def zone_id(self):
1655
+ return self._dns_zone.id
1656
+
1657
+ @property
1658
+ def type(self):
1659
+ return _azure_record_type(self._dns_rec)
1660
+
1661
+ @property
1662
+ def data(self):
1663
+ return _azure_record_data(self._dns_rec)
1664
+
1665
+ @property
1666
+ def ttl(self):
1667
+ return self._dns_rec.ttl
1668
+
1669
+ def delete(self):
1670
+ # pylint:disable=protected-access
1671
+ return self._provider.dns._records.delete(self._dns_zone, self)
@@ -8,6 +8,8 @@ from cloudbridge.base.resources import (ClientPagedResultList,
8
8
  ServerPagedResultList)
9
9
  from cloudbridge.base.services import (BaseBucketObjectService,
10
10
  BaseBucketService, BaseComputeService,
11
+ BaseDnsRecordService, BaseDnsService,
12
+ BaseDnsZoneService,
11
13
  BaseFloatingIPService,
12
14
  BaseGatewayService, BaseImageService,
13
15
  BaseInstanceService, BaseKeyPairService,
@@ -40,10 +42,11 @@ from azure.mgmt.network.models import (AddressSpace,
40
42
  PublicIPAddressSku,
41
43
  PublicIPAddressSkuName, SubResource)
42
44
 
43
- from .resources import (AzureBucket, AzureBucketObject, AzureFloatingIP,
44
- AzureInstance, AzureInternetGateway, AzureKeyPair,
45
- AzureLaunchConfig, AzureMachineImage, AzureNetwork,
46
- AzureRegion, AzureRouter, AzureSnapshot, AzureSubnet,
45
+ from .resources import (AzureBucket, AzureBucketObject, AzureDnsRecord,
46
+ AzureDnsZone, AzureFloatingIP, AzureInstance,
47
+ AzureInternetGateway, AzureKeyPair, AzureLaunchConfig,
48
+ AzureMachineImage, AzureNetwork, AzureRegion,
49
+ AzureRouter, AzureSnapshot, AzureSubnet,
47
50
  AzureVMFirewall, AzureVMFirewallRule, AzureVMType,
48
51
  AzureVolume)
49
52
 
@@ -388,7 +391,6 @@ class AzureVolumeService(BaseVolumeService):
388
391
  disk_name = AzureVolume._generate_name_from_label(label, "cb-vol")
389
392
  tags = {'Label': label}
390
393
 
391
- zone_name = self.provider.zone_name
392
394
  snapshot = (self.provider.storage.snapshots.get(snapshot)
393
395
  if snapshot and isinstance(snapshot, str) else snapshot)
394
396
 
@@ -397,7 +399,7 @@ class AzureVolumeService(BaseVolumeService):
397
399
 
398
400
  if snapshot:
399
401
  params = {
400
- 'location': zone_name,
402
+ 'location': self.provider.region_name,
401
403
  'creation_data': CreationData(
402
404
  create_option=DiskCreateOption.copy,
403
405
  source_uri=snapshot.resource_id,
@@ -410,7 +412,7 @@ class AzureVolumeService(BaseVolumeService):
410
412
 
411
413
  else:
412
414
  params = {
413
- 'location': zone_name,
415
+ 'location': self.provider.region_name,
414
416
  'disk_size_gb': size,
415
417
  'creation_data': CreationData(
416
418
  create_option=DiskCreateOption.empty,
@@ -1376,3 +1378,192 @@ class AzureFloatingIPService(BaseFloatingIPService):
1376
1378
  def delete(self, gateway, fip):
1377
1379
  fip_id = fip.id if isinstance(fip, AzureFloatingIP) else fip
1378
1380
  self.provider.azure_client.delete_floating_ip(fip_id)
1381
+
1382
+
1383
+ def _strip_trailing_dot(name):
1384
+ return name[:-1] if name and name.endswith('.') else name
1385
+
1386
+
1387
+ def _to_relative_record_name(fqdn, zone_name):
1388
+ """Translate a cloudbridge FQDN record name to Azure's relative form.
1389
+
1390
+ Azure's record set API works with names relative to the zone (e.g.
1391
+ ``foo`` inside ``example.com``) plus the special ``@`` token for the
1392
+ zone apex. cloudbridge callers pass either the bare zone (apex) or a
1393
+ dotted FQDN such as ``foo.example.com.``.
1394
+ """
1395
+ name = _strip_trailing_dot(fqdn) if fqdn else ''
1396
+ zone = _strip_trailing_dot(zone_name) if zone_name else ''
1397
+ if not name or name == zone:
1398
+ return '@'
1399
+ suffix = '.' + zone
1400
+ if name.endswith(suffix):
1401
+ return name[: -len(suffix)] or '@'
1402
+ return name
1403
+
1404
+
1405
+ class AzureDnsService(BaseDnsService):
1406
+
1407
+ def __init__(self, provider):
1408
+ super(AzureDnsService, self).__init__(provider)
1409
+
1410
+ # Initialize provider services
1411
+ self._zone_svc = AzureDnsZoneService(self.provider)
1412
+ self._record_svc = AzureDnsRecordService(self.provider)
1413
+
1414
+ @property
1415
+ def host_zones(self):
1416
+ return self._zone_svc
1417
+
1418
+ @property
1419
+ def _records(self):
1420
+ return self._record_svc
1421
+
1422
+
1423
+ class AzureDnsZoneService(BaseDnsZoneService):
1424
+
1425
+ def __init__(self, provider):
1426
+ super(AzureDnsZoneService, self).__init__(provider)
1427
+
1428
+ @dispatch(event="provider.dns.host_zones.get",
1429
+ priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
1430
+ def get(self, dns_zone_id):
1431
+ try:
1432
+ zone = self.provider.azure_client.get_dns_zone(
1433
+ _strip_trailing_dot(dns_zone_id))
1434
+ return AzureDnsZone(self.provider, zone)
1435
+ except ResourceNotFoundError:
1436
+ return None
1437
+
1438
+ @dispatch(event="provider.dns.host_zones.list",
1439
+ priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
1440
+ def list(self, limit=None, marker=None):
1441
+ zones = [AzureDnsZone(self.provider, z)
1442
+ for z in self.provider.azure_client.list_dns_zones()]
1443
+ return ClientPagedResultList(self.provider, zones, limit, marker)
1444
+
1445
+ @dispatch(event="provider.dns.host_zones.find",
1446
+ priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
1447
+ def find(self, **kwargs):
1448
+ filters = ['name']
1449
+ matches = cb_helpers.generic_find(filters, kwargs, self)
1450
+ return ClientPagedResultList(self.provider, list(matches),
1451
+ limit=None, marker=None)
1452
+
1453
+ @dispatch(event="provider.dns.host_zones.create",
1454
+ priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
1455
+ def create(self, name, admin_email):
1456
+ AzureDnsZone.assert_valid_resource_name(name)
1457
+ zone_name = _strip_trailing_dot(name)
1458
+ params = {
1459
+ # DNS zones in Azure are global resources but the API still
1460
+ # requires location='global'.
1461
+ 'location': 'global',
1462
+ 'tags': {'admin_email': admin_email},
1463
+ }
1464
+ zone = self.provider.azure_client.create_dns_zone(zone_name, params)
1465
+ return AzureDnsZone(self.provider, zone)
1466
+
1467
+ @dispatch(event="provider.dns.host_zones.delete",
1468
+ priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
1469
+ def delete(self, dns_zone):
1470
+ zone_name = (dns_zone.id if isinstance(dns_zone, AzureDnsZone)
1471
+ else dns_zone)
1472
+ self.provider.azure_client.delete_dns_zone(
1473
+ _strip_trailing_dot(zone_name))
1474
+
1475
+
1476
+ class AzureDnsRecordService(BaseDnsRecordService):
1477
+
1478
+ def __init__(self, provider):
1479
+ super(AzureDnsRecordService, self).__init__(provider)
1480
+
1481
+ def _to_record_params(self, rec_type, data, ttl):
1482
+ """Translate cloudbridge data to Azure record-set parameters."""
1483
+ # Local imports keep the module importable when azure-mgmt-dns
1484
+ # isn't installed (e.g. on AWS-only test environments).
1485
+ from azure.mgmt.dns.models import (AaaaRecord, ARecord, CnameRecord,
1486
+ MxRecord, NsRecord, PtrRecord,
1487
+ SrvRecord, TxtRecord)
1488
+
1489
+ values = data if isinstance(data, list) else [data]
1490
+ params = {'ttl': ttl or 300}
1491
+
1492
+ if rec_type == 'A':
1493
+ params['a_records'] = [ARecord(ipv4_address=v) for v in values]
1494
+ elif rec_type == 'AAAA':
1495
+ params['aaaa_records'] = [
1496
+ AaaaRecord(ipv6_address=v) for v in values]
1497
+ elif rec_type == 'CNAME':
1498
+ # CNAME is a single-valued record in Azure.
1499
+ params['cname_record'] = CnameRecord(
1500
+ cname=self._standardize_record(values[0], rec_type))
1501
+ elif rec_type == 'MX':
1502
+ mx = []
1503
+ for v in values:
1504
+ preference, exchange = v.split(' ', 1)
1505
+ mx.append(MxRecord(
1506
+ preference=int(preference),
1507
+ exchange=self._standardize_record(exchange.strip(),
1508
+ rec_type)))
1509
+ params['mx_records'] = mx
1510
+ elif rec_type == 'NS':
1511
+ params['ns_records'] = [NsRecord(nsdname=v) for v in values]
1512
+ elif rec_type == 'PTR':
1513
+ params['ptr_records'] = [PtrRecord(ptrdname=v) for v in values]
1514
+ elif rec_type == 'SRV':
1515
+ srv = []
1516
+ for v in values:
1517
+ priority, weight, port, target = v.split(' ', 3)
1518
+ srv.append(SrvRecord(
1519
+ priority=int(priority), weight=int(weight),
1520
+ port=int(port), target=target))
1521
+ params['srv_records'] = srv
1522
+ elif rec_type == 'TXT':
1523
+ params['txt_records'] = [
1524
+ TxtRecord(value=v if isinstance(v, list) else [v])
1525
+ for v in values]
1526
+ else:
1527
+ raise InvalidParamException(
1528
+ "Unsupported DNS record type: %s" % rec_type)
1529
+ return params
1530
+
1531
+ def get(self, dns_zone, rec_id):
1532
+ if not rec_id or ':' not in rec_id:
1533
+ return None
1534
+ rec_name, rec_type = rec_id.split(':', 1)
1535
+ try:
1536
+ rec = self.provider.azure_client.get_dns_record(
1537
+ dns_zone.id, rec_name, rec_type)
1538
+ return AzureDnsRecord(self.provider, dns_zone, rec)
1539
+ except ResourceNotFoundError:
1540
+ return None
1541
+
1542
+ def list(self, dns_zone, limit=None, marker=None):
1543
+ records = [AzureDnsRecord(self.provider, dns_zone, r)
1544
+ for r in self.provider.azure_client.list_dns_records(
1545
+ dns_zone.id)]
1546
+ return ClientPagedResultList(self.provider, records, limit, marker)
1547
+
1548
+ def find(self, dns_zone, **kwargs):
1549
+ filters = ['name']
1550
+ matches = cb_helpers.generic_find(filters, kwargs, dns_zone.records)
1551
+ return ClientPagedResultList(self.provider, list(matches),
1552
+ limit=None, marker=None)
1553
+
1554
+ def create(self, dns_zone, name, type, data, ttl=None):
1555
+ AzureDnsRecord.assert_valid_resource_name(name)
1556
+ relative_name = _to_relative_record_name(name, dns_zone.id)
1557
+ params = self._to_record_params(type, data, ttl)
1558
+ self.provider.azure_client.create_dns_record(
1559
+ dns_zone.id, relative_name, type, params)
1560
+ return self.get(dns_zone, relative_name + ':' + type)
1561
+
1562
+ def delete(self, dns_zone, record):
1563
+ if isinstance(record, AzureDnsRecord):
1564
+ rec_name = record.name
1565
+ rec_type = record.type
1566
+ else:
1567
+ rec_name, rec_type = record.split(':', 1)
1568
+ self.provider.azure_client.delete_dns_record(
1569
+ dns_zone.id, rec_name, rec_type)
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
 
3
3
  from cloudbridge.base.subservices import BaseBucketObjectSubService
4
+ from cloudbridge.base.subservices import BaseDnsRecordSubService
4
5
  from cloudbridge.base.subservices import BaseFloatingIPSubService
5
6
  from cloudbridge.base.subservices import BaseGatewaySubService
6
7
  from cloudbridge.base.subservices import BaseSubnetSubService
@@ -36,3 +37,9 @@ class AzureSubnetSubService(BaseSubnetSubService):
36
37
 
37
38
  def __init__(self, provider, network):
38
39
  super(AzureSubnetSubService, self).__init__(provider, network)
40
+
41
+
42
+ class AzureDnsRecordSubService(BaseDnsRecordSubService):
43
+
44
+ def __init__(self, provider, dns_zone):
45
+ super(AzureDnsRecordSubService, self).__init__(provider, dns_zone)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudbridge
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: A simple layer of abstraction over multiple cloud providers.
5
5
  Author-email: Galaxy and GVL Projects <help@genome.edu.au>
6
6
  License-Expression: MIT
@@ -34,6 +34,7 @@ Requires-Dist: azure-mgmt-devtestlabs<10.0.0,>=9.0.0; extra == "azure"
34
34
  Requires-Dist: azure-mgmt-resource<26.0.0,>=23.0.0; extra == "azure"
35
35
  Requires-Dist: azure-mgmt-subscription<4.0.0,>=3.0.0; extra == "azure"
36
36
  Requires-Dist: azure-mgmt-compute<39.0.0,>=34.0.0; extra == "azure"
37
+ Requires-Dist: azure-mgmt-dns<10.0.0,>=9.0.0; extra == "azure"
37
38
  Requires-Dist: azure-mgmt-network<31.0.0,>=28.0.0; extra == "azure"
38
39
  Requires-Dist: azure-mgmt-storage<25.0.0,>=22.0.0; extra == "azure"
39
40
  Requires-Dist: azure-storage-blob<13.0.0,>=12.20.0; extra == "azure"
@@ -13,6 +13,7 @@ azure-mgmt-devtestlabs<10.0.0,>=9.0.0
13
13
  azure-mgmt-resource<26.0.0,>=23.0.0
14
14
  azure-mgmt-subscription<4.0.0,>=3.0.0
15
15
  azure-mgmt-compute<39.0.0,>=34.0.0
16
+ azure-mgmt-dns<10.0.0,>=9.0.0
16
17
  azure-mgmt-network<31.0.0,>=28.0.0
17
18
  azure-mgmt-storage<25.0.0,>=22.0.0
18
19
  azure-storage-blob<13.0.0,>=12.20.0
@@ -55,6 +55,7 @@ azure = [
55
55
  "azure-mgmt-resource>=23.0.0,<26.0.0",
56
56
  "azure-mgmt-subscription>=3.0.0,<4.0.0",
57
57
  "azure-mgmt-compute>=34.0.0,<39.0.0",
58
+ "azure-mgmt-dns>=9.0.0,<10.0.0",
58
59
  "azure-mgmt-network>=28.0.0,<31.0.0",
59
60
  "azure-mgmt-storage>=22.0.0,<25.0.0",
60
61
  "azure-storage-blob>=12.20.0,<13.0.0",
File without changes
File without changes
File without changes