| OLD | NEW |
| 1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ | 1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. | 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. |
| 3 # All rights reserved. | 3 # All rights reserved. |
| 4 # | 4 # |
| 5 # Permission is hereby granted, free of charge, to any person obtaining a | 5 # Permission is hereby granted, free of charge, to any person obtaining a |
| 6 # copy of this software and associated documentation files (the | 6 # copy of this software and associated documentation files (the |
| 7 # "Software"), to deal in the Software without restriction, including | 7 # "Software"), to deal in the Software without restriction, including |
| 8 # without limitation the rights to use, copy, modify, merge, publish, dis- | 8 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 9 # tribute, sublicense, and/or sell copies of the Software, and to permit | 9 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 10 # persons to whom the Software is furnished to do so, subject to the fol- | 10 # persons to whom the Software is furnished to do so, subject to the fol- |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 47 import logging.handlers | 47 import logging.handlers |
| 48 import boto | 48 import boto |
| 49 import tempfile | 49 import tempfile |
| 50 import smtplib | 50 import smtplib |
| 51 import datetime | 51 import datetime |
| 52 from email.MIMEMultipart import MIMEMultipart | 52 from email.MIMEMultipart import MIMEMultipart |
| 53 from email.MIMEBase import MIMEBase | 53 from email.MIMEBase import MIMEBase |
| 54 from email.MIMEText import MIMEText | 54 from email.MIMEText import MIMEText |
| 55 from email.Utils import formatdate | 55 from email.Utils import formatdate |
| 56 from email import Encoders | 56 from email import Encoders |
| 57 import gzip |
| 58 |
| 57 | 59 |
| 58 try: | 60 try: |
| 59 import hashlib | 61 import hashlib |
| 60 _hashfn = hashlib.sha512 | 62 _hashfn = hashlib.sha512 |
| 61 except ImportError: | 63 except ImportError: |
| 62 import md5 | 64 import md5 |
| 63 _hashfn = md5.md5 | 65 _hashfn = md5.md5 |
| 64 | 66 |
| 65 # List of Query String Arguments of Interest | 67 # List of Query String Arguments of Interest |
| 66 qsa_of_interest = ['acl', 'location', 'logging', 'partNumber', 'policy', | 68 qsa_of_interest = ['acl', 'location', 'logging', 'partNumber', 'policy', |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 return final_headers | 144 return final_headers |
| 143 | 145 |
| 144 def get_aws_metadata(headers, provider=None): | 146 def get_aws_metadata(headers, provider=None): |
| 145 if not provider: | 147 if not provider: |
| 146 provider = boto.provider.get_default() | 148 provider = boto.provider.get_default() |
| 147 metadata_prefix = provider.metadata_prefix | 149 metadata_prefix = provider.metadata_prefix |
| 148 metadata = {} | 150 metadata = {} |
| 149 for hkey in headers.keys(): | 151 for hkey in headers.keys(): |
| 150 if hkey.lower().startswith(metadata_prefix): | 152 if hkey.lower().startswith(metadata_prefix): |
| 151 val = urllib.unquote_plus(headers[hkey]) | 153 val = urllib.unquote_plus(headers[hkey]) |
| 152 metadata[hkey[len(metadata_prefix):]] = unicode(val, 'utf-8') | 154 try: |
| 155 metadata[hkey[len(metadata_prefix):]] = unicode(val, 'utf-8') |
| 156 except UnicodeDecodeError: |
| 157 metadata[hkey[len(metadata_prefix):]] = val |
| 153 del headers[hkey] | 158 del headers[hkey] |
| 154 return metadata | 159 return metadata |
| 155 | 160 |
| 156 def retry_url(url, retry_on_404=True): | 161 def retry_url(url, retry_on_404=True, num_retries=10): |
| 157 for i in range(0, 10): | 162 for i in range(0, num_retries): |
| 158 try: | 163 try: |
| 159 req = urllib2.Request(url) | 164 req = urllib2.Request(url) |
| 160 resp = urllib2.urlopen(req) | 165 resp = urllib2.urlopen(req) |
| 161 return resp.read() | 166 return resp.read() |
| 162 except urllib2.HTTPError, e: | 167 except urllib2.HTTPError, e: |
| 163 # in 2.6 you use getcode(), in 2.5 and earlier you use code | 168 # in 2.6 you use getcode(), in 2.5 and earlier you use code |
| 164 if hasattr(e, 'getcode'): | 169 if hasattr(e, 'getcode'): |
| 165 code = e.getcode() | 170 code = e.getcode() |
| 166 else: | 171 else: |
| 167 code = e.code | 172 code = e.code |
| (...skipping 21 matching lines...) Expand all Loading... |
| 189 resource = field[0:p] + '/openssh-key' | 194 resource = field[0:p] + '/openssh-key' |
| 190 else: | 195 else: |
| 191 key = resource = field | 196 key = resource = field |
| 192 val = retry_url(url + resource) | 197 val = retry_url(url + resource) |
| 193 p = val.find('\n') | 198 p = val.find('\n') |
| 194 if p > 0: | 199 if p > 0: |
| 195 val = val.split('\n') | 200 val = val.split('\n') |
| 196 d[key] = val | 201 d[key] = val |
| 197 return d | 202 return d |
| 198 | 203 |
| 199 def get_instance_metadata(version='latest'): | 204 def get_instance_metadata(version='latest', url='http://169.254.169.254'): |
| 200 """ | 205 """ |
| 201 Returns the instance metadata as a nested Python dictionary. | 206 Returns the instance metadata as a nested Python dictionary. |
| 202 Simple values (e.g. local_hostname, hostname, etc.) will be | 207 Simple values (e.g. local_hostname, hostname, etc.) will be |
| 203 stored as string values. Values such as ancestor-ami-ids will | 208 stored as string values. Values such as ancestor-ami-ids will |
| 204 be stored in the dict as a list of string values. More complex | 209 be stored in the dict as a list of string values. More complex |
| 205 fields such as public-keys and will be stored as nested dicts. | 210 fields such as public-keys and will be stored as nested dicts. |
| 206 """ | 211 """ |
| 207 url = 'http://169.254.169.254/%s/meta-data/' % version | 212 return _get_instance_metadata('%s/%s/meta-data/' % (url, version)) |
| 208 return _get_instance_metadata(url) | |
| 209 | 213 |
| 210 def get_instance_userdata(version='latest', sep=None): | 214 def get_instance_userdata(version='latest', sep=None, |
| 211 url = 'http://169.254.169.254/%s/user-data' % version | 215 url='http://169.254.169.254'): |
| 212 user_data = retry_url(url, retry_on_404=False) | 216 ud_url = '%s/%s/user-data' % (url,version) |
| 217 user_data = retry_url(ud_url, retry_on_404=False) |
| 213 if user_data: | 218 if user_data: |
| 214 if sep: | 219 if sep: |
| 215 l = user_data.split(sep) | 220 l = user_data.split(sep) |
| 216 user_data = {} | 221 user_data = {} |
| 217 for nvpair in l: | 222 for nvpair in l: |
| 218 t = nvpair.split('=') | 223 t = nvpair.split('=') |
| 219 user_data[t[0].strip()] = t[1].strip() | 224 user_data[t[0].strip()] = t[1].strip() |
| 220 return user_data | 225 return user_data |
| 221 | 226 |
| 222 ISO8601 = '%Y-%m-%dT%H:%M:%SZ' | 227 ISO8601 = '%Y-%m-%dT%H:%M:%SZ' |
| 228 ISO8601_MS = '%Y-%m-%dT%H:%M:%S.%fZ' |
| 223 | 229 |
| 224 def get_ts(ts=None): | 230 def get_ts(ts=None): |
| 225 if not ts: | 231 if not ts: |
| 226 ts = time.gmtime() | 232 ts = time.gmtime() |
| 227 return time.strftime(ISO8601, ts) | 233 return time.strftime(ISO8601, ts) |
| 228 | 234 |
| 229 def parse_ts(ts): | 235 def parse_ts(ts): |
| 230 return datetime.datetime.strptime(ts, ISO8601) | 236 try: |
| 237 dt = datetime.datetime.strptime(ts, ISO8601) |
| 238 return dt |
| 239 except ValueError: |
| 240 dt = datetime.datetime.strptime(ts, ISO8601_MS) |
| 241 return dt |
| 231 | 242 |
| 232 def find_class(module_name, class_name=None): | 243 def find_class(module_name, class_name=None): |
| 233 if class_name: | 244 if class_name: |
| 234 module_name = "%s.%s" % (module_name, class_name) | 245 module_name = "%s.%s" % (module_name, class_name) |
| 235 modules = module_name.split('.') | 246 modules = module_name.split('.') |
| 236 c = None | 247 c = None |
| 237 | 248 |
| 238 try: | 249 try: |
| 239 for m in modules[1:]: | 250 for m in modules[1:]: |
| 240 if c: | 251 if c: |
| (...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 495 item.next.previous = previous | 506 item.next.previous = previous |
| 496 else: | 507 else: |
| 497 self.tail = previous | 508 self.tail = previous |
| 498 | 509 |
| 499 item.previous = None | 510 item.previous = None |
| 500 item.next = self.head | 511 item.next = self.head |
| 501 self.head.previous = self.head = item | 512 self.head.previous = self.head = item |
| 502 | 513 |
| 503 class Password(object): | 514 class Password(object): |
| 504 """ | 515 """ |
| 505 Password object that stores itself as SHA512 hashed. | 516 Password object that stores itself as hashed. |
| 517 Hash defaults to SHA512 if available, MD5 otherwise. |
| 506 """ | 518 """ |
| 507 def __init__(self, str=None): | 519 hashfunc=_hashfn |
| 520 def __init__(self, str=None, hashfunc=None): |
| 508 """ | 521 """ |
| 509 Load the string from an initial value, this should be the raw SHA512 has
hed password | 522 Load the string from an initial value, this should be the raw hashed pas
sword. |
| 510 """ | 523 """ |
| 511 self.str = str | 524 self.str = str |
| 525 if hashfunc: |
| 526 self.hashfunc = hashfunc |
| 512 | 527 |
| 513 def set(self, value): | 528 def set(self, value): |
| 514 self.str = _hashfn(value).hexdigest() | 529 self.str = self.hashfunc(value).hexdigest() |
| 515 | 530 |
| 516 def __str__(self): | 531 def __str__(self): |
| 517 return str(self.str) | 532 return str(self.str) |
| 518 | 533 |
| 519 def __eq__(self, other): | 534 def __eq__(self, other): |
| 520 if other == None: | 535 if other == None: |
| 521 return False | 536 return False |
| 522 return str(_hashfn(other).hexdigest()) == str(self.str) | 537 return str(self.hashfunc(other).hexdigest()) == str(self.str) |
| 523 | 538 |
| 524 def __len__(self): | 539 def __len__(self): |
| 525 if self.str: | 540 if self.str: |
| 526 return len(self.str) | 541 return len(self.str) |
| 527 else: | 542 else: |
| 528 return 0 | 543 return 0 |
| 529 | 544 |
| 530 def notify(subject, body=None, html_body=None, to_string=None, attachments=[], a
ppend_instance_id=True): | 545 def notify(subject, body=None, html_body=None, to_string=None, attachments=None,
append_instance_id=True): |
| 546 attachments = attachments or [] |
| 531 if append_instance_id: | 547 if append_instance_id: |
| 532 subject = "[%s] %s" % (boto.config.get_value("Instance", "instance-id"),
subject) | 548 subject = "[%s] %s" % (boto.config.get_value("Instance", "instance-id"),
subject) |
| 533 if not to_string: | 549 if not to_string: |
| 534 to_string = boto.config.get_value('Notification', 'smtp_to', None) | 550 to_string = boto.config.get_value('Notification', 'smtp_to', None) |
| 535 if to_string: | 551 if to_string: |
| 536 try: | 552 try: |
| 537 from_string = boto.config.get_value('Notification', 'smtp_from', 'bo
to') | 553 from_string = boto.config.get_value('Notification', 'smtp_from', 'bo
to') |
| 538 msg = MIMEMultipart() | 554 msg = MIMEMultipart() |
| 539 msg['From'] = from_string | 555 msg['From'] = from_string |
| 540 msg['Reply-To'] = from_string | 556 msg['Reply-To'] = from_string |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 596 s = '' | 612 s = '' |
| 597 if name[0].isupper: | 613 if name[0].isupper: |
| 598 s = name[0].lower() | 614 s = name[0].lower() |
| 599 for c in name[1:]: | 615 for c in name[1:]: |
| 600 if c.isupper(): | 616 if c.isupper(): |
| 601 s += sep + c.lower() | 617 s += sep + c.lower() |
| 602 else: | 618 else: |
| 603 s += c | 619 s += c |
| 604 return s | 620 return s |
| 605 | 621 |
| 606 def awsify_name(name): | 622 def write_mime_multipart(content, compress=False, deftype='text/plain', delimite
r=':'): |
| 607 return name[0:1].upper()+name[1:] | 623 """Description: |
| 624 :param content: A list of tuples of name-content pairs. This is used |
| 625 instead of a dict to ensure that scripts run in order |
| 626 :type list of tuples: |
| 627 |
| 628 :param compress: Use gzip to compress the scripts, defaults to no compressio
n |
| 629 :type bool: |
| 630 |
| 631 :param deftype: The type that should be assumed if nothing else can be figur
ed out |
| 632 :type str: |
| 633 |
| 634 :param delimiter: mime delimiter |
| 635 :type str: |
| 636 |
| 637 :return: Final mime multipart |
| 638 :rtype: str: |
| 639 """ |
| 640 wrapper = MIMEMultipart() |
| 641 for name,con in content: |
| 642 definite_type = guess_mime_type(con, deftype) |
| 643 maintype, subtype = definite_type.split('/', 1) |
| 644 if maintype == 'text': |
| 645 mime_con = MIMEText(con, _subtype=subtype) |
| 646 else: |
| 647 mime_con = MIMEBase(maintype, subtype) |
| 648 mime_con.set_payload(con) |
| 649 # Encode the payload using Base64 |
| 650 Encoders.encode_base64(mime_con) |
| 651 mime_con.add_header('Content-Disposition', 'attachment', filename=name) |
| 652 wrapper.attach(mime_con) |
| 653 rcontent = wrapper.as_string() |
| 654 |
| 655 if compress: |
| 656 buf = StringIO.StringIO() |
| 657 gz = gzip.GzipFile(mode='wb', fileobj=buf) |
| 658 try: |
| 659 gz.write(rcontent) |
| 660 finally: |
| 661 gz.close() |
| 662 rcontent = buf.getvalue() |
| 663 |
| 664 return rcontent |
| 665 |
| 666 def guess_mime_type(content, deftype): |
| 667 """Description: Guess the mime type of a block of text |
| 668 :param content: content we're finding the type of |
| 669 :type str: |
| 670 |
| 671 :param deftype: Default mime type |
| 672 :type str: |
| 673 |
| 674 :rtype: <type>: |
| 675 :return: <description> |
| 676 """ |
| 677 #Mappings recognized by cloudinit |
| 678 starts_with_mappings={ |
| 679 '#include' : 'text/x-include-url', |
| 680 '#!' : 'text/x-shellscript', |
| 681 '#cloud-config' : 'text/cloud-config', |
| 682 '#upstart-job' : 'text/upstart-job', |
| 683 '#part-handler' : 'text/part-handler', |
| 684 '#cloud-boothook' : 'text/cloud-boothook' |
| 685 } |
| 686 rtype = deftype |
| 687 for possible_type,mimetype in starts_with_mappings.items(): |
| 688 if content.startswith(possible_type): |
| 689 rtype = mimetype |
| 690 break |
| 691 return(rtype) |
| OLD | NEW |