~ubuntu-branches/ubuntu/trusty/python-boto/trusty

« back to all changes in this revision

Viewing changes to boto/utils.py

  • Committer: Package Import Robot
  • Author(s): Eric Evans
  • Date: 2013-05-10 23:38:14 UTC
  • mfrom: (1.1.10) (14.1.2 experimental)
  • Revision ID: package-import@ubuntu.com-20130510233814-701dvlop7xfh88i7
Tags: 2.9.2-1
New upstream release (Closes: #700743).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
 
1
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
2
2
# Copyright (c) 2010, Eucalyptus Systems, Inc.
 
3
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
3
4
# All rights reserved.
4
5
#
5
6
# Permission is hereby granted, free of charge, to any person obtaining a
16
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
18
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
18
19
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19
 
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 
20
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
21
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
22
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
23
# IN THE SOFTWARE.
38
39
Some handy utility functions used by several classes.
39
40
"""
40
41
 
 
42
import socket
41
43
import urllib
42
44
import urllib2
43
45
import imp
50
52
import tempfile
51
53
import smtplib
52
54
import datetime
53
 
from email.MIMEMultipart import MIMEMultipart
54
 
from email.MIMEBase import MIMEBase
55
 
from email.MIMEText import MIMEText
56
 
from email.Utils import formatdate
57
 
from email import Encoders
 
55
import re
 
56
import email.mime.multipart
 
57
import email.mime.base
 
58
import email.mime.text
 
59
import email.utils
 
60
import email.encoders
58
61
import gzip
59
62
import base64
60
63
try:
70
73
    import md5
71
74
    _hashfn = md5.md5
72
75
 
 
76
from boto.compat import json
 
77
 
73
78
# List of Query String Arguments of Interest
74
 
qsa_of_interest = ['acl', 'defaultObjectAcl', 'location', 'logging', 
75
 
                   'partNumber', 'policy', 'requestPayment', 'torrent', 
76
 
                   'versioning', 'versionId', 'versions', 'website', 
77
 
                   'uploads', 'uploadId', 'response-content-type', 
78
 
                   'response-content-language', 'response-expires', 
 
79
qsa_of_interest = ['acl', 'cors', 'defaultObjectAcl', 'location', 'logging',
 
80
                   'partNumber', 'policy', 'requestPayment', 'torrent',
 
81
                   'versioning', 'versionId', 'versions', 'website',
 
82
                   'uploads', 'uploadId', 'response-content-type',
 
83
                   'response-content-language', 'response-expires',
79
84
                   'response-cache-control', 'response-content-disposition',
80
 
                   'response-content-encoding', 'delete', 'lifecycle']
 
85
                   'response-content-encoding', 'delete', 'lifecycle',
 
86
                   'tagging', 'restore',
 
87
                   # storageClass is a QSA for buckets in Google Cloud Storage.
 
88
                   # (StorageClass is associated to individual keys in S3, but
 
89
                   # having it listed here should cause no problems because
 
90
                   # GET bucket?storageClass is not part of the S3 API.)
 
91
                   'storageClass',
 
92
                   # websiteConfig is a QSA for buckets in Google Cloud Storage.
 
93
                   'websiteConfig',
 
94
                   # compose is a QSA for objects in Google Cloud Storage.
 
95
                   'compose']
 
96
 
 
97
 
 
98
_first_cap_regex = re.compile('(.)([A-Z][a-z]+)')
 
99
_number_cap_regex = re.compile('([a-z])([0-9]+)')
 
100
_end_cap_regex = re.compile('([a-z0-9])([A-Z])')
 
101
 
81
102
 
82
103
def unquote_v(nv):
83
104
    if len(nv) == 1:
85
106
    else:
86
107
        return (nv[0], urllib.unquote(nv[1]))
87
108
 
88
 
# generates the aws canonical string for the given parameters
 
109
 
89
110
def canonical_string(method, path, headers, expires=None,
90
111
                     provider=None):
 
112
    """
 
113
    Generates the aws canonical string for the given parameters
 
114
    """
91
115
    if not provider:
92
116
        provider = boto.provider.get_default()
93
117
    interesting_headers = {}
95
119
        lk = key.lower()
96
120
        if headers[key] != None and (lk in ['content-md5', 'content-type', 'date'] or
97
121
                                     lk.startswith(provider.header_prefix)):
98
 
            interesting_headers[lk] = headers[key].strip()
 
122
            interesting_headers[lk] = str(headers[key]).strip()
99
123
 
100
124
    # these keys get empty strings if they don't exist
101
 
    if not interesting_headers.has_key('content-type'):
 
125
    if 'content-type' not in interesting_headers:
102
126
        interesting_headers['content-type'] = ''
103
 
    if not interesting_headers.has_key('content-md5'):
 
127
    if 'content-md5' not in interesting_headers:
104
128
        interesting_headers['content-md5'] = ''
105
129
 
106
130
    # just in case someone used this.  it's not necessary in this lib.
107
 
    if interesting_headers.has_key(provider.date_header):
 
131
    if provider.date_header in interesting_headers:
108
132
        interesting_headers['date'] = ''
109
133
 
110
134
    # if you're using expires for query string auth, then it trumps date
112
136
    if expires:
113
137
        interesting_headers['date'] = str(expires)
114
138
 
115
 
    sorted_header_keys = interesting_headers.keys()
116
 
    sorted_header_keys.sort()
 
139
    sorted_header_keys = sorted(interesting_headers.keys())
117
140
 
118
141
    buf = "%s\n" % method
119
142
    for key in sorted_header_keys:
125
148
 
126
149
    # don't include anything after the first ? in the resource...
127
150
    # unless it is one of the QSA of interest, defined above
128
 
    t =  path.split('?')
 
151
    t = path.split('?')
129
152
    buf += t[0]
130
153
 
131
154
    if len(t) > 1:
132
155
        qsa = t[1].split('&')
133
 
        qsa = [ a.split('=', 1) for a in qsa]
134
 
        qsa = [ unquote_v(a) for a in qsa if a[0] in qsa_of_interest ]
 
156
        qsa = [a.split('=', 1) for a in qsa]
 
157
        qsa = [unquote_v(a) for a in qsa if a[0] in qsa_of_interest]
135
158
        if len(qsa) > 0:
136
 
            qsa.sort(cmp=lambda x,y:cmp(x[0], y[0]))
137
 
            qsa = [ '='.join(a) for a in qsa ]
 
159
            qsa.sort(cmp=lambda x, y:cmp(x[0], y[0]))
 
160
            qsa = ['='.join(a) for a in qsa]
138
161
            buf += '?'
139
162
            buf += '&'.join(qsa)
140
163
 
141
164
    return buf
142
165
 
 
166
 
143
167
def merge_meta(headers, metadata, provider=None):
144
168
    if not provider:
145
169
        provider = boto.provider.get_default()
155
179
 
156
180
    return final_headers
157
181
 
 
182
 
158
183
def get_aws_metadata(headers, provider=None):
159
184
    if not provider:
160
185
        provider = boto.provider.get_default()
170
195
            del headers[hkey]
171
196
    return metadata
172
197
 
 
198
 
173
199
def retry_url(url, retry_on_404=True, num_retries=10):
 
200
    """
 
201
    Retry a url.  This is specifically used for accessing the metadata
 
202
    service on an instance.  Since this address should never be proxied
 
203
    (for security reasons), we create a ProxyHandler with a NULL
 
204
    dictionary to override any proxy settings in the environment.
 
205
    """
174
206
    for i in range(0, num_retries):
175
207
        try:
 
208
            proxy_handler = urllib2.ProxyHandler({})
 
209
            opener = urllib2.build_opener(proxy_handler)
176
210
            req = urllib2.Request(url)
177
 
            resp = urllib2.urlopen(req)
178
 
            return resp.read()
 
211
            r = opener.open(req)
 
212
            result = r.read()
 
213
            return result
179
214
        except urllib2.HTTPError, e:
180
215
            # in 2.6 you use getcode(), in 2.5 and earlier you use code
181
216
            if hasattr(e, 'getcode'):
184
219
                code = e.code
185
220
            if code == 404 and not retry_on_404:
186
221
                return ''
187
 
        except:
 
222
        except Exception, e:
188
223
            pass
189
224
        boto.log.exception('Caught exception reading instance data')
190
 
        time.sleep(2**i)
 
225
        # If not on the last iteration of the loop then sleep.
 
226
        if i + 1 != num_retries:
 
227
            time.sleep(2 ** i)
191
228
    boto.log.error('Unable to read instance data, giving up')
192
229
    return ''
193
230
 
194
 
def _get_instance_metadata(url):
195
 
    d = {}
196
 
    data = retry_url(url)
197
 
    if data:
198
 
        fields = data.split('\n')
199
 
        for field in fields:
200
 
            if field.endswith('/'):
201
 
                d[field[0:-1]] = _get_instance_metadata(url + field)
202
 
            else:
203
 
                p = field.find('=')
204
 
                if p > 0:
205
 
                    key = field[p+1:]
206
 
                    resource = field[0:p] + '/openssh-key'
 
231
 
 
232
def _get_instance_metadata(url, num_retries):
 
233
    return LazyLoadMetadata(url, num_retries)
 
234
 
 
235
 
 
236
class LazyLoadMetadata(dict):
 
237
    def __init__(self, url, num_retries):
 
238
        self._url = url
 
239
        self._num_retries = num_retries
 
240
        self._leaves = {}
 
241
        self._dicts = []
 
242
        data = boto.utils.retry_url(self._url, num_retries=self._num_retries)
 
243
        if data:
 
244
            fields = data.split('\n')
 
245
            for field in fields:
 
246
                if field.endswith('/'):
 
247
                    key = field[0:-1]
 
248
                    self._dicts.append(key)
207
249
                else:
208
 
                    key = resource = field
209
 
                val = retry_url(url + resource)
 
250
                    p = field.find('=')
 
251
                    if p > 0:
 
252
                        key = field[p + 1:]
 
253
                        resource = field[0:p] + '/openssh-key'
 
254
                    else:
 
255
                        key = resource = field
 
256
                    self._leaves[key] = resource
 
257
                self[key] = None
 
258
 
 
259
    def _materialize(self):
 
260
        for key in self:
 
261
            self[key]
 
262
 
 
263
    def __getitem__(self, key):
 
264
        if key not in self:
 
265
            # allow dict to throw the KeyError
 
266
            return super(LazyLoadMetadata, self).__getitem__(key)
 
267
 
 
268
        # already loaded
 
269
        val = super(LazyLoadMetadata, self).__getitem__(key)
 
270
        if val is not None:
 
271
            return val
 
272
 
 
273
        if key in self._leaves:
 
274
            resource = self._leaves[key]
 
275
            val = boto.utils.retry_url(self._url + urllib.quote(resource,
 
276
                                                                safe="/:"),
 
277
                                       num_retries=self._num_retries)
 
278
            if val and val[0] == '{':
 
279
                val = json.loads(val)
 
280
            else:
210
281
                p = val.find('\n')
211
282
                if p > 0:
212
283
                    val = val.split('\n')
213
 
                d[key] = val
214
 
    return d
215
 
 
216
 
def get_instance_metadata(version='latest', url='http://169.254.169.254'):
 
284
            self[key] = val
 
285
        elif key in self._dicts:
 
286
            self[key] = LazyLoadMetadata(self._url + key + '/',
 
287
                                         self._num_retries)
 
288
 
 
289
        return super(LazyLoadMetadata, self).__getitem__(key)
 
290
 
 
291
    def get(self, key, default=None):
 
292
        try:
 
293
            return self[key]
 
294
        except KeyError:
 
295
            return default
 
296
 
 
297
    def values(self):
 
298
        self._materialize()
 
299
        return super(LazyLoadMetadata, self).values()
 
300
 
 
301
    def items(self):
 
302
        self._materialize()
 
303
        return super(LazyLoadMetadata, self).items()
 
304
 
 
305
    def __str__(self):
 
306
        self._materialize()
 
307
        return super(LazyLoadMetadata, self).__str__()
 
308
 
 
309
    def __repr__(self):
 
310
        self._materialize()
 
311
        return super(LazyLoadMetadata, self).__repr__()
 
312
 
 
313
 
 
314
def _build_instance_metadata_url(url, version, path):
 
315
    """
 
316
    Builds an EC2 metadata URL for fetching information about an instance.
 
317
 
 
318
    Requires the following arguments: a URL, a version and a path.
 
319
 
 
320
    Example:
 
321
 
 
322
        >>> _build_instance_metadata_url('http://169.254.169.254', 'latest', 'meta-data')
 
323
        http://169.254.169.254/latest/meta-data/
 
324
 
 
325
    """
 
326
    return '%s/%s/%s/' % (url, version, path)
 
327
 
 
328
 
 
329
def get_instance_metadata(version='latest', url='http://169.254.169.254',
 
330
                          data='meta-data', timeout=None, num_retries=5):
217
331
    """
218
332
    Returns the instance metadata as a nested Python dictionary.
219
333
    Simple values (e.g. local_hostname, hostname, etc.) will be
220
334
    stored as string values.  Values such as ancestor-ami-ids will
221
335
    be stored in the dict as a list of string values.  More complex
222
336
    fields such as public-keys and will be stored as nested dicts.
223
 
    """
224
 
    return _get_instance_metadata('%s/%s/meta-data/' % (url, version))
 
337
 
 
338
    If the timeout is specified, the connection to the specified url
 
339
    will time out after the specified number of seconds.
 
340
 
 
341
    """
 
342
    if timeout is not None:
 
343
        original = socket.getdefaulttimeout()
 
344
        socket.setdefaulttimeout(timeout)
 
345
    try:
 
346
        metadata_url = _build_instance_metadata_url(url, version, data)
 
347
        return _get_instance_metadata(metadata_url, num_retries=num_retries)
 
348
    except urllib2.URLError, e:
 
349
        return None
 
350
    finally:
 
351
        if timeout is not None:
 
352
            socket.setdefaulttimeout(original)
 
353
 
 
354
 
 
355
def get_instance_identity(version='latest', url='http://169.254.169.254',
 
356
                          timeout=None, num_retries=5):
 
357
    """
 
358
    Returns the instance identity as a nested Python dictionary.
 
359
    """
 
360
    iid = {}
 
361
    base_url = _build_instance_metadata_url(url, version, 'dynamic/instance-identity')
 
362
    if timeout is not None:
 
363
        original = socket.getdefaulttimeout()
 
364
        socket.setdefaulttimeout(timeout)
 
365
    try:
 
366
        data = retry_url(base_url, num_retries=num_retries)
 
367
        fields = data.split('\n')
 
368
        for field in fields:
 
369
            val = retry_url(base_url + '/' + field + '/')
 
370
            if val[0] == '{':
 
371
                val = json.loads(val)
 
372
            if field:
 
373
                iid[field] = val
 
374
        return iid
 
375
    except urllib2.URLError, e:
 
376
        return None
 
377
    finally:
 
378
        if timeout is not None:
 
379
            socket.setdefaulttimeout(original)
 
380
 
225
381
 
226
382
def get_instance_userdata(version='latest', sep=None,
227
383
                          url='http://169.254.169.254'):
228
 
    ud_url = '%s/%s/user-data' % (url,version)
 
384
    ud_url = _build_instance_metadata_url(url, version, 'user-data')
229
385
    user_data = retry_url(ud_url, retry_on_404=False)
230
386
    if user_data:
231
387
        if sep:
238
394
 
239
395
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
240
396
ISO8601_MS = '%Y-%m-%dT%H:%M:%S.%fZ'
241
 
    
 
397
RFC1123 = '%a, %d %b %Y %H:%M:%S %Z'
 
398
 
242
399
def get_ts(ts=None):
243
400
    if not ts:
244
401
        ts = time.gmtime()
245
402
    return time.strftime(ISO8601, ts)
246
403
 
 
404
 
247
405
def parse_ts(ts):
248
406
    ts = ts.strip()
249
407
    try:
250
408
        dt = datetime.datetime.strptime(ts, ISO8601)
251
409
        return dt
252
410
    except ValueError:
253
 
        dt = datetime.datetime.strptime(ts, ISO8601_MS)
254
 
        return dt
 
411
        try:
 
412
            dt = datetime.datetime.strptime(ts, ISO8601_MS)
 
413
            return dt
 
414
        except ValueError:
 
415
            dt = datetime.datetime.strptime(ts, RFC1123)
 
416
            return dt
255
417
 
256
418
def find_class(module_name, class_name=None):
257
419
    if class_name:
268
430
        return c
269
431
    except:
270
432
        return None
271
 
    
 
433
 
 
434
 
272
435
def update_dme(username, password, dme_id, ip_address):
273
436
    """
274
437
    Update your Dynamic DNS record with DNSMadeEasy.com
278
441
    s = urllib2.urlopen(dme_url % (username, password, dme_id, ip_address))
279
442
    return s.read()
280
443
 
 
444
 
281
445
def fetch_file(uri, file=None, username=None, password=None):
282
446
    """
283
447
    Fetch a file based on the URI provided. If you do not pass in a file pointer
284
 
    a tempfile.NamedTemporaryFile, or None if the file could not be 
 
448
    a tempfile.NamedTemporaryFile, or None if the file could not be
285
449
    retrieved is returned.
286
450
    The URI can be either an HTTP url, or "s3://bucket_name/key_name"
287
451
    """
291
455
    try:
292
456
        if uri.startswith('s3://'):
293
457
            bucket_name, key_name = uri[len('s3://'):].split('/', 1)
294
 
            c = boto.connect_s3(aws_access_key_id=username, aws_secret_access_key=password)
 
458
            c = boto.connect_s3(aws_access_key_id=username,
 
459
                                aws_secret_access_key=password)
295
460
            bucket = c.get_bucket(bucket_name)
296
461
            key = bucket.get_key(key_name)
297
462
            key.get_contents_to_file(file)
311
476
        file = None
312
477
    return file
313
478
 
 
479
 
314
480
class ShellCommand(object):
315
481
 
316
 
    def __init__(self, command, wait=True, fail_fast=False, cwd = None):
 
482
    def __init__(self, command, wait=True, fail_fast=False, cwd=None):
317
483
        self.exit_code = 0
318
484
        self.command = command
319
485
        self.log_fp = StringIO.StringIO()
320
486
        self.wait = wait
321
487
        self.fail_fast = fail_fast
322
 
        self.run(cwd = cwd)
 
488
        self.run(cwd=cwd)
323
489
 
324
490
    def run(self, cwd=None):
325
491
        boto.log.info('running:%s' % self.command)
326
 
        self.process = subprocess.Popen(self.command, shell=True, stdin=subprocess.PIPE,
327
 
                                        stdout=subprocess.PIPE, stderr=subprocess.PIPE,
 
492
        self.process = subprocess.Popen(self.command, shell=True,
 
493
                                        stdin=subprocess.PIPE,
 
494
                                        stdout=subprocess.PIPE,
 
495
                                        stderr=subprocess.PIPE,
328
496
                                        cwd=cwd)
329
497
        if(self.wait):
330
498
            while self.process.poll() == None:
353
521
 
354
522
    output = property(getOutput, setReadOnly, None, 'The STDIN and STDERR output of the command')
355
523
 
 
524
 
356
525
class AuthSMTPHandler(logging.handlers.SMTPHandler):
357
526
    """
358
527
    This class extends the SMTPHandler in the standard Python logging module
359
528
    to accept a username and password on the constructor and to then use those
360
529
    credentials to authenticate with the SMTP server.  To use this, you could
361
530
    add something like this in your boto config file:
362
 
    
 
531
 
363
532
    [handler_hand07]
364
533
    class=boto.utils.AuthSMTPHandler
365
534
    level=WARN
367
536
    args=('localhost', 'username', 'password', 'from@abc', ['user1@abc', 'user2@xyz'], 'Logger Subject')
368
537
    """
369
538
 
370
 
    def __init__(self, mailhost, username, password, fromaddr, toaddrs, subject):
 
539
    def __init__(self, mailhost, username, password,
 
540
                 fromaddr, toaddrs, subject):
371
541
        """
372
542
        Initialize the handler.
373
543
 
374
544
        We have extended the constructor to accept a username/password
375
545
        for SMTP authentication.
376
546
        """
377
 
        logging.handlers.SMTPHandler.__init__(self, mailhost, fromaddr, toaddrs, subject)
 
547
        logging.handlers.SMTPHandler.__init__(self, mailhost, fromaddr,
 
548
                                              toaddrs, subject)
378
549
        self.username = username
379
550
        self.password = password
380
 
        
 
551
 
381
552
    def emit(self, record):
382
553
        """
383
554
        Emit a record.
397
568
                            self.fromaddr,
398
569
                            ','.join(self.toaddrs),
399
570
                            self.getSubject(record),
400
 
                            formatdate(), msg)
 
