summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--heatclient/common/http.py11
-rw-r--r--heatclient/openstack/common/__init__.py17
-rw-r--r--heatclient/openstack/common/_i18n.py40
-rw-r--r--heatclient/openstack/common/apiclient/auth.py4
-rw-r--r--heatclient/openstack/common/apiclient/base.py58
-rw-r--r--heatclient/openstack/common/apiclient/client.py48
-rw-r--r--heatclient/openstack/common/apiclient/exceptions.py89
-rw-r--r--heatclient/openstack/common/apiclient/fake_client.py8
-rw-r--r--heatclient/openstack/common/apiclient/utils.py87
-rw-r--r--heatclient/openstack/common/cliutils.py118
-rw-r--r--heatclient/openstack/common/strutils.py245
-rw-r--r--heatclient/shell.py5
-rw-r--r--heatclient/tests/test_shell.py14
-rw-r--r--heatclient/v1/events.py7
-rw-r--r--heatclient/v1/resource_types.py7
-rw-r--r--heatclient/v1/resources.py11
-rw-r--r--heatclient/v1/shell.py2
-rw-r--r--openstack-common.conf2
-rw-r--r--requirements.txt1
19 files changed, 331 insertions, 443 deletions
diff --git a/heatclient/common/http.py b/heatclient/common/http.py
index 1659f71..68245f3 100644
--- a/heatclient/common/http.py
+++ b/heatclient/common/http.py
@@ -24,10 +24,10 @@ import six
from six.moves.urllib import parse
from oslo.serialization import jsonutils
+from oslo.utils import encodeutils
from heatclient import exc
from heatclient.openstack.common import importutils
-from heatclient.openstack.common import strutils
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-heatclient'
@@ -84,15 +84,20 @@ class HTTPClient(object):
else:
self.verify_cert = kwargs.get('ca_file', get_system_ca_file())
+ # FIXME(shardy): We need this for compatibility with the oslo apiclient
+ # we should move to inheriting this class from the oslo HTTPClient
+ self.last_request_id = None
+
def safe_header(self, name, value):
if name in SENSITIVE_HEADERS:
# because in python3 byte string handling is ... ug
v = value.encode('utf-8')
h = hashlib.sha1(v)
d = h.hexdigest()
- return strutils.safe_decode(name), "{SHA1}%s" % d
+ return encodeutils.safe_decode(name), "{SHA1}%s" % d
else:
- return strutils.safe_decode(name), strutils.safe_decode(value)
+ return (encodeutils.safe_decode(name),
+ encodeutils.safe_decode(value))
def log_curl_request(self, method, url, kwargs):
curl = ['curl -i -X %s' % method]
diff --git a/heatclient/openstack/common/__init__.py b/heatclient/openstack/common/__init__.py
index d1223ea..e69de29 100644
--- a/heatclient/openstack/common/__init__.py
+++ b/heatclient/openstack/common/__init__.py
@@ -1,17 +0,0 @@
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import six
-
-
-six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox'))
diff --git a/heatclient/openstack/common/_i18n.py b/heatclient/openstack/common/_i18n.py
new file mode 100644
index 0000000..54206f4
--- /dev/null
+++ b/heatclient/openstack/common/_i18n.py
@@ -0,0 +1,40 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""oslo.i18n integration module.
+
+See http://docs.openstack.org/developer/oslo.i18n/usage.html
+
+"""
+
+import oslo.i18n
+
+
+# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
+# application name when this module is synced into the separate
+# repository. It is OK to have more than one translation function
+# using the same domain, since there will still only be one message
+# catalog.
+_translators = oslo.i18n.TranslatorFactory(domain='heatclient')
+
+# The primary translation function using the well-known name "_"
+_ = _translators.primary
+
+# Translators for log levels.
+#
+# The abbreviated names are meant to reflect the usual use of a short
+# name like '_'. The "L" is for "log" and the other letter comes from
+# the level.
+_LI = _translators.log_info
+_LW = _translators.log_warning
+_LE = _translators.log_error
+_LC = _translators.log_critical
diff --git a/heatclient/openstack/common/apiclient/auth.py b/heatclient/openstack/common/apiclient/auth.py
index 0535748..9c7f97e 100644
--- a/heatclient/openstack/common/apiclient/auth.py
+++ b/heatclient/openstack/common/apiclient/auth.py
@@ -213,8 +213,8 @@ class BaseAuthPlugin(object):
:type service_type: string
:param endpoint_type: Type of endpoint.
Possible values: public or publicURL,
- internal or internalURL,
- admin or adminURL
+ internal or internalURL,
+ admin or adminURL
:type endpoint_type: string
:returns: tuple of token and endpoint strings
:raises: EndpointException
diff --git a/heatclient/openstack/common/apiclient/base.py b/heatclient/openstack/common/apiclient/base.py
index 14b5766..c305861 100644
--- a/heatclient/openstack/common/apiclient/base.py
+++ b/heatclient/openstack/common/apiclient/base.py
@@ -26,11 +26,12 @@ Base utilities to build API operation managers and objects on top of.
import abc
import copy
+from oslo.utils import strutils
import six
from six.moves.urllib import parse
+from heatclient.openstack.common._i18n import _
from heatclient.openstack.common.apiclient import exceptions
-from heatclient.openstack.common import strutils
def getid(obj):
@@ -74,8 +75,8 @@ class HookableMixin(object):
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
- :param **args: args to be passed to every hook function
- :param **kwargs: kwargs to be passed to every hook function
+ :param args: args to be passed to every hook function
+ :param kwargs: kwargs to be passed to every hook function
"""
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
@@ -98,12 +99,13 @@ class BaseManager(HookableMixin):
super(BaseManager, self).__init__()
self.client = client
- def _list(self, url, response_key, obj_class=None, json=None):
+ def _list(self, url, response_key=None, obj_class=None, json=None):
"""List the collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
- e.g., 'servers'
+ e.g., 'servers'. If response_key is None - all response body
+ will be used.
:param obj_class: class for constructing the returned objects
(self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST
@@ -117,7 +119,7 @@ class BaseManager(HookableMixin):
if obj_class is None:
obj_class = self.resource_class
- data = body[response_key]
+ data = body[response_key] if response_key is not None else body
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
@@ -127,15 +129,17 @@ class BaseManager(HookableMixin):
return [obj_class(self, res, loaded=True) for res in data if res]
- def _get(self, url, response_key):
+ def _get(self, url, response_key=None):
"""Get an object from collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
- e.g., 'server'
+ e.g., 'server'. If response_key is None - all response body
+ will be used.
"""
body = self.client.get(url).json()
- return self.resource_class(self, body[response_key], loaded=True)
+ data = body[response_key] if response_key is not None else body
+ return self.resource_class(self, data, loaded=True)
def _head(self, url):
"""Retrieve request headers for an object.
@@ -145,21 +149,23 @@ class BaseManager(HookableMixin):
resp = self.client.head(url)
return resp.status_code == 204
- def _post(self, url, json, response_key, return_raw=False):
+ def _post(self, url, json, response_key=None, return_raw=False):
"""Create an object.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
- e.g., 'servers'
+ e.g., 'server'. If response_key is None - all response body
+ will be used.
:param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class
"""
body = self.client.post(url, json=json).json()
+ data = body[response_key] if response_key is not None else body
if return_raw:
- return body[response_key]
- return self.resource_class(self, body[response_key])
+ return data
+ return self.resource_class(self, data)
def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method.
@@ -168,7 +174,8 @@ class BaseManager(HookableMixin):
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
- e.g., 'servers'
+ e.g., 'servers'. If response_key is None - all response body
+ will be used.
"""
resp = self.client.put(url, json=json)
# PUT requests may not return a body
@@ -186,7 +193,8 @@ class BaseManager(HookableMixin):
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
- e.g., 'servers'
+ e.g., 'servers'. If response_key is None - all response body
+ will be used.
"""
body = self.client.patch(url, json=json).json()
if response_key is not None:
@@ -219,7 +227,10 @@ class ManagerWithFind(BaseManager):
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
- msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
+ msg = _("No %(name)s matching %(args)s.") % {
+ 'name': self.resource_class.__name__,
+ 'args': kwargs
+ }
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
@@ -373,7 +384,10 @@ class CrudManager(BaseManager):
num = len(rl)
if num == 0:
- msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
+ msg = _("No %(name)s matching %(args)s.") % {
+ 'name': self.resource_class.__name__,
+ 'args': kwargs
+ }
raise exceptions.NotFound(404, msg)
elif num > 1:
raise exceptions.NoUniqueMatch
@@ -441,8 +455,10 @@ class Resource(object):
def human_id(self):
"""Human-readable ID which can be used for bash completion.
"""
- if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
- return strutils.to_slug(getattr(self, self.NAME_ATTR))
+ if self.HUMAN_ID:
+ name = getattr(self, self.NAME_ATTR, None)
+ if name is not None:
+ return strutils.to_slug(name)
return None
def _add_details(self, info):
@@ -456,7 +472,7 @@ class Resource(object):
def __getattr__(self, k):
if k not in self.__dict__:
- #NOTE(bcwaldon): disallow lazy-loading if already loaded once
+ # NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
@@ -479,6 +495,8 @@ class Resource(object):
new = self.manager.get(self.id)
if new:
self._add_details(new._info)
+ self._add_details(
+ {'x_request_id': self.manager.client.last_request_id})
def __eq__(self, other):
if not isinstance(other, Resource):
diff --git a/heatclient/openstack/common/apiclient/client.py b/heatclient/openstack/common/apiclient/client.py
index 8f67185..af60f16 100644
--- a/heatclient/openstack/common/apiclient/client.py
+++ b/heatclient/openstack/common/apiclient/client.py
@@ -25,6 +25,7 @@ OpenStack Client interface. Handles the REST calls and responses.
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
+import hashlib
import logging
import time
@@ -33,19 +34,22 @@ try:
except ImportError:
import json
+from oslo.utils import encodeutils
+from oslo.utils import importutils
import requests
+from heatclient.openstack.common._i18n import _
from heatclient.openstack.common.apiclient import exceptions
-from heatclient.openstack.common import importutils
-
_logger = logging.getLogger(__name__)
+SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
class HTTPClient(object):
"""This client handles sending HTTP requests to OpenStack servers.
Features:
+
- share authentication information between several clients to different
services (e.g., for compute and image clients);
- reissue authentication request for expired tokens;
@@ -96,6 +100,18 @@ class HTTPClient(object):
self.http = http or requests.Session()
self.cached_token = None
+ self.last_request_id = None
+
+ def _safe_header(self, name, value):
+ if name in SENSITIVE_HEADERS:
+ # because in python3 byte string handling is ... ug
+ v = value.encode('utf-8')
+ h = hashlib.sha1(v)
+ d = h.hexdigest()
+ return encodeutils.safe_decode(name), "{SHA1}%s" % d
+ else:
+ return (encodeutils.safe_decode(name),
+ encodeutils.safe_decode(value))
def _http_log_req(self, method, url, kwargs):
if not self.debug:
@@ -108,7 +124,8 @@ class HTTPClient(object):
]
for element in kwargs['headers']:
- header = "-H '%s: %s'" % (element, kwargs['headers'][element])
+ header = ("-H '%s: %s'" %
+ self._safe_header(element, kwargs['headers'][element]))
string_parts.append(header)
_logger.debug("REQ: %s" % " ".join(string_parts))
@@ -151,10 +168,10 @@ class HTTPClient(object):
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
-' requests.Session.request (such as `headers`) or `json`
+ requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
- kwargs.setdefault("headers", kwargs.get("headers", {}))
+ kwargs.setdefault("headers", {})
kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
@@ -175,6 +192,8 @@ class HTTPClient(object):
start_time, time.time()))
self._http_log_resp(resp)
+ self.last_request_id = resp.headers.get('x-openstack-request-id')
+
if resp.status_code >= 400:
_logger.debug(
"Request returned failure status: %s",
@@ -206,7 +225,7 @@ class HTTPClient(object):
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
-' `HTTPClient.request`
+ `HTTPClient.request`
"""
filter_args = {
@@ -228,7 +247,7 @@ class HTTPClient(object):
**filter_args)
if not (token and endpoint):
raise exceptions.AuthorizationFailure(
- "Cannot find endpoint or token for request")
+ _("Cannot find endpoint or token for request"))
old_token_endpoint = (token, endpoint)
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
@@ -245,6 +264,10 @@ class HTTPClient(object):
raise
self.cached_token = None
client.cached_endpoint = None
+ if self.auth_plugin.opts.get('token'):
+ self.auth_plugin.opts['token'] = None
+ if self.auth_plugin.opts.get('endpoint'):
+ self.auth_plugin.opts['endpoint'] = None
self.authenticate()
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
@@ -321,6 +344,10 @@ class BaseClient(object):
return self.http_client.client_request(
self, method, url, **kwargs)
+ @property
+ def last_request_id(self):
+ return self.http_client.last_request_id
+
def head(self, url, **kwargs):
return self.client_request("HEAD", url, **kwargs)
@@ -351,8 +378,11 @@ class BaseClient(object):
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
- msg = "Invalid %s client version '%s'. must be one of: %s" % (
- (api_name, version, ', '.join(version_map.keys())))
+ msg = _("Invalid %(api_name)s client version '%(version)s'. "
+ "Must be one of: %(version_map)s") % {
+ 'api_name': api_name,
+ 'version': version,
+ 'version_map': ', '.join(version_map.keys())}
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)
diff --git a/heatclient/openstack/common/apiclient/exceptions.py b/heatclient/openstack/common/apiclient/exceptions.py
index ada1344..745ad06 100644
--- a/heatclient/openstack/common/apiclient/exceptions.py
+++ b/heatclient/openstack/common/apiclient/exceptions.py
@@ -25,6 +25,8 @@ import sys
import six
+from heatclient.openstack.common._i18n import _
+
class ClientException(Exception):
"""The base exception class for all exceptions this library raises.
@@ -32,14 +34,6 @@ class ClientException(Exception):
pass
-class MissingArgs(ClientException):
- """Supplied arguments are not sufficient for calling a function."""
- def __init__(self, missing):
- self.missing = missing
- msg = "Missing argument(s): %s" % ", ".join(missing)
- super(MissingArgs, self).__init__(msg)
-
-
class ValidationError(ClientException):
"""Error in validation on API client side."""
pass
@@ -69,16 +63,16 @@ class AuthPluginOptionsMissing(AuthorizationFailure):
"""Auth plugin misses some options."""
def __init__(self, opt_names):
super(AuthPluginOptionsMissing, self).__init__(
- "Authentication failed. Missing options: %s" %
+ _("Authentication failed. Missing options: %s") %
", ".join(opt_names))
self.opt_names = opt_names
class AuthSystemNotFound(AuthorizationFailure):
- """User has specified a AuthSystem that is not installed."""
+ """User has specified an AuthSystem that is not installed."""
def __init__(self, auth_system):
super(AuthSystemNotFound, self).__init__(
- "AuthSystemNotFound: %s" % repr(auth_system))
+ _("AuthSystemNotFound: %s") % repr(auth_system))
self.auth_system = auth_system
@@ -101,7 +95,7 @@ class AmbiguousEndpoints(EndpointException):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
super(AmbiguousEndpoints, self).__init__(
- "AmbiguousEndpoints: %s" % repr(endpoints))
+ _("AmbiguousEndpoints: %s") % repr(endpoints))
self.endpoints = endpoints
@@ -109,7 +103,7 @@ class HttpError(ClientException):
"""The base exception class for all HTTP exceptions.
"""
http_status = 0
- message = "HTTP Error"
+ message = _("HTTP Error")
def __init__(self, message=None, details=None,
response=None, request_id=None,
@@ -129,7 +123,7 @@ class HttpError(ClientException):
class HTTPRedirection(HttpError):
"""HTTP Redirection."""
- message = "HTTP Redirection"
+ message = _("HTTP Redirection")
class HTTPClientError(HttpError):
@@ -137,7 +131,7 @@ class HTTPClientError(HttpError):
Exception for cases in which the client seems to have erred.
"""
- message = "HTTP Client Error"
+ message = _("HTTP Client Error")
class HttpServerError(HttpError):
@@ -146,7 +140,7 @@ class HttpServerError(HttpError):
Exception for cases in which the server is aware that it has
erred or is incapable of performing the request.
"""
- message = "HTTP Server Error"
+ message = _("HTTP Server Error")
class MultipleChoices(HTTPRedirection):
@@ -156,7 +150,7 @@ class MultipleChoices(HTTPRedirection):
"""
http_status = 300
- message = "Multiple Choices"
+ message = _("Multiple Choices")
class BadRequest(HTTPClientError):
@@ -165,7 +159,7 @@ class BadRequest(HTTPClientError):
The request cannot be fulfilled due to bad syntax.
"""
http_status = 400
- message = "Bad Request"
+ message = _("Bad Request")
class Unauthorized(HTTPClientError):
@@ -175,7 +169,7 @@ class Unauthorized(HTTPClientError):
is required and has failed or has not yet been provided.
"""
http_status = 401
- message = "Unauthorized"
+ message = _("Unauthorized")
class PaymentRequired(HTTPClientError):
@@ -184,7 +178,7 @@ class PaymentRequired(HTTPClientError):
Reserved for future use.
"""
http_status = 402
- message = "Payment Required"
+ message = _("Payment Required")
class Forbidden(HTTPClientError):
@@ -194,7 +188,7 @@ class Forbidden(HTTPClientError):
to it.
"""
http_status = 403
- message = "Forbidden"
+ message = _("Forbidden")
class NotFound(HTTPClientError):
@@ -204,7 +198,7 @@ class NotFound(HTTPClientError):
in the future.
"""
http_status = 404
- message = "Not Found"
+ message = _("Not Found")
class MethodNotAllowed(HTTPClientError):
@@ -214,7 +208,7 @@ class MethodNotAllowed(HTTPClientError):
by that resource.
"""
http_status = 405
- message = "Method Not Allowed"
+ message = _("Method Not Allowed")
class NotAcceptable(HTTPClientError):
@@ -224,7 +218,7 @@ class NotAcceptable(HTTPClientError):
acceptable according to the Accept headers sent in the request.
"""
http_status = 406
- message = "Not Acceptable"
+ message = _("Not Acceptable")
class ProxyAuthenticationRequired(HTTPClientError):
@@ -233,7 +227,7 @@ class ProxyAuthenticationRequired(HTTPClientError):
The client must first authenticate itself with the proxy.
"""
http_status = 407
- message = "Proxy Authentication Required"
+ message = _("Proxy Authentication Required")
class RequestTimeout(HTTPClientError):
@@ -242,7 +236,7 @@ class RequestTimeout(HTTPClientError):
The server timed out waiting for the request.
"""
http_status = 408
- message = "Request Timeout"
+ message = _("Request Timeout")
class Conflict(HTTPClientError):
@@ -252,7 +246,7 @@ class Conflict(HTTPClientError):
in the request, such as an edit conflict.
"""
http_status = 409
- message = "Conflict"
+ message = _("Conflict")
class Gone(HTTPClientError):
@@ -262,7 +256,7 @@ class Gone(HTTPClientError):
not be available again.
"""
http_status = 410
- message = "Gone"
+ message = _("Gone")
class LengthRequired(HTTPClientError):
@@ -272,7 +266,7 @@ class LengthRequired(HTTPClientError):
required by the requested resource.
"""
http_status = 411
- message = "Length Required"
+ message = _("Length Required")
class PreconditionFailed(HTTPClientError):
@@ -282,7 +276,7 @@ class PreconditionFailed(HTTPClientError):
put on the request.
"""
http_status = 412
- message = "Precondition Failed"
+ message = _("Precondition Failed")
class RequestEntityTooLarge(HTTPClientError):
@@ -291,7 +285,7 @@ class RequestEntityTooLarge(HTTPClientError):
The request is larger than the server is willing or able to process.
"""
http_status = 413
- message = "Request Entity Too Large"
+ message = _("Request Entity Too Large")
def __init__(self, *args, **kwargs):
try:
@@ -308,7 +302,7 @@ class RequestUriTooLong(HTTPClientError):
The URI provided was too long for the server to process.
"""
http_status = 414
- message = "Request-URI Too Long"
+ message = _("Request-URI Too Long")
class UnsupportedMediaType(HTTPClientError):
@@ -318,7 +312,7 @@ class UnsupportedMediaType(HTTPClientError):
not support.
"""
http_status = 415
- message = "Unsupported Media Type"
+ message = _("Unsupported Media Type")
class RequestedRangeNotSatisfiable(HTTPClientError):
@@ -328,7 +322,7 @@ class RequestedRangeNotSatisfiable(HTTPClientError):
supply that portion.
"""
http_status = 416
- message = "Requested Range Not Satisfiable"
+ message = _("Requested Range Not Satisfiable")
class ExpectationFailed(HTTPClientError):
@@ -337,7 +331,7 @@ class ExpectationFailed(HTTPClientError):
The server cannot meet the requirements of the Expect request-header field.
"""
http_status = 417
- message = "Expectation Failed"
+ message = _("Expectation Failed")
class UnprocessableEntity(HTTPClientError):
@@ -347,7 +341,7 @@ class UnprocessableEntity(HTTPClientError):
errors.
"""
http_status = 422
- message = "Unprocessable Entity"
+ message = _("Unprocessable Entity")
class InternalServerError(HttpServerError):
@@ -356,7 +350,7 @@ class InternalServerError(HttpServerError):
A generic error message, given when no more specific message is suitable.
"""
http_status = 500
- message = "Internal Server Error"
+ message = _("Internal Server Error")
# NotImplemented is a python keyword.
@@ -367,7 +361,7 @@ class HttpNotImplemented(HttpServerError):
the ability to fulfill the request.
"""
http_status = 501
- message = "Not Implemented"
+ message = _("Not Implemented")
class BadGateway(HttpServerError):
@@ -377,7 +371,7 @@ class BadGateway(HttpServerError):
response from the upstream server.
"""
http_status = 502
- message = "Bad Gateway"
+ message = _("Bad Gateway")
class ServiceUnavailable(HttpServerError):
@@ -386,7 +380,7 @@ class ServiceUnavailable(HttpServerError):
The server is currently unavailable.
"""
http_status = 503
- message = "Service Unavailable"
+ message = _("Service Unavailable")
class GatewayTimeout(HttpServerError):
@@ -396,7 +390,7 @@ class GatewayTimeout(HttpServerError):
response from the upstream server.
"""
http_status = 504
- message = "Gateway Timeout"
+ message = _("Gateway Timeout")
class HttpVersionNotSupported(HttpServerError):
@@ -405,7 +399,7 @@ class HttpVersionNotSupported(HttpServerError):
The server does not support the HTTP protocol version used in the request.
"""
http_status = 505
- message = "HTTP Version Not Supported"
+ message = _("HTTP Version Not Supported")
# _code_map contains all the classes that have http_status attribute.
@@ -423,12 +417,17 @@ def from_response(response, method, url):
:param method: HTTP method used for request
:param url: URL used for request
"""
+
+ req_id = response.headers.get("x-openstack-request-id")
+ # NOTE(hdd) true for older versions of nova and cinder
+ if not req_id:
+ req_id = response.headers.get("x-compute-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
- "request_id": response.headers.get("x-compute-request-id"),
+ "request_id": req_id,
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]
@@ -440,8 +439,8 @@ def from_response(response, method, url):
except ValueError:
pass
else:
- if isinstance(body, dict):
- error = list(body.values())[0]
+ if isinstance(body, dict) and isinstance(body.get("error"), dict):
+ error = body["error"]
kwargs["message"] = error.get("message")
kwargs["details"] = error.get("details")
elif content_type.startswith("text/"):
diff --git a/heatclient/openstack/common/apiclient/fake_client.py b/heatclient/openstack/common/apiclient/fake_client.py
index eb10e0f..44ae68c 100644
--- a/heatclient/openstack/common/apiclient/fake_client.py
+++ b/heatclient/openstack/common/apiclient/fake_client.py
@@ -33,7 +33,9 @@ from six.moves.urllib import parse
from heatclient.openstack.common.apiclient import client
-def assert_has_keys(dct, required=[], optional=[]):
+def assert_has_keys(dct, required=None, optional=None):
+ required = required or []
+ optional = optional or []
for k in required:
try:
assert k in dct
@@ -79,7 +81,7 @@ class FakeHTTPClient(client.HTTPClient):
def __init__(self, *args, **kwargs):
self.callstack = []
self.fixtures = kwargs.pop("fixtures", None) or {}
- if not args and not "auth_plugin" in kwargs:
+ if not args and "auth_plugin" not in kwargs:
args = (None, )
super(FakeHTTPClient, self).__init__(*args, **kwargs)
@@ -166,6 +168,8 @@ class FakeHTTPClient(client.HTTPClient):
else:
status, body = resp
headers = {}
+ self.last_request_id = headers.get('x-openstack-request-id',
+ 'req-test')
return TestResponse({
"status_code": status,
"text": body,
diff --git a/heatclient/openstack/common/apiclient/utils.py b/heatclient/openstack/common/apiclient/utils.py
new file mode 100644
index 0000000..63004bc
--- /dev/null
+++ b/heatclient/openstack/common/apiclient/utils.py
@@ -0,0 +1,87 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo.utils import encodeutils
+import six
+
+from heatclient.openstack.common._i18n import _
+from heatclient.openstack.common.apiclient import exceptions
+from heatclient.openstack.common import uuidutils
+
+
+def find_resource(manager, name_or_id, **find_args):
+ """Look for resource in a given manager.
+
+ Used as a helper for the _find_* methods.
+ Example:
+
+ .. code-block:: python
+
+ def _find_hypervisor(cs, hypervisor):
+ #Get a hypervisor by name or ID.
+ return cliutils.find_resource(cs.hypervisors, hypervisor)
+ """
+ # first try to get entity as integer id
+ try:
+ return manager.get(int(name_or_id))
+ except (TypeError, ValueError, exceptions.NotFound):
+ pass
+
+ # now try to get entity as uuid
+ try:
+ if six.PY2:
+ tmp_id = encodeutils.safe_encode(name_or_id)
+ else:
+ tmp_id = encodeutils.safe_decode(name_or_id)
+
+ if uuidutils.is_uuid_like(tmp_id):
+ return manager.get(tmp_id)
+ except (TypeError, ValueError, exceptions.NotFound):
+ pass
+
+ # for str id which is not uuid
+ if getattr(manager, 'is_alphanum_id_allowed', False):
+ try:
+ return manager.get(name_or_id)
+ except exceptions.NotFound:
+ pass
+
+ try:
+ try:
+ return manager.find(human_id=name_or_id, **find_args)
+ except exceptions.NotFound:
+ pass
+
+ # finally try to find entity by name
+ try:
+ resource = getattr(manager, 'resource_class', None)
+ name_attr = resource.NAME_ATTR if resource else 'name'
+ kwargs = {name_attr: name_or_id}
+ kwargs.update(find_args)
+ return manager.find(**kwargs)
+ except exceptions.NotFound:
+ msg = _("No %(name)s with a name or "
+ "ID of '%(name_or_id)s' exists.") % \
+ {
+ "name": manager.resource_class.__name__.lower(),
+ "name_or_id": name_or_id
+ }
+ raise exceptions.CommandError(msg)
+ except exceptions.NoUniqueMatch:
+ msg = _("Multiple %(name)s matches found for "
+ "'%(name_or_id)s', use an ID to be more specific.") % \
+ {
+ "name": manager.resource_class.__name__.lower(),
+ "name_or_id": name_or_id
+ }
+ raise exceptions.CommandError(msg)
diff --git a/heatclient/openstack/common/cliutils.py b/heatclient/openstack/common/cliutils.py
index e493fd8..47e97f8 100644
--- a/heatclient/openstack/common/cliutils.py
+++ b/heatclient/openstack/common/cliutils.py
@@ -24,14 +24,21 @@ import os
import sys
import textwrap
+from oslo.utils import encodeutils
+from oslo.utils import strutils
import prettytable
import six
from six import moves
-from heatclient.openstack.common.apiclient import exceptions
-from heatclient.openstack.common.gettextutils import _
-from heatclient.openstack.common import strutils
-from heatclient.openstack.common import uuidutils
+from heatclient.openstack.common._i18n import _
+
+
+class MissingArgs(Exception):
+ """Supplied arguments are not sufficient for calling a function."""
+ def __init__(self, missing):
+ self.missing = missing
+ msg = _("Missing arguments: %s") % ", ".join(missing)
+ super(MissingArgs, self).__init__(msg)
def validate_args(fn, *args, **kwargs):
@@ -56,7 +63,7 @@ def validate_args(fn, *args, **kwargs):
required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method):
- return getattr(method, 'im_self', None) is not None
+ return getattr(method, '__self__', None) is not None
if isbound(fn):
required_args.pop(0)
@@ -64,7 +71,7 @@ def validate_args(fn, *args, **kwargs):
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
- raise exceptions.MissingArgs(missing)
+ raise MissingArgs(missing)
def arg(*args, **kwargs):
@@ -132,7 +139,7 @@ def isunauthenticated(func):
def print_list(objs, fields, formatters=None, sortby_index=0,
- mixed_case_fields=None):
+ mixed_case_fields=None, field_labels=None):
"""Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
@@ -141,14 +148,22 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
+ :param field_labels: Labels to use in the heading of the table, default to
+ fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
+ field_labels = field_labels or fields
+ if len(field_labels) != len(fields):
+ raise ValueError(_("Field labels list %(labels)s has different number "
+ "of elements than fields list %(fields)s"),
+ {'labels': field_labels, 'fields': fields})
+
if sortby_index is None:
kwargs = {}
else:
- kwargs = {'sortby': fields[sortby_index]}
- pt = prettytable.PrettyTable(fields, caching=False)
+ kwargs = {'sortby': field_labels[sortby_index]}
+ pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
@@ -165,7 +180,10 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
row.append(data)
pt.add_row(row)
- print(strutils.safe_encode(pt.get_string(**kwargs)))
+ if six.PY3:
+ print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
+ else:
+ print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
@@ -175,7 +193,7 @@ def print_dict(dct, dict_property="Property", wrap=0):
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
- pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False)
+ pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l'
for k, v in six.iteritems(dct):
# convert dict to str to check length
@@ -193,7 +211,11 @@ def print_dict(dct, dict_property="Property", wrap=0):
col1 = ''
else:
pt.add_row([k, v])
- print(strutils.safe_encode(pt.get_string()))
+
+ if six.PY3:
+ print(encodeutils.safe_encode(pt.get_string()).decode())
+ else:
+ print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
@@ -217,76 +239,16 @@ def get_password(max_password_prompts=3):
return pw
-def find_resource(manager, name_or_id, **find_args):
- """Look for resource in a given manager.
-
- Used as a helper for the _find_* methods.
- Example:
-
- def _find_hypervisor(cs, hypervisor):
- #Get a hypervisor by name or ID.
- return cliutils.find_resource(cs.hypervisors, hypervisor)
- """
- # first try to get entity as integer id
- try:
- return manager.get(int(name_or_id))
- except (TypeError, ValueError, exceptions.NotFound):
- pass
-
- # now try to get entity as uuid
- try:
- tmp_id = strutils.safe_encode(name_or_id)
-
- if uuidutils.is_uuid_like(tmp_id):
- return manager.get(tmp_id)
- except (TypeError, ValueError, exceptions.NotFound):
- pass
-
- # for str id which is not uuid
- if getattr(manager, 'is_alphanum_id_allowed', False):
- try:
- return manager.get(name_or_id)
- except exceptions.NotFound:
- pass
-
- try:
- try:
- return manager.find(human_id=name_or_id, **find_args)
- except exceptions.NotFound:
- pass
-
- # finally try to find entity by name
- try:
- resource = getattr(manager, 'resource_class', None)
- name_attr = resource.NAME_ATTR if resource else 'name'
- kwargs = {name_attr: name_or_id}
- kwargs.update(find_args)
- return manager.find(**kwargs)
- except exceptions.NotFound:
- msg = _("No %(name)s with a name or "
- "ID of '%(name_or_id)s' exists.") % \
- {
- "name": manager.resource_class.__name__.lower(),
- "name_or_id": name_or_id
- }
- raise exceptions.CommandError(msg)
- except exceptions.NoUniqueMatch:
- msg = _("Multiple %(name)s matches found for "
- "'%(name_or_id)s', use an ID to be more specific.") % \
- {
- "name": manager.resource_class.__name__.lower(),
- "name_or_id": name_or_id
- }
- raise exceptions.CommandError(msg)
-
-
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
Usage:
- @service_type('volume')
- def mymethod(f):
- ...
+
+ .. code-block:: python
+
+ @service_type('volume')
+ def mymethod(f):
+ ...
"""
def inner(f):
f.service_type = stype
diff --git a/heatclient/openstack/common/strutils.py b/heatclient/openstack/common/strutils.py
deleted file mode 100644
index d6aef26..0000000
--- a/heatclient/openstack/common/strutils.py
+++ /dev/null
@@ -1,245 +0,0 @@
-# Copyright 2011 OpenStack Foundation.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""
-System-level utilities and helper functions.
-"""
-
-import math
-import re
-import sys
-import unicodedata
-
-import six
-
-from heatclient.openstack.common.gettextutils import _
-
-
-UNIT_PREFIX_EXPONENT = {
- 'k': 1,
- 'K': 1,
- 'Ki': 1,
- 'M': 2,
- 'Mi': 2,
- 'G': 3,
- 'Gi': 3,
- 'T': 4,
- 'Ti': 4,
-}
-UNIT_SYSTEM_INFO = {
- 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
- 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
-}
-
-TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
-FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
-
-SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
-SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
-
-
-def int_from_bool_as_string(subject):
- """Interpret a string as a boolean and return either 1 or 0.
-
- Any string value in:
-
- ('True', 'true', 'On', 'on', '1')
-
- is interpreted as a boolean True.
-
- Useful for JSON-decoded stuff and config file parsing
- """
- return bool_from_string(subject) and 1 or 0
-
-
-def bool_from_string(subject, strict=False, default=False):
- """Interpret a string as a boolean.
-
- A case-insensitive match is performed such that strings matching 't',
- 'true', 'on', 'y', 'yes', or '1' are considered True and, when
- `strict=False`, anything else returns the value specified by 'default'.
-
- Useful for JSON-decoded stuff and config file parsing.
-
- If `strict=True`, unrecognized values, including None, will raise a
- ValueError which is useful when parsing values passed in from an API call.
- Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
- """
- if not isinstance(subject, six.string_types):
- subject = str(subject)
-
- lowered = subject.strip().lower()
-
- if lowered in TRUE_STRINGS:
- return True
- elif lowered in FALSE_STRINGS:
- return False
- elif strict:
- acceptable = ', '.join(
- "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
- msg = _("Unrecognized value '%(val)s', acceptable values are:"
- " %(acceptable)s") % {'val': subject,
- 'acceptable': acceptable}
- raise ValueError(msg)
- else:
- return default
-
-
-def safe_decode(text, incoming=None, errors='strict'):
- """Decodes incoming text/bytes string using `incoming` if they're not
- already unicode.
-
- :param incoming: Text's current encoding
- :param errors: Errors handling policy. See here for valid
- values http://docs.python.org/2/library/codecs.html
- :returns: text or a unicode `incoming` encoded
- representation of it.
- :raises TypeError: If text is not an instance of str
- """
- if not isinstance(text, (six.string_types, six.binary_type)):
- raise TypeError("%s can't be decoded" % type(text))
-
- if isinstance(text, six.text_type):
- return text
-
- if not incoming:
- incoming = (sys.stdin.encoding or
- sys.getdefaultencoding())
-
- try:
- return text.decode(incoming, errors)
- except UnicodeDecodeError:
- # Note(flaper87) If we get here, it means that
- # sys.stdin.encoding / sys.getdefaultencoding
- # didn't return a suitable encoding to decode
- # text. This happens mostly when global LANG
- # var is not set correctly and there's no
- # default encoding. In this case, most likely
- # python will use ASCII or ANSI encoders as
- # default encodings but they won't be capable
- # of decoding non-ASCII characters.
- #
- # Also, UTF-8 is being used since it's an ASCII
- # extension.
- return text.decode('utf-8', errors)
-
-
-def safe_encode(text, incoming=None,
- encoding='utf-8', errors='strict'):
- """Encodes incoming text/bytes string using `encoding`.
-
- If incoming is not specified, text is expected to be encoded with
- current python's default encoding. (`sys.getdefaultencoding`)
-
- :param incoming: Text's current encoding
- :param encoding: Expected encoding for text (Default UTF-8)
- :param errors: Errors handling policy. See here for valid
- values http://docs.python.org/2/library/codecs.html
- :returns: text or a bytestring `encoding` encoded
- representation of it.
- :raises TypeError: If text is not an instance of str
- """
- if not isinstance(text, (six.string_types, six.binary_type)):
- raise TypeError("%s can't be encoded" % type(text))
-
- if not incoming:
- incoming = (sys.stdin.encoding or
- sys.getdefaultencoding())
-
- if isinstance(text, six.text_type):
- if six.PY3:
- return text.encode(encoding, errors).decode(incoming)
- else:
- return text.encode(encoding, errors)
- elif text and encoding != incoming:
- # Decode text before encoding it with `encoding`
- text = safe_decode(text, incoming, errors)
- if six.PY3:
- return text.encode(encoding, errors).decode(incoming)
- else:
- return text.encode(encoding, errors)
-
- return text
-
-
-def string_to_bytes(text, unit_system='IEC', return_int=False):
- """Converts a string into an float representation of bytes.
-
- The units supported for IEC ::
-
- Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
- KB, KiB, MB, MiB, GB, GiB, TB, TiB
-
- The units supported for SI ::
-
- kb(it), Mb(it), Gb(it), Tb(it)
- kB, MB, GB, TB
-
- Note that the SI unit system does not support capital letter 'K'
-
- :param text: String input for bytes size conversion.
- :param unit_system: Unit system for byte size conversion.
- :param return_int: If True, returns integer representation of text
- in bytes. (default: decimal)
- :returns: Numerical representation of text in bytes.
- :raises ValueError: If text has an invalid value.
-
- """
- try:
- base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
- except KeyError:
- msg = _('Invalid unit system: "%s"') % unit_system
- raise ValueError(msg)
- match = reg_ex.match(text)
- if match:
- magnitude = float(match.group(1))
- unit_prefix = match.group(2)
- if match.group(3) in ['b', 'bit']:
- magnitude /= 8
- else:
- msg = _('Invalid string format: %s') % text
- raise ValueError(msg)
- if not unit_prefix:
- res = magnitude
- else:
- res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
- if return_int:
- return int(math.ceil(res))
- return res
-
-
-def to_slug(value, incoming=None, errors="strict"):
- """Normalize string.
-
- Convert to lowercase, remove non-word characters, and convert spaces
- to hyphens.
-
- Inspired by Django's `slugify` filter.
-
- :param value: Text to slugify
- :param incoming: Text's current encoding
- :param errors: Errors handling policy. See here for valid
- values http://docs.python.org/2/library/codecs.html
- :returns: slugified unicode representation of `value`
- :raises TypeError: If text is not an instance of str
- """
- value = safe_decode(value, incoming, errors)
- # NOTE(aababilov): no need to use safe_(encode|decode) here:
- # encodings are always "ascii", error handling is always "ignore"
- # and types are always known (first: unicode; second: str)
- value = unicodedata.normalize("NFKD", value).encode(
- "ascii", "ignore").decode("ascii")
- value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
- return SLUGIFY_HYPHENATE_RE.sub("-", value)
diff --git a/heatclient/shell.py b/heatclient/shell.py
index ed1d827..97f78d9 100644
--- a/heatclient/shell.py
+++ b/heatclient/shell.py
@@ -23,6 +23,8 @@ import sys
import six
import six.moves.urllib.parse as urlparse
+from oslo.utils import encodeutils
+
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover
@@ -35,7 +37,6 @@ from heatclient.common import utils
from heatclient import exc
from heatclient.openstack.common.gettextutils import _ # noqa
from heatclient.openstack.common import importutils
-from heatclient.openstack.common import strutils
logger = logging.getLogger(__name__)
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
@@ -662,7 +663,7 @@ def main(args=None):
if '--debug' in args or '-d' in args:
raise
else:
- print(strutils.safe_encode(six.text_type(e)), file=sys.stderr)
+ print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
diff --git a/heatclient/tests/test_shell.py b/heatclient/tests/test_shell.py
index 8ddc6c8..2e77f4a 100644
--- a/heatclient/tests/test_shell.py
+++ b/heatclient/tests/test_shell.py
@@ -27,11 +27,11 @@ import testtools
import uuid
from oslo.serialization import jsonutils
+from oslo.utils import encodeutils
from keystoneclient.fixture import v2 as ks_v2_fixture
from keystoneclient.fixture import v3 as ks_v3_fixture
-from heatclient.openstack.common import strutils
from mox3 import mox
from heatclient.common import http
@@ -1972,7 +1972,7 @@ class ShellTestEvents(ShellBase):
http.HTTPClient.json_request(
'GET', '/stacks/%s/resources/%s/events' % (
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(
+ parse.quote(encodeutils.safe_encode(
resource_name), ''))).AndReturn((resp, resp_dict))
self.m.ReplayAll()
@@ -2030,7 +2030,7 @@ class ShellTestEvents(ShellBase):
'GET', '/stacks/%s/resources/%s/events/%s' %
(
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(
+ parse.quote(encodeutils.safe_encode(
resource_name), ''),
parse.quote(self.event_id_one, '')
)).AndReturn((resp, resp_dict))
@@ -2219,7 +2219,7 @@ class ShellTestResources(ShellBase):
'GET', '/stacks/%s/resources/%s' %
(
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(
+ parse.quote(encodeutils.safe_encode(
resource_name), '')
)).AndReturn((resp, resp_dict))
@@ -2265,7 +2265,7 @@ class ShellTestResources(ShellBase):
'POST', '/stacks/%s/resources/%s/signal' %
(
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(
+ parse.quote(encodeutils.safe_encode(
resource_name), '')
),
data={'message': 'Content'}).AndReturn((resp, ''))
@@ -2291,7 +2291,7 @@ class ShellTestResources(ShellBase):
'POST', '/stacks/%s/resources/%s/signal' %
(
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(
+ parse.quote(encodeutils.safe_encode(
resource_name), '')
), data=None).AndReturn((resp, ''))
@@ -2358,7 +2358,7 @@ class ShellTestResources(ShellBase):
'POST', '/stacks/%s/resources/%s/signal' %
(
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(
+ parse.quote(encodeutils.safe_encode(
resource_name), '')
),
data={'message': 'Content'}).AndReturn((resp, ''))
diff --git a/heatclient/v1/events.py b/heatclient/v1/events.py
index f63d30c..205e8ab 100644
--- a/heatclient/v1/events.py
+++ b/heatclient/v1/events.py
@@ -16,8 +16,9 @@
import six
from six.moves.urllib import parse
+from oslo.utils import encodeutils
+
from heatclient.openstack.common.apiclient import base
-from heatclient.openstack.common import strutils
from heatclient.v1 import stacks
DEFAULT_PAGE_SIZE = 20
@@ -61,7 +62,7 @@ class EventManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id)
url = '/stacks/%s/resources/%s/events' % (
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(resource_name), ''))
+ parse.quote(encodeutils.safe_encode(resource_name), ''))
if params:
url += '?%s' % parse.urlencode(params, True)
@@ -77,7 +78,7 @@ class EventManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s/events/%s' % (
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(resource_name), ''),
+ parse.quote(encodeutils.safe_encode(resource_name), ''),
parse.quote(event_id, ''))
resp, body = self.client.json_request('GET', url_str)
return Event(self, body['event'])
diff --git a/heatclient/v1/resource_types.py b/heatclient/v1/resource_types.py
index 9ffb80b..527e3bf 100644
--- a/heatclient/v1/resource_types.py
+++ b/heatclient/v1/resource_types.py
@@ -13,8 +13,9 @@
from six.moves.urllib import parse
+from oslo.utils import encodeutils
+
from heatclient.openstack.common.apiclient import base
-from heatclient.openstack.common import strutils
class ResourceType(base.Resource):
@@ -43,12 +44,12 @@ class ResourceTypeManager(base.BaseManager):
:param resource_type: name of the resource type to get the details for
"""
url_str = '/resource_types/%s' % (
- parse.quote(strutils.safe_encode(resource_type), ''))
+ parse.quote(encodeutils.safe_encode(resource_type), ''))
resp, body = self.client.json_request('GET', url_str)
return body
def generate_template(self, resource_type):
url_str = '/resource_types/%s/template' % (
- parse.quote(strutils.safe_encode(resource_type), ''))
+ parse.quote(encodeutils.safe_encode(resource_type), ''))
resp, body = self.client.json_request('GET', url_str)
return body
diff --git a/heatclient/v1/resources.py b/heatclient/v1/resources.py
index 82c62ac..24d9b18 100644
--- a/heatclient/v1/resources.py
+++ b/heatclient/v1/resources.py
@@ -15,8 +15,9 @@
from six.moves.urllib import parse
+from oslo.utils import encodeutils
+
from heatclient.openstack.common.apiclient import base
-from heatclient.openstack.common import strutils
from heatclient.v1 import stacks
DEFAULT_PAGE_SIZE = 20
@@ -57,7 +58,7 @@ class ResourceManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s' % (
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(resource_name), ''))
+ parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('GET', url_str)
return Resource(self, body['resource'])
@@ -70,7 +71,7 @@ class ResourceManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s/metadata' % (
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(resource_name), ''))
+ parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('GET', url_str)
return body['metadata']
@@ -83,7 +84,7 @@ class ResourceManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s/signal' % (
parse.quote(stack_id, ''),
- parse.quote(strutils.safe_encode(resource_name), ''))
+ parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('POST', url_str, data=data)
return body
@@ -92,6 +93,6 @@ class ResourceManager(stacks.StackChildManager):
instead.
"""
url_str = '/resource_types/%s/template' % (
- parse.quote(strutils.safe_encode(resource_name), ''))
+ parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('GET', url_str)
return body
diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py
index ffeca26..28cb72e 100644
--- a/heatclient/v1/shell.py
+++ b/heatclient/v1/shell.py
@@ -19,10 +19,10 @@ from six.moves.urllib import request
import yaml
from oslo.serialization import jsonutils
+from oslo.utils import strutils
from heatclient.common import template_utils
from heatclient.common import utils
-from heatclient.openstack.common import strutils
import heatclient.exc as exc
diff --git a/openstack-common.conf b/openstack-common.conf
index 2b51a39..febc13f 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,7 +1,7 @@
[DEFAULT]
# The list of modules to copy from openstack-common
-modules=importutils,gettextutils,strutils,apiclient.base,apiclient.exceptions
+modules=apiclient
module=cliutils
# The base module to hold the copy of openstack.common
diff --git a/requirements.txt b/requirements.txt
index 3651bbc..60936c7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,6 +7,7 @@ argparse
iso8601>=0.1.9
PrettyTable>=0.7,<0.8
oslo.serialization>=1.0.0 # Apache-2.0
+oslo.utils>=1.0.0 # Apache-2.0
python-keystoneclient>=0.11.1
PyYAML>=3.1.0
requests>=2.2.0,!=2.4.0