comparison venv/lib/python2.7/site-packages/boto/provider.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 # Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
2 # Copyright 2010 Google Inc.
3 # Copyright (c) 2010, Eucalyptus Systems, Inc.
4 # Copyright (c) 2011, Nexenta Systems Inc.
5 # All rights reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a
8 # copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish, dis-
11 # tribute, sublicense, and/or sell copies of the Software, and to permit
12 # persons to whom the Software is furnished to do so, subject to the fol-
13 # lowing conditions:
14 #
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
20 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 # IN THE SOFTWARE.
25 """
26 This class encapsulates the provider-specific header differences.
27 """
28
29 import os
30 from boto.compat import six
31 from datetime import datetime
32
33 import boto
34 from boto import config
35 from boto.compat import expanduser
36 from boto.pyami.config import Config
37 from boto.gs.acl import ACL
38 from boto.gs.acl import CannedACLStrings as CannedGSACLStrings
39 from boto.s3.acl import CannedACLStrings as CannedS3ACLStrings
40 from boto.s3.acl import Policy
41
42
43 HEADER_PREFIX_KEY = 'header_prefix'
44 METADATA_PREFIX_KEY = 'metadata_prefix'
45
46 AWS_HEADER_PREFIX = 'x-amz-'
47 GOOG_HEADER_PREFIX = 'x-goog-'
48
49 ACL_HEADER_KEY = 'acl-header'
50 AUTH_HEADER_KEY = 'auth-header'
51 COPY_SOURCE_HEADER_KEY = 'copy-source-header'
52 COPY_SOURCE_VERSION_ID_HEADER_KEY = 'copy-source-version-id-header'
53 COPY_SOURCE_RANGE_HEADER_KEY = 'copy-source-range-header'
54 DELETE_MARKER_HEADER_KEY = 'delete-marker-header'
55 DATE_HEADER_KEY = 'date-header'
56 METADATA_DIRECTIVE_HEADER_KEY = 'metadata-directive-header'
57 RESUMABLE_UPLOAD_HEADER_KEY = 'resumable-upload-header'
58 SECURITY_TOKEN_HEADER_KEY = 'security-token-header'
59 STORAGE_CLASS_HEADER_KEY = 'storage-class'
60 MFA_HEADER_KEY = 'mfa-header'
61 SERVER_SIDE_ENCRYPTION_KEY = 'server-side-encryption-header'
62 VERSION_ID_HEADER_KEY = 'version-id-header'
63 RESTORE_HEADER_KEY = 'restore-header'
64
65 STORAGE_COPY_ERROR = 'StorageCopyError'
66 STORAGE_CREATE_ERROR = 'StorageCreateError'
67 STORAGE_DATA_ERROR = 'StorageDataError'
68 STORAGE_PERMISSIONS_ERROR = 'StoragePermissionsError'
69 STORAGE_RESPONSE_ERROR = 'StorageResponseError'
70 NO_CREDENTIALS_PROVIDED = object()
71
72
73 class ProfileNotFoundError(ValueError):
74 pass
75
76
77 class Provider(object):
78
79 CredentialMap = {
80 'aws': ('aws_access_key_id', 'aws_secret_access_key',
81 'aws_security_token', 'aws_profile'),
82 'google': ('gs_access_key_id', 'gs_secret_access_key',
83 None, None),
84 }
85
86 AclClassMap = {
87 'aws': Policy,
88 'google': ACL
89 }
90
91 CannedAclsMap = {
92 'aws': CannedS3ACLStrings,
93 'google': CannedGSACLStrings
94 }
95
96 HostKeyMap = {
97 'aws': 's3',
98 'google': 'gs'
99 }
100
101 ChunkedTransferSupport = {
102 'aws': False,
103 'google': True
104 }
105
106 MetadataServiceSupport = {
107 'aws': True,
108 'google': False
109 }
110
111 # If you update this map please make sure to put "None" for the
112 # right-hand-side for any headers that don't apply to a provider, rather
113 # than simply leaving that header out (which would cause KeyErrors).
114 HeaderInfoMap = {
115 'aws': {
116 HEADER_PREFIX_KEY: AWS_HEADER_PREFIX,
117 METADATA_PREFIX_KEY: AWS_HEADER_PREFIX + 'meta-',
118 ACL_HEADER_KEY: AWS_HEADER_PREFIX + 'acl',
119 AUTH_HEADER_KEY: 'AWS',
120 COPY_SOURCE_HEADER_KEY: AWS_HEADER_PREFIX + 'copy-source',
121 COPY_SOURCE_VERSION_ID_HEADER_KEY: AWS_HEADER_PREFIX +
122 'copy-source-version-id',
123 COPY_SOURCE_RANGE_HEADER_KEY: AWS_HEADER_PREFIX +
124 'copy-source-range',
125 DATE_HEADER_KEY: AWS_HEADER_PREFIX + 'date',
126 DELETE_MARKER_HEADER_KEY: AWS_HEADER_PREFIX + 'delete-marker',
127 METADATA_DIRECTIVE_HEADER_KEY: AWS_HEADER_PREFIX +
128 'metadata-directive',
129 RESUMABLE_UPLOAD_HEADER_KEY: None,
130 SECURITY_TOKEN_HEADER_KEY: AWS_HEADER_PREFIX + 'security-token',
131 SERVER_SIDE_ENCRYPTION_KEY: AWS_HEADER_PREFIX +
132 'server-side-encryption',
133 VERSION_ID_HEADER_KEY: AWS_HEADER_PREFIX + 'version-id',
134 STORAGE_CLASS_HEADER_KEY: AWS_HEADER_PREFIX + 'storage-class',
135 MFA_HEADER_KEY: AWS_HEADER_PREFIX + 'mfa',
136 RESTORE_HEADER_KEY: AWS_HEADER_PREFIX + 'restore',
137 },
138 'google': {
139 HEADER_PREFIX_KEY: GOOG_HEADER_PREFIX,
140 METADATA_PREFIX_KEY: GOOG_HEADER_PREFIX + 'meta-',
141 ACL_HEADER_KEY: GOOG_HEADER_PREFIX + 'acl',
142 AUTH_HEADER_KEY: 'GOOG1',
143 COPY_SOURCE_HEADER_KEY: GOOG_HEADER_PREFIX + 'copy-source',
144 COPY_SOURCE_VERSION_ID_HEADER_KEY: GOOG_HEADER_PREFIX +
145 'copy-source-version-id',
146 COPY_SOURCE_RANGE_HEADER_KEY: None,
147 DATE_HEADER_KEY: GOOG_HEADER_PREFIX + 'date',
148 DELETE_MARKER_HEADER_KEY: GOOG_HEADER_PREFIX + 'delete-marker',
149 METADATA_DIRECTIVE_HEADER_KEY: GOOG_HEADER_PREFIX +
150 'metadata-directive',
151 RESUMABLE_UPLOAD_HEADER_KEY: GOOG_HEADER_PREFIX + 'resumable',
152 SECURITY_TOKEN_HEADER_KEY: GOOG_HEADER_PREFIX + 'security-token',
153 SERVER_SIDE_ENCRYPTION_KEY: None,
154 # Note that this version header is not to be confused with
155 # the Google Cloud Storage 'x-goog-api-version' header.
156 VERSION_ID_HEADER_KEY: GOOG_HEADER_PREFIX + 'version-id',
157 STORAGE_CLASS_HEADER_KEY: None,
158 MFA_HEADER_KEY: None,
159 RESTORE_HEADER_KEY: None,
160 }
161 }
162
163 ErrorMap = {
164 'aws': {
165 STORAGE_COPY_ERROR: boto.exception.S3CopyError,
166 STORAGE_CREATE_ERROR: boto.exception.S3CreateError,
167 STORAGE_DATA_ERROR: boto.exception.S3DataError,
168 STORAGE_PERMISSIONS_ERROR: boto.exception.S3PermissionsError,
169 STORAGE_RESPONSE_ERROR: boto.exception.S3ResponseError,
170 },
171 'google': {
172 STORAGE_COPY_ERROR: boto.exception.GSCopyError,
173 STORAGE_CREATE_ERROR: boto.exception.GSCreateError,
174 STORAGE_DATA_ERROR: boto.exception.GSDataError,
175 STORAGE_PERMISSIONS_ERROR: boto.exception.GSPermissionsError,
176 STORAGE_RESPONSE_ERROR: boto.exception.GSResponseError,
177 }
178 }
179
180 def __init__(self, name, access_key=None, secret_key=None,
181 security_token=None, profile_name=None):
182 self.host = None
183 self.port = None
184 self.host_header = None
185 self.access_key = access_key
186 self.secret_key = secret_key
187 self.security_token = security_token
188 self.profile_name = profile_name
189 self.name = name
190 self.acl_class = self.AclClassMap[self.name]
191 self.canned_acls = self.CannedAclsMap[self.name]
192 self._credential_expiry_time = None
193
194 # Load shared credentials file if it exists
195 shared_path = os.path.join(expanduser('~'), '.' + name, 'credentials')
196 self.shared_credentials = Config(do_load=False)
197 if os.path.isfile(shared_path):
198 self.shared_credentials.load_from_path(shared_path)
199
200 self.get_credentials(access_key, secret_key, security_token, profile_name)
201 self.configure_headers()
202 self.configure_errors()
203
204 # Allow config file to override default host and port.
205 host_opt_name = '%s_host' % self.HostKeyMap[self.name]
206 if config.has_option('Credentials', host_opt_name):
207 self.host = config.get('Credentials', host_opt_name)
208 port_opt_name = '%s_port' % self.HostKeyMap[self.name]
209 if config.has_option('Credentials', port_opt_name):
210 self.port = config.getint('Credentials', port_opt_name)
211 host_header_opt_name = '%s_host_header' % self.HostKeyMap[self.name]
212 if config.has_option('Credentials', host_header_opt_name):
213 self.host_header = config.get('Credentials', host_header_opt_name)
214
215 def get_access_key(self):
216 if self._credentials_need_refresh():
217 self._populate_keys_from_metadata_server()
218 return self._access_key
219
220 def set_access_key(self, value):
221 self._access_key = value
222
223 access_key = property(get_access_key, set_access_key)
224
225 def get_secret_key(self):
226 if self._credentials_need_refresh():
227 self._populate_keys_from_metadata_server()
228 return self._secret_key
229
230 def set_secret_key(self, value):
231 self._secret_key = value
232
233 secret_key = property(get_secret_key, set_secret_key)
234
235 def get_security_token(self):
236 if self._credentials_need_refresh():
237 self._populate_keys_from_metadata_server()
238 return self._security_token
239
240 def set_security_token(self, value):
241 self._security_token = value
242
243 security_token = property(get_security_token, set_security_token)
244
245 def _credentials_need_refresh(self):
246 if self._credential_expiry_time is None:
247 return False
248 else:
249 # The credentials should be refreshed if they're going to expire
250 # in less than 5 minutes.
251 delta = self._credential_expiry_time - datetime.utcnow()
252 # python2.6 does not have timedelta.total_seconds() so we have
253 # to calculate this ourselves. This is straight from the
254 # datetime docs.
255 seconds_left = (
256 (delta.microseconds + (delta.seconds + delta.days * 24 * 3600)
257 * 10 ** 6) / 10 ** 6)
258 if seconds_left < (5 * 60):
259 boto.log.debug("Credentials need to be refreshed.")
260 return True
261 else:
262 return False
263
264 def get_credentials(self, access_key=None, secret_key=None,
265 security_token=None, profile_name=None):
266 access_key_name, secret_key_name, security_token_name, \
267 profile_name_name = self.CredentialMap[self.name]
268
269 # Load profile from shared environment variable if it was not
270 # already passed in and the environment variable exists
271 if profile_name is None and profile_name_name is not None and \
272 profile_name_name.upper() in os.environ:
273 profile_name = os.environ[profile_name_name.upper()]
274
275 shared = self.shared_credentials
276
277 if access_key is not None:
278 self.access_key = access_key
279 boto.log.debug("Using access key provided by client.")
280 elif access_key_name.upper() in os.environ:
281 self.access_key = os.environ[access_key_name.upper()]
282 boto.log.debug("Using access key found in environment variable.")
283 elif profile_name is not None:
284 if shared.has_option(profile_name, access_key_name):
285 self.access_key = shared.get(profile_name, access_key_name)
286 boto.log.debug("Using access key found in shared credential "
287 "file for profile %s." % profile_name)
288 elif config.has_option("profile %s" % profile_name,
289 access_key_name):
290 self.access_key = config.get("profile %s" % profile_name,
291 access_key_name)
292 boto.log.debug("Using access key found in config file: "
293 "profile %s." % profile_name)
294 else:
295 raise ProfileNotFoundError('Profile "%s" not found!' %
296 profile_name)
297 elif shared.has_option('default', access_key_name):
298 self.access_key = shared.get('default', access_key_name)
299 boto.log.debug("Using access key found in shared credential file.")
300 elif config.has_option('Credentials', access_key_name):
301 self.access_key = config.get('Credentials', access_key_name)
302 boto.log.debug("Using access key found in config file.")
303
304 if secret_key is not None:
305 self.secret_key = secret_key
306 boto.log.debug("Using secret key provided by client.")
307 elif secret_key_name.upper() in os.environ:
308 self.secret_key = os.environ[secret_key_name.upper()]
309 boto.log.debug("Using secret key found in environment variable.")
310 elif profile_name is not None:
311 if shared.has_option(profile_name, secret_key_name):
312 self.secret_key = shared.get(profile_name, secret_key_name)
313 boto.log.debug("Using secret key found in shared credential "
314 "file for profile %s." % profile_name)
315 elif config.has_option("profile %s" % profile_name, secret_key_name):
316 self.secret_key = config.get("profile %s" % profile_name,
317 secret_key_name)
318 boto.log.debug("Using secret key found in config file: "
319 "profile %s." % profile_name)
320 else:
321 raise ProfileNotFoundError('Profile "%s" not found!' %
322 profile_name)
323 elif shared.has_option('default', secret_key_name):
324 self.secret_key = shared.get('default', secret_key_name)
325 boto.log.debug("Using secret key found in shared credential file.")
326 elif config.has_option('Credentials', secret_key_name):
327 self.secret_key = config.get('Credentials', secret_key_name)
328 boto.log.debug("Using secret key found in config file.")
329 elif config.has_option('Credentials', 'keyring'):
330 keyring_name = config.get('Credentials', 'keyring')
331 try:
332 import keyring
333 except ImportError:
334 boto.log.error("The keyring module could not be imported. "
335 "For keyring support, install the keyring "
336 "module.")
337 raise
338 self.secret_key = keyring.get_password(
339 keyring_name, self.access_key)
340 boto.log.debug("Using secret key found in keyring.")
341
342 if security_token is not None:
343 self.security_token = security_token
344 boto.log.debug("Using security token provided by client.")
345 elif ((security_token_name is not None) and
346 (access_key is None) and (secret_key is None)):
347 # Only provide a token from the environment/config if the
348 # caller did not specify a key and secret. Otherwise an
349 # environment/config token could be paired with a
350 # different set of credentials provided by the caller
351 if security_token_name.upper() in os.environ:
352 self.security_token = os.environ[security_token_name.upper()]
353 boto.log.debug("Using security token found in environment"
354 " variable.")
355 elif shared.has_option(profile_name or 'default',
356 security_token_name):
357 self.security_token = shared.get(profile_name or 'default',
358 security_token_name)
359 boto.log.debug("Using security token found in shared "
360 "credential file.")
361 elif profile_name is not None:
362 if config.has_option("profile %s" % profile_name,
363 security_token_name):
364 boto.log.debug("config has option")
365 self.security_token = config.get("profile %s" % profile_name,
366 security_token_name)
367 boto.log.debug("Using security token found in config file: "
368 "profile %s." % profile_name)
369 elif config.has_option('Credentials', security_token_name):
370 self.security_token = config.get('Credentials',
371 security_token_name)
372 boto.log.debug("Using security token found in config file.")
373
374 if ((self._access_key is None or self._secret_key is None) and
375 self.MetadataServiceSupport[self.name]):
376 self._populate_keys_from_metadata_server()
377 self._secret_key = self._convert_key_to_str(self._secret_key)
378
379 def _populate_keys_from_metadata_server(self):
380 # get_instance_metadata is imported here because of a circular
381 # dependency.
382 boto.log.debug("Retrieving credentials from metadata server.")
383 from boto.utils import get_instance_metadata
384 timeout = config.getfloat('Boto', 'metadata_service_timeout', 1.0)
385 attempts = config.getint('Boto', 'metadata_service_num_attempts', 1)
386 # The num_retries arg is actually the total number of attempts made,
387 # so the config options is named *_num_attempts to make this more
388 # clear to users.
389 metadata = get_instance_metadata(
390 timeout=timeout, num_retries=attempts,
391 data='meta-data/iam/security-credentials/')
392 if metadata:
393 # I'm assuming there's only one role on the instance profile.
394 security = list(metadata.values())[0]
395 self._access_key = security['AccessKeyId']
396 self._secret_key = self._convert_key_to_str(security['SecretAccessKey'])
397 self._security_token = security['Token']
398 expires_at = security['Expiration']
399 self._credential_expiry_time = datetime.strptime(
400 expires_at, "%Y-%m-%dT%H:%M:%SZ")
401 boto.log.debug("Retrieved credentials will expire in %s at: %s",
402 self._credential_expiry_time - datetime.now(), expires_at)
403
404 def _convert_key_to_str(self, key):
405 if isinstance(key, six.text_type):
406 # the secret key must be bytes and not unicode to work
407 # properly with hmac.new (see http://bugs.python.org/issue5285)
408 return str(key)
409 return key
410
411 def configure_headers(self):
412 header_info_map = self.HeaderInfoMap[self.name]
413 self.metadata_prefix = header_info_map[METADATA_PREFIX_KEY]
414 self.header_prefix = header_info_map[HEADER_PREFIX_KEY]
415 self.acl_header = header_info_map[ACL_HEADER_KEY]
416 self.auth_header = header_info_map[AUTH_HEADER_KEY]
417 self.copy_source_header = header_info_map[COPY_SOURCE_HEADER_KEY]
418 self.copy_source_version_id = header_info_map[
419 COPY_SOURCE_VERSION_ID_HEADER_KEY]
420 self.copy_source_range_header = header_info_map[
421 COPY_SOURCE_RANGE_HEADER_KEY]
422 self.date_header = header_info_map[DATE_HEADER_KEY]
423 self.delete_marker = header_info_map[DELETE_MARKER_HEADER_KEY]
424 self.metadata_directive_header = (
425 header_info_map[METADATA_DIRECTIVE_HEADER_KEY])
426 self.security_token_header = header_info_map[SECURITY_TOKEN_HEADER_KEY]
427 self.resumable_upload_header = (
428 header_info_map[RESUMABLE_UPLOAD_HEADER_KEY])
429 self.server_side_encryption_header = header_info_map[SERVER_SIDE_ENCRYPTION_KEY]
430 self.storage_class_header = header_info_map[STORAGE_CLASS_HEADER_KEY]
431 self.version_id = header_info_map[VERSION_ID_HEADER_KEY]
432 self.mfa_header = header_info_map[MFA_HEADER_KEY]
433 self.restore_header = header_info_map[RESTORE_HEADER_KEY]
434
435 def configure_errors(self):
436 error_map = self.ErrorMap[self.name]
437 self.storage_copy_error = error_map[STORAGE_COPY_ERROR]
438 self.storage_create_error = error_map[STORAGE_CREATE_ERROR]
439 self.storage_data_error = error_map[STORAGE_DATA_ERROR]
440 self.storage_permissions_error = error_map[STORAGE_PERMISSIONS_ERROR]
441 self.storage_response_error = error_map[STORAGE_RESPONSE_ERROR]
442
443 def get_provider_name(self):
444 return self.HostKeyMap[self.name]
445
446 def supports_chunked_transfer(self):
447 return self.ChunkedTransferSupport[self.name]
448
449
450 # Static utility method for getting default Provider.
451 def get_default():
452 return Provider('aws')