571
                            email.utils.formatdate(), msg)
401
572
            smtp.sendmail(self.fromaddr, self.toaddrs, msg)
402
573
            smtp.quit()
403
574
        except (KeyboardInterrupt, SystemExit):
405
576
        except:
406
577
            self.handleError(record)
407
578
 
 
579
 
408
580
class LRUCache(dict):
409
581
    """A dictionary-like object that stores only a certain number of items, and
410
582
    discards its least recently used item when full.
411
 
    
 
583
 
412
584
    >>> cache = LRUCache(3)
413
585
    >>> cache['A'] = 0
414
586
    >>> cache['B'] = 1
415
587
    >>> cache['C'] = 2
416
588
    >>> len(cache)
417
589
    3
418
 
    
 
590
 
419
591
    >>> cache['A']
420
592
    0
421
 
    
 
593
 
422
594
    Adding new items to the cache does not increase its size. Instead, the least
423
595
    recently used item is dropped:
424
 
    
 
596
 
425
597
    >>> cache['D'] = 3
426
598
    >>> len(cache)
427
599
    3
428
600
    >>> 'B' in cache
429
601
    False
430
 
    
 
602
 
431
603
    Iterating over the cache returns the keys, starting with the most recently
432
604
    used:
433
 
    
 
605
 
434
606
    >>> for key in cache:
435
607
    ...     print key
436
608
    D
438
610
    C
439
611
 
440
612
    This code is based on the LRUCache class from Genshi which is based on
441
 
    Mighty's LRUCache from ``myghtyutils.util``, written
442
 
    by Mike Bayer and released under the MIT license (Genshi uses the
443
 
    BSD License). See:
444
 
 
445
 
      http://svn.myghty.org/myghtyutils/trunk/lib/myghtyutils/util.py
 
613
    `Myghty <http://www.myghty.org>`_'s LRUCache from ``myghtyutils.util``,
 
614
    written by Mike Bayer and released under the MIT license (Genshi uses the
 
615
    BSD License).
446
616
    """
