Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Management API is now Python 2.5 compatible.

Cert and key for management API can now be in separate files, default
behaviour assumes they are together but if management_key_path is passed
when instantiating ServiceManagementEndpoints, then that key will be
passed through to the underlying HTTPSConnection and hence SSL socket.
This is *required* under Python 2.5 as the httplib.HTTPSConnection
therein expects both to be set.

Fixed error handling code to work with lxml. Unfortunately there doesn't
seem to be a reliable way to use isinstance against an lxml ElementTree
instance due to the ElementTree class not being part of the public API.
The _get_wa_error method now accepts an extra argument for pre-parsed
response data in order to avoid type testing.
  • Loading branch information...
commit 4bec8badc285e21ee235ff8769d4f27b9e9c1c29 1 parent e56240e
@bmb bmb authored
View
7 pyazure/hostedservices.py
@@ -46,13 +46,12 @@
class HostedServices(ServiceManagementEndpoint):
- def __init__(self, management_cert_path, subscription_id):
+ def __init__(self, *args, **kwargs):
log.debug("init hosted service")
self.wasm_ops = self.get_wasm_ops()
self._locations = None
self.last_response_data = None
- super(HostedServices, self).__init__(management_cert_path,
- subscription_id)
+ super(HostedServices, self).__init__(*args, **kwargs)
@property
def base_url(self):
@@ -64,7 +63,7 @@ def locations(self):
# cached list of data center locations for deployments
if not self._locations:
self._locations = list(Locations(self.cert,
- self.sub_id).list_locations())
+ self.sub_id, self.key).list_locations())
return self._locations
def get_wasm_ops(self):
View
5 pyazure/locations.py
@@ -37,11 +37,10 @@
class Locations(ServiceManagementEndpoint):
- def __init__(self, management_cert_path, subscription_id):
+ def __init__(self, *args, **kwargs):
log.debug('init locations')
self.wasm_ops = []
- super(Locations, self).__init__(management_cert_path,
- subscription_id)
+ super(Locations, self).__init__(*args, **kwargs)
@property
def base_url(self):
View
45 pyazure/pyazure.py
@@ -42,7 +42,8 @@ class PyAzure(object):
def __init__(self, storage_account_name=DEVSTORE_ACCOUNT,
storage_account_key=None, use_path_style_uris=False,
- management_cert_path=None, subscription_id=None):
+ management_cert_path=None, management_key_path=None,
+ subscription_id=None):
self.storage_account = storage_account_name
if storage_account_name == DEVSTORE_ACCOUNT:
blob_host = DEVSTORE_BLOB_HOST
@@ -56,7 +57,8 @@ def __init__(self, storage_account_name=DEVSTORE_ACCOUNT,
table_host = CLOUD_TABLE_HOST
if management_cert_path and subscription_id:
- self.wasm = WASM(management_cert_path, subscription_id)
+ self.wasm = WASM(management_cert_path, subscription_id,
+ management_key_path)
else:
self.wasm = None
@@ -132,13 +134,15 @@ def set_storage_account(self, storage_account_name, create=False,
class WASM(object):
- """Class exposing Windows Azure Service Management operations.
+ """Single class that conveniently exposes Windows Azure Service Management
+ operations from those implemented and explicitly exposed by the individual
+ service wrappers.
Using WASM
----------
>>> import pyazure
>>> pa = pyazure.PyAzure(management_cert_path=MANAGEMENT_CERT,
- ... subscription_id=SUBSCRIPTION_ID)
+ ... subscription_id=SUBSCRIPTION_ID, management_key_path=MANAGEMENT_KEY)
>>> 'Anywhere Asia' in pa.wasm.list_locations()
True
>>> request_id = pa.wasm.create_storage_account('pyazuretest','doctest',
@@ -146,7 +150,7 @@ class WASM(object):
>>> pa.wasm.wait_for_request(request_id)
True
>>> (pa.wasm.get_operation_status(request_id) ==
- ... {'HttpStatusCode': '200', 'Status': 'Succeeded'})
+ ... {'HttpStatusCode': 200, 'Status': 'Succeeded'})
True
>>> request_id = pa.wasm.create_storage_account(
... 'pyazuretestwithaverylongname','doctest','anywhere us')
@@ -188,19 +192,27 @@ class WASM(object):
WASMError: (404, 'ResourceNotFound', 'The requested storage account was not found.')
"""
- def __init__(self, management_cert_path, subscription_id):
+ def __init__(self, management_cert_path, subscription_id,
+ management_key_path=None):
+ """Initialise the various API interfaces.
+
+ Note that management_key_path is not required (except for Python 2.5),
+ as the key can be included in the certificate chain file. The OpenSSL
+ command line can create an appropriate PEM file like so:
+ openssl pkcs12 -in azure.pfx -out azure.pem -nodes
+ """
from hostedservices import HostedServices, ServiceConfiguration
from storageaccounts import StorageAccounts
from locations import Locations
self.service_api = HostedServices(management_cert_path,
- subscription_id)
+ subscription_id, management_key_path)
self.ServiceConfiguration = ServiceConfiguration
self.storage_api = StorageAccounts(management_cert_path,
- subscription_id)
+ subscription_id, management_key_path)
self.location_api = Locations(management_cert_path,
- subscription_id)
+ subscription_id, management_key_path)
self._sme = ServiceManagementEndpoint(management_cert_path,
- subscription_id)
+ subscription_id, management_key_path)
self.WASMError = WASMError
self.get_operation_status = self._sme.get_operation_status
self.request_done = self._sme.request_done
@@ -234,13 +246,13 @@ def usage():
import getopt
try:
opts, _ = getopt.getopt(sys.argv[1:],
- 'hs:c:v',
- ['help','subscription_id','management_cert','verbose'])
+ 'hvs:c:k:',
+ ['help','verbose','subscription_id','management_cert',
+ 'management_key'])
except getopt.GetoptError, e:
print str(e)
sys.exit(2)
- sub_id = None
- cert = None
+ sub_id = cert = key = None
loud = False
for opt, arg in opts:
if opt in ('-h','--help'):
@@ -250,12 +262,15 @@ def usage():
sub_id = arg
elif opt in ('-c','--management_cert'):
cert = arg
+ elif opt in ('-k','--management_key'):
+ key = arg
elif opt in ('-v','--verbose'):
loud = True
if sub_id is None or cert is None:
usage()
sys.exit(2)
doctest.testmod(
- extraglobs={'SUBSCRIPTION_ID':sub_id, 'MANAGEMENT_CERT':cert},
+ extraglobs={'SUBSCRIPTION_ID':sub_id, 'MANAGEMENT_CERT':cert,
+ 'MANAGEMENT_KEY':key},
verbose = loud)
sys.exit()
View
7 pyazure/storageaccounts.py
@@ -40,12 +40,11 @@ class StorageAccounts(ServiceManagementEndpoint):
wasm_ops = []
- def __init__(self, management_cert_path, subscription_id):
+ def __init__(self, *args, **kwargs):
log.debug('init storage accounts')
self.wasm_ops = self.get_wasm_ops()
self._locations = None
- super(StorageAccounts, self).__init__(management_cert_path,
- subscription_id)
+ super(StorageAccounts, self).__init__(*args, **kwargs)
@property
def base_url(self):
@@ -57,7 +56,7 @@ def locations(self):
# cached list of data center locations for deployments
if not self._locations:
self._locations = list(Locations(self.cert,
- self.sub_id).list_locations())
+ self.sub_id, self.key).list_locations())
return self._locations
def get_wasm_ops(self):
View
50 pyazure/util.py
@@ -510,14 +510,22 @@ def create_data_connection_string(storage_account_name, storage_account_key):
class ServiceManagementEndpoint(object):
"""Base class for the various service management API operation groups."""
- def __init__(self, management_cert_path, subscription_id):
+ def __init__(self, management_cert_path, subscription_id,
+ management_key_path=None):
if not os.path.isfile(management_cert_path):
raise ValueError('Management certificate not readable or not '
- + 'a real file')
+ + 'a real file', management_cert_path)
+ if (management_key_path is not None) and \
+ not os.path.isfile(management_key_path):
+ raise ValueError('Management key not readable or not '
+ + 'a real file', management_key_path)
self.cert = management_cert_path
+ self.key = management_key_path
self.sub_id = subscription_id
+ log.debug('init ServiceManagementEndpoint; cert:%s, key:%s, sub_id:%s',
+ self.cert, self.key, self.sub_id)
self._cert_handler = \
- HTTPSClientAuthHandler(self.cert)
+ HTTPSClientAuthHandler(self.cert, self.key)
self._opener = \
urllib2.build_opener(self._cert_handler)
@@ -542,12 +550,12 @@ def urlopen(self, request):
except WASMError:
# OK, WASMError exception successfully crafted
raise
- except Exception, e2:
+ except Exception:
log.error("Could't create Windows Azure error exception " +
"following HTTPError:%s%s%s" +
"there was probably a problem querying the service.",
- os.sep, str(e), os.sep)
- raise (e2, e)
+ os.linesep, str(e), os.linesep)
+ raise
def get_operation_status(self, request_id):
"""The Get Operation Status operation returns the status of the
@@ -569,6 +577,11 @@ def get_operation_status(self, request_id):
# Succeeded or Failed...
result['HttpStatusCode'] = ET.findtext(
'.//{%s}HttpStatusCode' % NAMESPACE_MANAGEMENT)
+ try:
+ result['HttpStatusCode'] = int(result['HttpStatusCode'])
+ except ValueError:
+ log.error("Couldn't convert HttpStatusCode: %s, from operation "
+ "response", result['HttpStatusCode'])
if result['Status'] == 'Succeeded':
return result
# Status must be 'Failed', get additional error info
@@ -589,21 +602,19 @@ def request_done(self, request_id):
else:
raise op_status['Error']
- @retry(float('infinity'),delay_ceiling=20, percolate_excs=(WASMError))
+ @retry(float('infinity'), delay_ceiling=20, percolate_excs=(WASMError))
def wait_for_request(self, request_id):
"""Example showing how to repeatedly poll asynchronous operation status
with retry and backoff provided by retry decorator. Tries forever."""
return self.request_done(request_id)
- def _get_wa_error(self, response):
+ def _get_wa_error(self, response, response_ET=None):
"""Extracts error details from a urlopen response, including extended
- WA error details that might be included in the response body."""
- if not isinstance(response, etree.ElementTree):
- response_data = response.read()
- ET = etree.parse(StringIO(response_data))
- else:
- # assume response is a preparsed response body
- ET = response
+ WA error details that might be included in the response body.
+
+ response_ET can be passed in if it has already been read from the
+ network, to save re-parsing."""
+ ET = etree.parse(response) if response_ET is None else response_ET
if 'Error' in ET.getroot().tag:
error = ET.getroot()
else:
@@ -617,7 +628,7 @@ def _get_wa_error(self, response):
http_status_code = response.code
else:
http_status_code = int(http_status_code)
- # NB: careful: bool(Element instance) returns False!
+ # NB: careful: bool(Element_instance) returns False!
if error is not None:
# the service returned extended WA error info
wa_code = error.findtext('{%s}Code' % NAMESPACE_MANAGEMENT)
@@ -634,9 +645,12 @@ def _raise_wa_error(self, response):
class HTTPSClientAuthHandler(urllib2.HTTPSHandler):
# thanks to: http://stackoverflow.com/questions/5896380/https-connection-
# using-pem-certificate/5899320#5899320
- def __init__(self, cert):
+ def __init__(self, cert, key=None):
urllib2.HTTPSHandler.__init__(self, debuglevel=0)
- self.key = '/pandora/messagelab/blair/azure/BlairBethwaiteAzure1.pfx.pem.key' # assume key & cert together in PEM encoded cert_file
+ # key & cert can be:
+ # 1) together in PEM encoded cert chain file (Python >= 2.6)
+ # 2) seperated into cert and key PEM files, required under Python 2.5
+ self.key = key
self.cert = cert
def https_open(self, req):
Please sign in to comment.
Something went wrong with that request. Please try again.