231 lines
9.8 KiB
Python
231 lines
9.8 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
# (the "License"); you may not use this file except in compliance with
|
|
# the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json # type: ignore
|
|
|
|
import time
|
|
from libcloud.utils.py3 import urlparse
|
|
|
|
from libcloud.common.base import (ConnectionUserAndKey,
|
|
JsonResponse,
|
|
RawResponse)
|
|
from libcloud.common.base import BaseDriver
|
|
from libcloud.http import LibcloudConnection
|
|
from libcloud.utils.py3 import basestring, urlencode
|
|
|
|
|
|
class AzureBaseDriver(BaseDriver):
|
|
name = "Microsoft Azure Resource Management API"
|
|
|
|
|
|
class AzureJsonResponse(JsonResponse):
|
|
def parse_error(self):
|
|
b = self.parse_body()
|
|
|
|
if isinstance(b, basestring):
|
|
return b
|
|
elif isinstance(b, dict) and "error" in b:
|
|
return "[%s] %s" % (b["error"].get("code"),
|
|
b["error"].get("message"))
|
|
else:
|
|
return str(b)
|
|
|
|
|
|
class AzureAuthJsonResponse(JsonResponse):
|
|
def parse_error(self):
|
|
b = self.parse_body()
|
|
|
|
if isinstance(b, basestring):
|
|
return b
|
|
elif isinstance(b, dict) and "error_description" in b:
|
|
return b["error_description"]
|
|
else:
|
|
return str(b)
|
|
|
|
|
|
# Based on
|
|
# https://github.com/Azure/azure-xplat-cli/blob/master/lib/util/profile/environment.js
|
|
publicEnvironments = {
|
|
"default": {
|
|
'name': 'default',
|
|
'portalUrl': 'http://go.microsoft.com/fwlink/?LinkId=254433',
|
|
'publishingProfileUrl':
|
|
'http://go.microsoft.com/fwlink/?LinkId=254432',
|
|
'managementEndpointUrl': 'https://management.core.windows.net',
|
|
'resourceManagerEndpointUrl':
|
|
'https://management.azure.com/',
|
|
'sqlManagementEndpointUrl':
|
|
'https://management.core.windows.net:8443/',
|
|
'sqlServerHostnameSuffix': '.database.windows.net',
|
|
'galleryEndpointUrl': 'https://gallery.azure.com/',
|
|
'activeDirectoryEndpointUrl': 'https://login.microsoftonline.com',
|
|
'activeDirectoryResourceId': 'https://management.core.windows.net/',
|
|
'activeDirectoryGraphResourceId': 'https://graph.windows.net/',
|
|
'activeDirectoryGraphApiVersion': '2013-04-05',
|
|
'storageEndpointSuffix': '.core.windows.net',
|
|
'keyVaultDnsSuffix': '.vault.azure.net',
|
|
'azureDataLakeStoreFileSystemEndpointSuffix': 'azuredatalakestore.net',
|
|
'azureDataLakeAnalyticsCatalogAndJobEndpointSuffix':
|
|
'azuredatalakeanalytics.net'
|
|
},
|
|
"AzureChinaCloud": {
|
|
'name': 'AzureChinaCloud',
|
|
'portalUrl': 'http://go.microsoft.com/fwlink/?LinkId=301902',
|
|
'publishingProfileUrl':
|
|
'http://go.microsoft.com/fwlink/?LinkID=301774',
|
|
'managementEndpointUrl': 'https://management.core.chinacloudapi.cn',
|
|
'resourceManagerEndpointUrl': 'https://management.chinacloudapi.cn',
|
|
'sqlManagementEndpointUrl':
|
|
'https://management.core.chinacloudapi.cn:8443/',
|
|
'sqlServerHostnameSuffix': '.database.chinacloudapi.cn',
|
|
'galleryEndpointUrl': 'https://gallery.chinacloudapi.cn/',
|
|
'activeDirectoryEndpointUrl': 'https://login.chinacloudapi.cn',
|
|
'activeDirectoryResourceId':
|
|
'https://management.core.chinacloudapi.cn/',
|
|
'activeDirectoryGraphResourceId': 'https://graph.chinacloudapi.cn/',
|
|
'activeDirectoryGraphApiVersion': '2013-04-05',
|
|
'storageEndpointSuffix': '.core.chinacloudapi.cn',
|
|
'keyVaultDnsSuffix': '.vault.azure.cn',
|
|
'azureDataLakeStoreFileSystemEndpointSuffix': 'N/A',
|
|
'azureDataLakeAnalyticsCatalogAndJobEndpointSuffix': 'N/A'
|
|
},
|
|
"AzureUSGovernment": {
|
|
'name': 'AzureUSGovernment',
|
|
'portalUrl': 'https://manage.windowsazure.us',
|
|
'publishingProfileUrl':
|
|
'https://manage.windowsazure.us/publishsettings/index',
|
|
'managementEndpointUrl': 'https://management.core.usgovcloudapi.net',
|
|
'resourceManagerEndpointUrl': 'https://management.usgovcloudapi.net',
|
|
'sqlManagementEndpointUrl':
|
|
'https://management.core.usgovcloudapi.net:8443/',
|
|
'sqlServerHostnameSuffix': '.database.usgovcloudapi.net',
|
|
'galleryEndpointUrl': 'https://gallery.usgovcloudapi.net/',
|
|
'activeDirectoryEndpointUrl': 'https://login-us.microsoftonline.com',
|
|
'activeDirectoryResourceId':
|
|
'https://management.core.usgovcloudapi.net/',
|
|
'activeDirectoryGraphResourceId': 'https://graph.windows.net/',
|
|
'activeDirectoryGraphApiVersion': '2013-04-05',
|
|
'storageEndpointSuffix': '.core.usgovcloudapi.net',
|
|
'keyVaultDnsSuffix': '.vault.usgovcloudapi.net',
|
|
'azureDataLakeStoreFileSystemEndpointSuffix': 'N/A',
|
|
'azureDataLakeAnalyticsCatalogAndJobEndpointSuffix': 'N/A'
|
|
},
|
|
"AzureGermanCloud": {
|
|
'name': 'AzureGermanCloud',
|
|
'portalUrl': 'http://portal.microsoftazure.de/',
|
|
'publishingProfileUrl':
|
|
'https://manage.microsoftazure.de/publishsettings/index',
|
|
'managementEndpointUrl': 'https://management.core.cloudapi.de',
|
|
'resourceManagerEndpointUrl': 'https://management.microsoftazure.de',
|
|
'sqlManagementEndpointUrl':
|
|
'https://management.core.cloudapi.de:8443/',
|
|
'sqlServerHostnameSuffix': '.database.cloudapi.de',
|
|
'galleryEndpointUrl': 'https://gallery.cloudapi.de/',
|
|
'activeDirectoryEndpointUrl': 'https://login.microsoftonline.de',
|
|
'activeDirectoryResourceId': 'https://management.core.cloudapi.de/',
|
|
'activeDirectoryGraphResourceId': 'https://graph.cloudapi.de/',
|
|
'activeDirectoryGraphApiVersion': '2013-04-05',
|
|
'storageEndpointSuffix': '.core.cloudapi.de',
|
|
'keyVaultDnsSuffix': '.vault.microsoftazure.de',
|
|
'azureDataLakeStoreFileSystemEndpointSuffix': 'N/A',
|
|
'azureDataLakeAnalyticsCatalogAndJobEndpointSuffix': 'N/A'
|
|
}
|
|
}
|
|
|
|
|
|
class AzureResourceManagementConnection(ConnectionUserAndKey):
|
|
"""
|
|
Represents a single connection to Azure
|
|
"""
|
|
|
|
conn_class = LibcloudConnection
|
|
driver = AzureBaseDriver
|
|
name = 'Azure AD Auth'
|
|
responseCls = AzureJsonResponse
|
|
rawResponseCls = RawResponse
|
|
|
|
def __init__(self, key, secret, secure=True, tenant_id=None,
|
|
subscription_id=None, cloud_environment=None, **kwargs):
|
|
super(AzureResourceManagementConnection, self) \
|
|
.__init__(key, secret, **kwargs)
|
|
if not cloud_environment:
|
|
cloud_environment = "default"
|
|
if isinstance(cloud_environment, basestring):
|
|
cloud_environment = publicEnvironments[cloud_environment]
|
|
if not isinstance(cloud_environment, dict):
|
|
raise Exception("cloud_environment must be one of '%s' or a dict "
|
|
"containing keys 'resourceManagerEndpointUrl', "
|
|
"'activeDirectoryEndpointUrl', "
|
|
"'activeDirectoryResourceId', "
|
|
"'storageEndpointSuffix'" % (
|
|
"', '".join(publicEnvironments.keys())))
|
|
self.host = urlparse.urlparse(
|
|
cloud_environment['resourceManagerEndpointUrl']).hostname
|
|
self.login_host = urlparse.urlparse(
|
|
cloud_environment['activeDirectoryEndpointUrl']).hostname
|
|
self.login_resource = cloud_environment['activeDirectoryResourceId']
|
|
self.storage_suffix = cloud_environment['storageEndpointSuffix']
|
|
self.tenant_id = tenant_id
|
|
self.subscription_id = subscription_id
|
|
|
|
def add_default_headers(self, headers):
|
|
headers['Content-Type'] = "application/json"
|
|
headers['Authorization'] = "Bearer %s" % self.access_token
|
|
return headers
|
|
|
|
def encode_data(self, data):
|
|
"""Encode data to JSON"""
|
|
return json.dumps(data)
|
|
|
|
def get_token_from_credentials(self):
|
|
"""
|
|
Log in and get bearer token used to authorize API requests.
|
|
"""
|
|
|
|
conn = self.conn_class(self.login_host, 443, timeout=self.timeout)
|
|
conn.connect()
|
|
params = urlencode({
|
|
"grant_type": "client_credentials",
|
|
"client_id": self.user_id,
|
|
"client_secret": self.key,
|
|
"resource": self.login_resource
|
|
})
|
|
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
|
conn.request("POST", "/%s/oauth2/token" % self.tenant_id,
|
|
params, headers)
|
|
js = AzureAuthJsonResponse(conn.getresponse(), conn)
|
|
self.access_token = js.object["access_token"]
|
|
self.expires_on = js.object["expires_on"]
|
|
|
|
def connect(self, **kwargs):
|
|
self.get_token_from_credentials()
|
|
return super(AzureResourceManagementConnection, self).connect(**kwargs)
|
|
|
|
def request(self, action, params=None, data=None, headers=None,
|
|
method='GET', raw=False):
|
|
|
|
# Log in again if the token has expired or is going to expire soon
|
|
# (next 5 minutes).
|
|
if (time.time() + 300) >= int(self.expires_on):
|
|
self.get_token_from_credentials()
|
|
|
|
return super(AzureResourceManagementConnection, self) \
|
|
.request(action, params=params,
|
|
data=data, headers=headers,
|
|
method=method, raw=raw)
|