447
617
 
448
618
    class _Item(object):
450
620
            self.previous = self.next = None
451
621
            self.key = key
452
622
            self.value = value
 
623
 
453
624
        def __repr__(self):
454
625
            return repr(self.value)
455
626
 
524
695
        item.next = self.head
525
696
        self.head.previous = self.head = item
526
697
 
 
698
 
527
699
class Password(object):
528
700
    """
529
701
    Password object that stores itself as hashed.
530
702
    Hash defaults to SHA512 if available, MD5 otherwise.
531
703
    """
532
 
    hashfunc=_hashfn
 
704
    hashfunc = _hashfn
 
705
 
533
706
    def __init__(self, str=None, hashfunc=None):
534
707
        """
535
 
        Load the string from an initial value, this should be the raw hashed password.
 
708
        Load the string from an initial value, this should be the
 
709
        raw hashed password.
536
710
        """
537
711
        self.str = str
538
712
        if hashfunc:
540
714
 
541
715
    def set(self, value):
542
716
        self.str = self.hashfunc(value).hexdigest()
543
 
   
 
717
 
544
718
    def __str__(self):
545
719
        return str(self.str)
546
 
   
 
720
 
547
721
    def __eq__(self, other):
548
722
        if other == None:
549
723
            return False
555
729
        else:
