76
from boto.compat import json
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',
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.)
92
# websiteConfig is a QSA for buckets in Google Cloud Storage.
94
# compose is a QSA for objects in Google Cloud Storage.
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])')
82
103
def unquote_v(nv):
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()
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'] = ''
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'] = ''
110
134
# if you're using expires for query string auth, then it trumps date
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
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]
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]
139
162
buf += '&'.join(qsa)
143
167
def merge_meta(headers, metadata, provider=None):
145
169
provider = boto.provider.get_default()
170
195
del headers[hkey]
173
199
def retry_url(url, retry_on_404=True, num_retries=10):
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.
174
206
for i in range(0, num_retries):
208
proxy_handler = urllib2.ProxyHandler({})
209
opener = urllib2.build_opener(proxy_handler)
176
210
req = urllib2.Request(url)
177
resp = urllib2.urlopen(req)
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'):
185
220
if code == 404 and not retry_on_404:
189
224
boto.log.exception('Caught exception reading instance data')
225
# If not on the last iteration of the loop then sleep.
226
if i + 1 != num_retries:
191
228
boto.log.error('Unable to read instance data, giving up')
194
def _get_instance_metadata(url):
196
data = retry_url(url)
198
fields = data.split('\n')
200
if field.endswith('/'):
201
d[field[0:-1]] = _get_instance_metadata(url + field)
206
resource = field[0:p] + '/openssh-key'
232
def _get_instance_metadata(url, num_retries):
233
return LazyLoadMetadata(url, num_retries)
236
class LazyLoadMetadata(dict):
237
def __init__(self, url, num_retries):
239
self._num_retries = num_retries
242
data = boto.utils.retry_url(self._url, num_retries=self._num_retries)
244
fields = data.split('\n')
246
if field.endswith('/'):
248
self._dicts.append(key)
208
key = resource = field
209
val = retry_url(url + resource)
253
resource = field[0:p] + '/openssh-key'
255
key = resource = field
256
self._leaves[key] = resource
259
def _materialize(self):
263
def __getitem__(self, key):
265
# allow dict to throw the KeyError
266
return super(LazyLoadMetadata, self).__getitem__(key)
269
val = super(LazyLoadMetadata, self).__getitem__(key)
273
if key in self._leaves:
274
resource = self._leaves[key]
275
val = boto.utils.retry_url(self._url + urllib.quote(resource,
277
num_retries=self._num_retries)
278
if val and val[0] == '{':
279
val = json.loads(val)
210
281
p = val.find('\n')
212
283
val = val.split('\n')
216
def get_instance_metadata(version='latest', url='http://169.254.169.254'):
285
elif key in self._dicts:
286
self[key] = LazyLoadMetadata(self._url + key + '/',
289
return super(LazyLoadMetadata, self).__getitem__(key)
291
def get(self, key, default=None):
299
return super(LazyLoadMetadata, self).values()
303
return super(LazyLoadMetadata, self).items()
307
return super(LazyLoadMetadata, self).__str__()
311
return super(LazyLoadMetadata, self).__repr__()
314
def _build_instance_metadata_url(url, version, path):
316
Builds an EC2 metadata URL for fetching information about an instance.
318
Requires the following arguments: a URL, a version and a path.
322
>>> _build_instance_metadata_url('http://169.254.169.254', 'latest', 'meta-data')
323
http://169.254.169.254/latest/meta-data/
326
return '%s/%s/%s/' % (url, version, path)
329
def get_instance_metadata(version='latest', url='http://169.254.169.254',
330
data='meta-data', timeout=None, num_retries=5):
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.
224
return _get_instance_metadata('%s/%s/meta-data/' % (url, version))
338
If the timeout is specified, the connection to the specified url
339
will time out after the specified number of seconds.
342
if timeout is not None:
343
original = socket.getdefaulttimeout()
344
socket.setdefaulttimeout(timeout)
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:
351
if timeout is not None:
352
socket.setdefaulttimeout(original)
355
def get_instance_identity(version='latest', url='http://169.254.169.254',
356
timeout=None, num_retries=5):
358
Returns the instance identity as a nested Python dictionary.
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)
366
data = retry_url(base_url, num_retries=num_retries)
367
fields = data.split('\n')
369
val = retry_url(base_url + '/' + field + '/')
371
val = json.loads(val)
375
except urllib2.URLError, e:
378
if timeout is not None:
379
socket.setdefaulttimeout(original)
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)
314
480
class ShellCommand(object):
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()
321
487
self.fail_fast = fail_fast
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,
330
498
while self.process.poll() == None:
367
536
args=('localhost', 'username', 'password', 'from@abc', ['user1@abc', 'user2@xyz'], 'Logger Subject')
370
def __init__(self, mailhost, username, password, fromaddr, toaddrs, subject):
539
def __init__(self, mailhost, username, password,
540
fromaddr, toaddrs, subject):
372
542
Initialize the handler.
374
544
We have extended the constructor to accept a username/password
375
545
for SMTP authentication.
377
logging.handlers.SMTPHandler.__init__(self, mailhost, fromaddr, toaddrs, subject)
547
logging.handlers.SMTPHandler.__init__(self, mailhost, fromaddr,
378
549
self.username = username
379
550
self.password = password
381
552
def emit(self, record):
650
836
:return: Final mime multipart
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)
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()
688
875
:return: <description>
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'
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):
706
894
def compute_md5(fp, buf_size=8192, size=None):
708
896
Compute MD5 hash on passed file and return results in a tuple of values.