Index: boto/utils.py |
diff --git a/boto/utils.py b/boto/utils.py |
index 6bad25d2e9db46144c3338978347d8f451c5c808..9a4ff31774cfc105d7fb18bda7085e886a38abed 100644 |
--- a/boto/utils.py |
+++ b/boto/utils.py |
@@ -54,6 +54,8 @@ from email.MIMEBase import MIMEBase |
from email.MIMEText import MIMEText |
from email.Utils import formatdate |
from email import Encoders |
+import gzip |
+ |
try: |
import hashlib |
@@ -149,12 +151,15 @@ def get_aws_metadata(headers, provider=None): |
for hkey in headers.keys(): |
if hkey.lower().startswith(metadata_prefix): |
val = urllib.unquote_plus(headers[hkey]) |
- metadata[hkey[len(metadata_prefix):]] = unicode(val, 'utf-8') |
+ try: |
+ metadata[hkey[len(metadata_prefix):]] = unicode(val, 'utf-8') |
+ except UnicodeDecodeError: |
+ metadata[hkey[len(metadata_prefix):]] = val |
del headers[hkey] |
return metadata |
-def retry_url(url, retry_on_404=True): |
- for i in range(0, 10): |
+def retry_url(url, retry_on_404=True, num_retries=10): |
+ for i in range(0, num_retries): |
try: |
req = urllib2.Request(url) |
resp = urllib2.urlopen(req) |
@@ -196,7 +201,7 @@ def _get_instance_metadata(url): |
d[key] = val |
return d |
-def get_instance_metadata(version='latest'): |
+def get_instance_metadata(version='latest', url='http://169.254.169.254'): |
""" |
Returns the instance metadata as a nested Python dictionary. |
Simple values (e.g. local_hostname, hostname, etc.) will be |
@@ -204,12 +209,12 @@ def get_instance_metadata(version='latest'): |
be stored in the dict as a list of string values. More complex |
fields such as public-keys and will be stored as nested dicts. |
""" |
- url = 'http://169.254.169.254/%s/meta-data/' % version |
- return _get_instance_metadata(url) |
+ return _get_instance_metadata('%s/%s/meta-data/' % (url, version)) |
-def get_instance_userdata(version='latest', sep=None): |
- url = 'http://169.254.169.254/%s/user-data' % version |
- user_data = retry_url(url, retry_on_404=False) |
+def get_instance_userdata(version='latest', sep=None, |
+ url='http://169.254.169.254'): |
+ ud_url = '%s/%s/user-data' % (url,version) |
+ user_data = retry_url(ud_url, retry_on_404=False) |
if user_data: |
if sep: |
l = user_data.split(sep) |
@@ -220,6 +225,7 @@ def get_instance_userdata(version='latest', sep=None): |
return user_data |
ISO8601 = '%Y-%m-%dT%H:%M:%SZ' |
+ISO8601_MS = '%Y-%m-%dT%H:%M:%S.%fZ' |
def get_ts(ts=None): |
if not ts: |
@@ -227,7 +233,12 @@ def get_ts(ts=None): |
return time.strftime(ISO8601, ts) |
def parse_ts(ts): |
- return datetime.datetime.strptime(ts, ISO8601) |
+ try: |
+ dt = datetime.datetime.strptime(ts, ISO8601) |
+ return dt |
+ except ValueError: |
+ dt = datetime.datetime.strptime(ts, ISO8601_MS) |
+ return dt |
def find_class(module_name, class_name=None): |
if class_name: |
@@ -502,16 +513,20 @@ class LRUCache(dict): |
class Password(object): |
""" |
- Password object that stores itself as SHA512 hashed. |
+ Password object that stores itself as hashed. |
+ Hash defaults to SHA512 if available, MD5 otherwise. |
""" |
- def __init__(self, str=None): |
+ hashfunc=_hashfn |
+ def __init__(self, str=None, hashfunc=None): |
""" |
- Load the string from an initial value, this should be the raw SHA512 hashed password |
+ Load the string from an initial value, this should be the raw hashed password. |
""" |
self.str = str |
+ if hashfunc: |
+ self.hashfunc = hashfunc |
def set(self, value): |
- self.str = _hashfn(value).hexdigest() |
+ self.str = self.hashfunc(value).hexdigest() |
def __str__(self): |
return str(self.str) |
@@ -519,7 +534,7 @@ class Password(object): |
def __eq__(self, other): |
if other == None: |
return False |
- return str(_hashfn(other).hexdigest()) == str(self.str) |
+ return str(self.hashfunc(other).hexdigest()) == str(self.str) |
def __len__(self): |
if self.str: |
@@ -527,7 +542,8 @@ class Password(object): |
else: |
return 0 |
-def notify(subject, body=None, html_body=None, to_string=None, attachments=[], append_instance_id=True): |
+def notify(subject, body=None, html_body=None, to_string=None, attachments=None, append_instance_id=True): |
+ attachments = attachments or [] |
if append_instance_id: |
subject = "[%s] %s" % (boto.config.get_value("Instance", "instance-id"), subject) |
if not to_string: |
@@ -603,5 +619,73 @@ def pythonize_name(name, sep='_'): |
s += c |
return s |
-def awsify_name(name): |
- return name[0:1].upper()+name[1:] |
+def write_mime_multipart(content, compress=False, deftype='text/plain', delimiter=':'): |
+ """Description: |
+ :param content: A list of tuples of name-content pairs. This is used |
+ instead of a dict to ensure that scripts run in order |
+ :type list of tuples: |
+ |
+ :param compress: Use gzip to compress the scripts, defaults to no compression |
+ :type bool: |
+ |
+ :param deftype: The type that should be assumed if nothing else can be figured out |
+ :type str: |
+ |
+ :param delimiter: mime delimiter |
+ :type str: |
+ |
+ :return: Final mime multipart |
+ :rtype: str: |
+ """ |
+ wrapper = MIMEMultipart() |
+ for name,con in content: |
+ definite_type = guess_mime_type(con, deftype) |
+ maintype, subtype = definite_type.split('/', 1) |
+ if maintype == 'text': |
+ mime_con = MIMEText(con, _subtype=subtype) |
+ else: |
+ mime_con = MIMEBase(maintype, subtype) |
+ mime_con.set_payload(con) |
+ # Encode the payload using Base64 |
+ Encoders.encode_base64(mime_con) |
+ mime_con.add_header('Content-Disposition', 'attachment', filename=name) |
+ wrapper.attach(mime_con) |
+ rcontent = wrapper.as_string() |
+ |
+ if compress: |
+ buf = StringIO.StringIO() |
+ gz = gzip.GzipFile(mode='wb', fileobj=buf) |
+ try: |
+ gz.write(rcontent) |
+ finally: |
+ gz.close() |
+ rcontent = buf.getvalue() |
+ |
+ return rcontent |
+ |
+def guess_mime_type(content, deftype): |
+ """Description: Guess the mime type of a block of text |
+ :param content: content we're finding the type of |
+ :type str: |
+ |
+ :param deftype: Default mime type |
+ :type str: |
+ |
+ :rtype: <type>: |
+ :return: <description> |
+ """ |
+ #Mappings recognized by cloudinit |
+ starts_with_mappings={ |
+ '#include' : 'text/x-include-url', |
+ '#!' : 'text/x-shellscript', |
+ '#cloud-config' : 'text/cloud-config', |
+ '#upstart-job' : 'text/upstart-job', |
+ '#part-handler' : 'text/part-handler', |
+ '#cloud-boothook' : 'text/cloud-boothook' |
+ } |
+ rtype = deftype |
+ for possible_type,mimetype in starts_with_mappings.items(): |
+ if content.startswith(possible_type): |
+ rtype = mimetype |
+ break |
+ return(rtype) |