556
730
            return 0
557
731
 
558
 
def notify(subject, body=None, html_body=None, to_string=None, attachments=None, append_instance_id=True):
 
732
 
 
733
def notify(subject, body=None, html_body=None, to_string=None,
 
734
           attachments=None, append_instance_id=True):
559
735
    attachments = attachments or []
560
736
    if append_instance_id:
561
737
        subject = "[%s] %s" % (boto.config.get_value("Instance", "instance-id"), subject)
564
740
    if to_string:
565
741
        try:
566
742
            from_string = boto.config.get_value('Notification', 'smtp_from', 'boto')
567
 
            msg = MIMEMultipart()
 
743
            msg = email.mime.multipart.MIMEMultipart()
568
744
            msg['From'] = from_string
569
745
            msg['Reply-To'] = from_string
570
746
            msg['To'] = to_string
571
 
            msg['Date'] = formatdate(localtime=True)
 
747
            msg['Date'] = email.utils.formatdate(localtime=True)
572
748
            msg['Subject'] = subject
573
 
        
 
749
 
574
750
            if body:
575
 
                msg.attach(MIMEText(body))
 
751
                msg.attach(email.mime.text.MIMEText(body))
576
752
 
577
753
            if html_body:
578
 
                part = MIMEBase('text', 'html')
 
