summaryrefslogtreecommitdiff
path: root/swiftclient
diff options
context:
space:
mode:
Diffstat (limited to 'swiftclient')
-rw-r--r--swiftclient/client.py101
-rw-r--r--swiftclient/exceptions.py12
-rw-r--r--swiftclient/service.py19
-rwxr-xr-xswiftclient/shell.py59
-rw-r--r--swiftclient/utils.py7
5 files changed, 167 insertions, 31 deletions
diff --git a/swiftclient/client.py b/swiftclient/client.py
index 449b6cd..5c63b60 100644
--- a/swiftclient/client.py
+++ b/swiftclient/client.py
@@ -45,6 +45,8 @@ AUTH_VERSIONS_V2 = ('2.0', '2', 2)
AUTH_VERSIONS_V3 = ('3.0', '3', 3)
USER_METADATA_TYPE = tuple('x-%s-meta-' % type_ for type_ in
('container', 'account', 'object'))
+URI_PATTERN_INFO = re.compile(r'/info')
+URI_PATTERN_VERSION = re.compile(r'\/v\d+\.?\d*(\/.*)?')
try:
from logging import NullHandler
@@ -60,7 +62,7 @@ except ImportError:
def createLock(self):
self.lock = None
-ksexceptions = ksclient_v2 = ksclient_v3 = None
+ksexceptions = ksclient_v2 = ksclient_v3 = ksa_v3 = None
try:
from keystoneclient import exceptions as ksexceptions
# prevent keystoneclient warning us that it has no log handlers
@@ -70,6 +72,9 @@ except ImportError:
pass
try:
from keystoneclient.v3 import client as ksclient_v3
+ from keystoneauth1.identity import v3 as ksa_v3
+ from keystoneauth1 import session as ksa_session
+ from keystoneauth1 import exceptions as ksauthexceptions
except ImportError:
pass
@@ -438,14 +443,16 @@ class HTTPConnection(object):
if timeout:
self.requests_args['timeout'] = timeout
- def __del__(self):
- """Cleanup resources other than memory"""
- if self.request_session:
- # The session we create must be closed to free up file descriptors
- try:
- self.request_session.close()
- finally:
- self.request_session = None
+ if not six.PY2:
+ def __del__(self):
+ """Cleanup resources other than memory"""
+ if self.request_session:
+ # The session we create must be closed to free up
+ # file descriptors
+ try:
+ self.request_session.close()
+ finally:
+ self.request_session = None
def _request(self, *arg, **kwarg):
"""Final wrapper before requests call, to be patched in tests"""
@@ -615,6 +622,51 @@ Auth versions 2.0 and 3 require python-keystoneclient, install it or use Auth
version 1.0 which requires ST_AUTH, ST_USER, and ST_KEY environment
variables to be set or overridden with -A, -U, or -K.''')
+ filter_kwargs = {}
+ service_type = os_options.get('service_type') or 'object-store'
+ endpoint_type = os_options.get('endpoint_type') or 'publicURL'
+ if os_options.get('region_name'):
+ filter_kwargs['attr'] = 'region'
+ filter_kwargs['filter_value'] = os_options['region_name']
+
+ if os_options.get('auth_type') and os_options['auth_type'] not in (
+ 'password', 'v2password', 'v3password',
+ 'v3applicationcredential'):
+ raise ClientException(
+ 'Swiftclient currently only supports v3applicationcredential '
+ 'for auth_type')
+ elif os_options.get('auth_type') == 'v3applicationcredential':
+ if ksa_v3 is None:
+ raise ClientException('Auth v3applicationcredential requires '
+ 'keystoneauth1 package; consider upgrading '
+ 'to python-keystoneclient>=2.0.0')
+
+ try:
+ auth = ksa_v3.ApplicationCredential(
+ auth_url=auth_url,
+ application_credential_secret=os_options.get(
+ 'application_credential_secret'),
+ application_credential_id=os_options.get(
+ 'application_credential_id'))
+ sess = ksa_session.Session(auth=auth)
+ token = sess.get_token()
+ except ksauthexceptions.Unauthorized:
+ msg = 'Unauthorized. Check application credential id and secret.'
+ raise ClientException(msg)
+ except ksauthexceptions.AuthorizationFailure as err:
+ raise ClientException('Authorization Failure. %s' % err)
+
+ try:
+ endpoint = sess.get_endpoint_data(service_type=service_type,
+ endpoint_type=endpoint_type,
+ **filter_kwargs)
+
+ return endpoint.catalog_url, token
+ except ksauthexceptions.EndpointNotFound:
+ raise ClientException(
+ 'Endpoint for %s not found - '
+ 'have you specified a region?' % service_type)
+
try:
_ksclient = ksclient.Client(
username=user,
@@ -642,13 +694,8 @@ variables to be set or overridden with -A, -U, or -K.''')
raise ClientException(msg)
except ksexceptions.AuthorizationFailure as err:
raise ClientException('Authorization Failure. %s' % err)
- service_type = os_options.get('service_type') or 'object-store'
- endpoint_type = os_options.get('endpoint_type') or 'publicURL'
+
try:
- filter_kwargs = {}
- if os_options.get('region_name'):
- filter_kwargs['attr'] = 'region'
- filter_kwargs['filter_value'] = os_options['region_name']
endpoint = _ksclient.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type,
@@ -717,9 +764,12 @@ def get_auth(auth_url, user, key, **kwargs):
if kwargs.get('tenant_name'):
os_options['tenant_name'] = kwargs['tenant_name']
- if not (os_options.get('tenant_name') or os_options.get('tenant_id') or
- os_options.get('project_name') or
- os_options.get('project_id')):
+ if os_options.get('auth_type') == 'v3applicationcredential':
+ pass
+ elif not (os_options.get('tenant_name') or
+ os_options.get('tenant_id') or
+ os_options.get('project_name') or
+ os_options.get('project_id')):
if auth_version in AUTH_VERSIONS_V2:
raise ClientException('No tenant specified')
raise ClientException('No project name or project id specified.')
@@ -1935,11 +1985,22 @@ class Connection(object):
response_dict=response_dict,
headers=headers)
- def get_capabilities(self, url=None):
+ def _map_url(self, url):
url = url or self.url
if not url:
url, _ = self.get_auth()
- parsed = urlparse(urljoin(url, '/info'))
+ scheme, netloc, path, params, query, fragment = urlparse(url)
+ if URI_PATTERN_VERSION.search(path):
+ path = URI_PATTERN_VERSION.sub('/info', path)
+ elif not URI_PATTERN_INFO.search(path):
+ if path.endswith('/'):
+ path += 'info'
+ else:
+ path += '/info'
+ return urlunparse((scheme, netloc, path, params, query, fragment))
+
+ def get_capabilities(self, url=None):
+ parsed = urlparse(self._map_url(url))
if not self.http_conn:
self.http_conn = self.http_connection(url)
return get_capabilities((parsed, self.http_conn[1]))
diff --git a/swiftclient/exceptions.py b/swiftclient/exceptions.py
index da70379..a9b993c 100644
--- a/swiftclient/exceptions.py
+++ b/swiftclient/exceptions.py
@@ -35,6 +35,13 @@ class ClientException(Exception):
self.http_response_content = http_response_content
self.http_response_headers = http_response_headers
+ self.transaction_id = None
+ if self.http_response_headers:
+ for header in ('X-Trans-Id', 'X-Openstack-Request-Id'):
+ if header in self.http_response_headers:
+ self.transaction_id = self.http_response_headers[header]
+ break
+
@classmethod
def from_response(cls, resp, msg=None, body=None):
msg = msg or '%s %s' % (resp.status_code, resp.reason)
@@ -78,4 +85,7 @@ class ClientException(Exception):
else:
b += ' [first 60 chars of response] %s' \
% self.http_response_content[:60]
- return b and '%s: %s' % (a, b) or a
+ c = ''
+ if self.transaction_id:
+ c = ' (txn: %s)' % self.transaction_id
+ return b and '%s: %s%s' % (a, b, c) or (a + c)
diff --git a/swiftclient/service.py b/swiftclient/service.py
index fb334fd..cd96a5b 100644
--- a/swiftclient/service.py
+++ b/swiftclient/service.py
@@ -110,6 +110,9 @@ def process_options(options):
else:
options['auth_version'] = '2.0'
+ if options.get('os_auth_type', None) == 'v3applicationcredential':
+ options['auth_version'] == '3'
+
# Use new-style args if old ones not present
if not options['auth'] and options['os_auth_url']:
options['auth'] = options['os_auth_url']
@@ -134,6 +137,11 @@ def process_options(options):
'auth_token': options['os_auth_token'],
'object_storage_url': options['os_storage_url'],
'region_name': options['os_region_name'],
+ 'auth_type': options['os_auth_type'],
+ 'application_credential_id':
+ options['os_application_credential_id'],
+ 'application_credential_secret':
+ options['os_application_credential_secret'],
}
@@ -162,6 +170,11 @@ def _build_default_global_options():
"os_project_domain_id": environ.get('OS_PROJECT_DOMAIN_ID'),
"os_auth_url": environ.get('OS_AUTH_URL'),
"os_auth_token": environ.get('OS_AUTH_TOKEN'),
+ "os_auth_type": environ.get('OS_AUTH_TYPE'),
+ "os_application_credential_id":
+ environ.get('OS_APPLICATION_CREDENTIAL_ID'),
+ "os_application_credential_secret":
+ environ.get('OS_APPLICATION_CREDENTIAL_SECRET'),
"os_storage_url": environ.get('OS_STORAGE_URL'),
"os_region_name": environ.get('OS_REGION_NAME'),
"os_service_type": environ.get('OS_SERVICE_TYPE'),
@@ -261,7 +274,7 @@ def get_conn(options):
return Connection(options['auth'],
options['user'],
options['key'],
- options['retries'],
+ retries=options['retries'],
auth_version=options['auth_version'],
os_options=options['os_options'],
snet=options['snet'],
@@ -270,7 +283,9 @@ def get_conn(options):
cert=options['os_cert'],
cert_key=options['os_key'],
ssl_compression=options['ssl_compression'],
- force_auth_retry=options['force_auth_retry'])
+ force_auth_retry=options['force_auth_retry'],
+ starting_backoff=options.get('starting_backoff', 1),
+ max_backoff=options.get('max_backoff', 64))
def mkdirs(path):
diff --git a/swiftclient/shell.py b/swiftclient/shell.py
index 1b34c08..dbcd437 100755
--- a/swiftclient/shell.py
+++ b/swiftclient/shell.py
@@ -1651,16 +1651,29 @@ def parse_args(parser, args, enforce_requires=True):
return options, args
if enforce_requires:
- if options['auth_version'] == '3':
+ if options['os_auth_type'] and options['os_auth_type'] not in (
+ 'password', 'v1password', 'v2password', 'v3password',
+ 'v3applicationcredential'):
+ exit('Only "v3applicationcredential" is supported for '
+ '--os-auth-type')
+ elif options['os_auth_type'] == 'v3applicationcredential':
+ if not (options['os_application_credential_id'] and
+ options['os_application_credential_secret']):
+ exit('Auth version 3 (application credential) requires '
+ 'OS_APPLICATION_CREDENTIAL_ID and '
+ 'OS_APPLICATION_CREDENTIAL_SECRET to be set or '
+ 'overridden with --os-application-credential-id and '
+ '--os-application-credential-secret respectively.')
+ elif options['auth_version'] == '3':
if not options['auth']:
- exit('Auth version 3 requires OS_AUTH_URL to be set or ' +
+ exit('Auth version 3 requires OS_AUTH_URL to be set or '
'overridden with --os-auth-url')
if not (options['user'] or options['os_user_id']):
- exit('Auth version 3 requires either OS_USERNAME or ' +
- 'OS_USER_ID to be set or overridden with ' +
+ exit('Auth version 3 requires either OS_USERNAME or '
+ 'OS_USER_ID to be set or overridden with '
'--os-username or --os-user-id respectively.')
if not options['key']:
- exit('Auth version 3 requires OS_PASSWORD to be set or ' +
+ exit('Auth version 3 requires OS_PASSWORD to be set or '
'overridden with --os-password')
elif not (options['auth'] and options['user'] and options['key']):
exit('''
@@ -1831,6 +1844,29 @@ def add_default_args(parser):
'env[OS_AUTH_URL].')
os_grp.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
+ os_grp.add_argument('--os-auth-type',
+ metavar='<auth-type>',
+ default=environ.get('OS_AUTH_TYPE'),
+ help='OpenStack auth type for v3. Defaults to '
+ 'env[OS_AUTH_TYPE].')
+ os_grp.add_argument('--os_auth_type',
+ help=argparse.SUPPRESS)
+ os_grp.add_argument('--os-application-credential-id',
+ metavar='<auth-application-credential-id>',
+ default=environ.get('OS_APPLICATION_CREDENTIAL_ID'),
+ help='OpenStack appplication credential id. '
+ 'Defaults to env[OS_APPLICATION_CREDENTIAL_ID].')
+ os_grp.add_argument('--os_application_credential_id',
+ help=argparse.SUPPRESS)
+ os_grp.add_argument('--os-application-credential-secret',
+ metavar='<auth-application-credential-secret>',
+ default=environ.get(
+ 'OS_APPLICATION_CREDENTIAL_SECRET'),
+ help='OpenStack appplication credential secret. '
+ 'Defaults to '
+ 'env[OS_APPLICATION_CREDENTIAL_SECRET].')
+ os_grp.add_argument('--os_application_credential_secret',
+ help=argparse.SUPPRESS)
os_grp.add_argument('--os-auth-token',
metavar='<auth-token>',
default=environ.get('OS_AUTH_TOKEN'),
@@ -1915,6 +1951,11 @@ def main(arguments=None):
[--os-project-domain-name <auth-project-domain-name>]
[--os-auth-url <auth-url>]
[--os-auth-token <auth-token>]
+ [--os-auth-type <os-auth-type>]
+ [--os-application-credential-id
+ <auth-application-credential-id>]
+ [--os-application-credential-secret
+ <auth-application-credential-secret>]
[--os-storage-url <storage-url>]
[--os-region-name <region-name>]
[--os-service-type <service-type>]
@@ -1967,6 +2008,11 @@ Examples:
--os-user-id abcdef0123456789abcdef0123456789 \\
--os-password password list
+ %(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
+ --os-application-credential-id d78683c92f0e4f9b9b02a2e208039412 \\
+ --os-application-credential-secret APPLICTION_CREDENTIAL_SECRET \\
+ --os-auth-type v3applicationcredential list
+
%(prog)s --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \\
--os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \\
list
@@ -2012,8 +2058,9 @@ Examples:
try:
globals()['st_%s' % args[0]](parser, argv[1:], output)
except ClientException as err:
+ trans_id = err.transaction_id
+ err.transaction_id = None # clear it so we aren't overly noisy
output.error(str(err))
- trans_id = (err.http_response_headers or {}).get('X-Trans-Id')
if trans_id:
output.error("Failed Transaction ID: %s",
parse_header_string(trans_id))
diff --git a/swiftclient/utils.py b/swiftclient/utils.py
index 9e43237..656acad 100644
--- a/swiftclient/utils.py
+++ b/swiftclient/utils.py
@@ -14,7 +14,10 @@
# limitations under the License.
"""Miscellaneous utility functions for use with Swift."""
from calendar import timegm
-import collections
+try:
+ from collections.abc import Mapping
+except ImportError:
+ from collections import Mapping
import gzip
import hashlib
import hmac
@@ -218,7 +221,7 @@ def parse_api_response(headers, body):
def split_request_headers(options, prefix=''):
headers = {}
- if isinstance(options, collections.Mapping):
+ if isinstance(options, Mapping):
options = options.items()
for item in options:
if isinstance(item, six.string_types):