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.
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/PKG-INFO +2 -1
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/__init__.py +1 -1
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/azure_client.py +57 -1
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/provider.py +3 -1
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/resources.py +116 -1
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/services.py +198 -7
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/subservices.py +7 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/PKG-INFO +2 -1
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/requires.txt +1 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/pyproject.toml +1 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/LICENSE +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/README.rst +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/middleware.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/provider.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/resources.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/services.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/base/subservices.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/factory.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/exceptions.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/provider.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/resources.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/services.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/interfaces/subservices.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/provider.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/resources.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/services.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/aws/subservices.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/azure/helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/provider.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/resources.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/services.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/gcp/subservices.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/mock/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/mock/provider.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/__init__.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/provider.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/resources.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/services.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge/providers/openstack/subservices.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/SOURCES.txt +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/dependency_links.txt +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/cloudbridge.egg-info/top_level.txt +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/setup.cfg +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_base_helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_block_store_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_cloud_factory.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_cloud_helpers.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_compute_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_dns_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_image_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_interface.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_middleware_system.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_network_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_object_life_cycle.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_object_store_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_region_service.py +0 -0
- {cloudbridge-4.0.0 → cloudbridge-4.1.0}/tests/test_security_service.py +0 -0
- {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.
|
|
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"
|
|
@@ -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(
|
|
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
|
-
|
|
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,
|
|
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,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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':
|
|
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':
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|