754
                part = email.mime.base.MIMEBase('text', 'html')
579
755
                part.set_payload(html_body)
580
 
                Encoders.encode_base64(part)
 
756
                email.encoders.encode_base64(part)
581
757
                msg.attach(part)
582
758
 
583
759
            for part in attachments:
605
781
        except:
606
782
            boto.log.exception('notify failed')
607
783
 
 
784
 
608
785
def get_utf8_value(value):
609
786
    if not isinstance(value, str) and not isinstance(value, unicode):
610
787
        value = str(value)
613
790
    else:
614
791
        return value
615
792
 
 
793
 
616
794
def mklist(value):
617
795
    if not isinstance(value, list):
618
796
        if isinstance(value, tuple):
621
799
            value = [value]
622
800
    return value
623
801
 
624
 
def pythonize_name(name, sep='_'):
625
 
    s = ''
626
 
    if name[0].isupper:
627
 
        s = name[0].lower()
628
 
    for c in name[1:]:
629
 
        if c.isupper():
630
 
            s += sep + c.lower()
631
 
        else:
632
 
            s += c
633
 
    return s
 
802
 
 
803
def pythonize_name(name):
 
804
    """Convert camel case to a "pythonic" name.
 
805
 
 
806
    Examples::
 
807
 
 
808
        pythonize_name('CamelCase') -> 'camel_case'
 
809
        pythonize_name('already_pythonized') -> 'already_pythonized'
 
810
        pythonize_name('HTTPRequest') -> 'http_request'
 
811
        pythonize_name('HTTPStatus200Ok') -> 'http_status_200_ok'
 
812
        pythonize_name('UPPER') -> 'upper'
 
813
        pythonize_name('') -> ''
 
814
 
 
815
    """
 
816
    s1 = _first_cap_regex.sub(r'\1_\2', name)
 
817
    s2 = _number_cap_regex.sub(r'\1_\2', s1)
 
818
    return _end_cap_regex.sub(r'\1_\2', s2).lower()
 
819
 
634
820
 
635
821
def write_mime_multipart(content, compress=False, deftype='text/plain', delimiter=':'):
636
822
    """Description:
650
836
    :return: Final mime multipart
651
837
    :rtype: str:
652
838
    """
653
 
    wrapper = MIMEMultipart()
654
 
    for name,con in content:
 
839
    wrapper = email.mime.multipart.MIMEMultipart()
 
840
    for name, con in content:
655
841
        definite_type = guess_mime_type(con, deftype)
656
842
        maintype, subtype = definite_type.split('/', 1)
657
843
        if maintype == 'text':
658
 
            mime_con = MIMEText(con, _subtype=subtype)
 
844
            mime_con = email.mime.text.MIMEText(con, _subtype=subtype)
659
845
        else:
660
 
            mime_con = MIMEBase(maintype, subtype)
 
846
            mime_con = email.mime.base.MIMEBase(maintype, subtype)
661
847
            mime_con.set_payload(con)
662
848
            # Encode the payload using Base64
663
 
            Encoders.encode_base64(mime_con)
 
849
            email.encoders.encode_base64(mime_con)
664
850
        mime_con.add_header('Content-Disposition', 'attachment', filename=name)
665
851
        wrapper.attach(mime_con)
666
852
    rcontent = wrapper.as_string()
676
862
 
677
863
    return rcontent
678
864
 
 
865
 
679
866
def guess_mime_type(content, deftype):
680
867
    """Description: Guess the mime type of a block of text
681
868
    :param content: content we're finding the type of
688
875
    :return: <description>
689
876
    """
690
877
    #Mappings recognized by cloudinit
691
 
    starts_with_mappings={
692
 
        '#include' : 'text/x-include-url',
693
 
        '#!' : 'text/x-shellscript',
694
 
        '#cloud-config' : 'text/cloud-config',
695
 
        '#upstart-job'  : 'text/upstart-job',
696
 
        '#part-handler' : 'text/part-handler',
697
 
        '#cloud-boothook' : 'text/cloud-boothook'
 
878
    starts_with_mappings = {
 
879
        '#include': 'text/x-include-url',
 
880
        '#!': 'text/x-shellscript',
 
881
        '#cloud-config': 'text/cloud-config',
 
882
        '#upstart-job': 'text/upstart-job',
 
883
        '#part-handler': 'text/part-handler',
 
884
        '#cloud-boothook': 'text/cloud-boothook'
698
885
    }
699
886
    rtype = deftype
700
 
    for possible_type,mimetype in starts_with_mappings.items():
 
887
    for possible_type, mimetype in starts_with_mappings.items():
701
888
        if content.startswith(possible_type):
702
889
            rtype = mimetype
703
890
            break
704
891
    return(rtype)
705
892
 
 
893
 
706
894
def compute_md5(fp, buf_size=8192, size=None):
707
895
    """
708
896
    Compute MD5 hash on passed file and return results in a tuple of values.
728
916
             plain digest as the second element and the data size as
729
917
             the third element.
730
918
    """
731
 
    m = md5()
 
919
    return compute_hash(fp, buf_size, size, hash_algorithm=md5)
 
920
 
 
921
 
 
922
def compute_hash(fp, buf_size=8192, size=None, hash_algorithm=md5):
 
923
    hash_obj = hash_algorithm()
732
924
    spos = fp.tell()
733
925
    if size and size < buf_size:
734
926
        s = fp.read(size)
735
927
    else:
736
928
        s = fp.read(buf_size)
737
929
    while s:
738
 
        m.update(s)
 
930
        hash_obj.update(s)
739
931
        if size:
740
932
            size -= len(s)
741
933
            if size <= 0:
744
936
            s = fp.read(size)
745
937
        else:
746
938
            s = fp.read(buf_size)
747
 
    hex_md5 = m.hexdigest()
748
 
    base64md5 = base64.encodestring(m.digest())
749
 
    if base64md5[-1] == '\n':
750
 
        base64md5 = base64md5[0:-1]
 
939
    hex_digest = hash_obj.hexdigest()
 
940
    base64_digest = base64.encodestring(hash_obj.digest())
 
941
    if base64_digest[-1] == '\n':
 
942
        base64_digest = base64_digest[0:-1]
751
943
    # data_size based on bytes read.
752
944
    data_size = fp.tell() - spos
753
945
    fp.seek(spos)
754
 
    return (hex_md5, base64md5, data_size)
 
946
    return (hex_digest, base64_digest, data_size)