summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/object_store_v1.py166
-rw-r--r--openstackclient/common/utils.py34
-rw-r--r--openstackclient/compute/client.py10
-rw-r--r--openstackclient/image/v1/image.py26
-rw-r--r--openstackclient/object/v1/account.py83
-rw-r--r--openstackclient/object/v1/container.py62
-rw-r--r--openstackclient/object/v1/object.py74
-rw-r--r--openstackclient/shell.py4
-rw-r--r--openstackclient/tests/api/test_object_store_v1.py2
-rw-r--r--openstackclient/tests/common/test_utils.py54
-rw-r--r--openstackclient/tests/object/v1/test_container_all.py2
-rw-r--r--openstackclient/tests/object/v1/test_object_all.py2
-rw-r--r--openstackclient/volume/v2/volume.py2
13 files changed, 488 insertions, 33 deletions
diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py
index c52eeb3a..c870332a 100644
--- a/openstackclient/api/object_store_v1.py
+++ b/openstackclient/api/object_store_v1.py
@@ -44,9 +44,8 @@ class APIv1(api.BaseAPI):
"""
response = self.create(container, method='PUT')
- url_parts = urlparse(self.endpoint)
data = {
- 'account': url_parts.path.split('/')[-1],
+ 'account': self._find_account_id(),
'container': container,
'x-trans-id': response.headers.get('x-trans-id', None),
}
@@ -140,6 +139,23 @@ class APIv1(api.BaseAPI):
for object in objects:
self.object_save(container=container, object=object['name'])
+ def container_set(
+ self,
+ container,
+ properties,
+ ):
+ """Set container properties
+
+ :param string container:
+ name of container to modify
+ :param dict properties:
+ properties to add or update for the container
+ """
+
+ headers = self._set_properties(properties, 'X-Container-Meta-%s')
+ if headers:
+ self.create(container, headers=headers)
+
def container_show(
self,
container=None,
@@ -154,12 +170,13 @@ class APIv1(api.BaseAPI):
response = self._request('HEAD', container)
data = {
- 'account': response.headers.get('x-container-meta-owner', None),
+ 'account': self._find_account_id(),
'container': container,
'object_count': response.headers.get(
'x-container-object-count',
None,
),
+ 'meta-owner': response.headers.get('x-container-meta-owner', None),
'bytes_used': response.headers.get('x-container-bytes-used', None),
'read_acl': response.headers.get('x-container-read', None),
'write_acl': response.headers.get('x-container-write', None),
@@ -168,6 +185,24 @@ class APIv1(api.BaseAPI):
}
return data
+ def container_unset(
+ self,
+ container,
+ properties,
+ ):
+ """Unset container properties
+
+ :param string container:
+ name of container to modify
+ :param dict properties:
+ properties to remove from the container
+ """
+
+ headers = self._unset_properties(properties,
+ 'X-Remove-Container-Meta-%s')
+ if headers:
+ self.create(container, headers=headers)
+
def object_create(
self,
container=None,
@@ -194,9 +229,8 @@ class APIv1(api.BaseAPI):
method='PUT',
data=f,
)
- url_parts = urlparse(self.endpoint)
data = {
- 'account': url_parts.path.split('/')[-1],
+ 'account': self._find_account_id(),
'container': container,
'object': object,
'x-trans-id': response.headers.get('X-Trans-Id', None),
@@ -332,6 +366,46 @@ class APIv1(api.BaseAPI):
for chunk in response.iter_content():
f.write(chunk)
+ def object_set(
+ self,
+ container,
+ object,
+ properties,
+ ):
+ """Set object properties
+
+ :param string container:
+ container name for object to modify
+ :param string object:
+ name of object to modify
+ :param dict properties:
+ properties to add or update for the container
+ """
+
+ headers = self._set_properties(properties, 'X-Object-Meta-%s')
+ if headers:
+ self.create("%s/%s" % (container, object), headers=headers)
+
+ def object_unset(
+ self,
+ container,
+ object,
+ properties,
+ ):
+ """Unset object properties
+
+ :param string container:
+ container name for object to modify
+ :param string object:
+ name of object to modify
+ :param dict properties:
+ properties to remove from the object
+ """
+
+ headers = self._unset_properties(properties, 'X-Remove-Object-Meta-%s')
+ if headers:
+ self.create("%s/%s" % (container, object), headers=headers)
+
def object_show(
self,
container=None,
@@ -352,10 +426,11 @@ class APIv1(api.BaseAPI):
response = self._request('HEAD', "%s/%s" % (container, object))
data = {
- 'account': response.headers.get('x-container-meta-owner', None),
+ 'account': self._find_account_id(),
'container': container,
'object': object,
'content-type': response.headers.get('content-type', None),
+ 'meta-owner': response.headers.get('x-container-meta-owner', None),
}
if 'content-length' in response.headers:
data['content-length'] = response.headers.get(
@@ -386,3 +461,82 @@ class APIv1(api.BaseAPI):
data[key.lower()] = value
return data
+
+ def account_set(
+ self,
+ properties,
+ ):
+ """Set account properties
+
+ :param dict properties:
+ properties to add or update for the account
+ """
+
+ headers = self._set_properties(properties, 'X-Account-Meta-%s')
+ if headers:
+ # NOTE(stevemar): The URL (first argument) in this case is already
+ # set to the swift account endpoint, because that's how it's
+ # registered in the catalog
+ self.create("", headers=headers)
+
+ def account_show(self):
+ """Show account details"""
+
+ # NOTE(stevemar): Just a HEAD request to the endpoint already in the
+ # catalog should be enough.
+ response = self._request("HEAD", "")
+ data = {}
+ for k, v in response.headers.iteritems():
+ data[k] = v
+ # Map containers, bytes and objects a bit nicer
+ data['Containers'] = data.pop('x-account-container-count', None)
+ data['Objects'] = data.pop('x-account-object-count', None)
+ data['Bytes'] = data.pop('x-account-bytes-used', None)
+ # Add in Account info too
+ data['Account'] = self._find_account_id()
+ return data
+
+ def account_unset(
+ self,
+ properties,
+ ):
+ """Unset account properties
+
+ :param dict properties:
+ properties to remove from the account
+ """
+
+ headers = self._unset_properties(properties,
+ 'X-Remove-Account-Meta-%s')
+ if headers:
+ self.create("", headers=headers)
+
+ def _find_account_id(self):
+ url_parts = urlparse(self.endpoint)
+ return url_parts.path.split('/')[-1]
+
+ def _unset_properties(self, properties, header_tag):
+ # NOTE(stevemar): As per the API, the headers have to be in the form
+ # of "X-Remove-Account-Meta-Book: x". In the case where metadata is
+ # removed, we can set the value of the header to anything, so it's
+ # set to 'x'. In the case of a Container property we use:
+ # "X-Remove-Container-Meta-Book: x", and the same logic applies for
+ # Object properties
+
+ headers = {}
+ for k in properties:
+ header_name = header_tag % k
+ headers[header_name] = 'x'
+ return headers
+
+ def _set_properties(self, properties, header_tag):
+ # NOTE(stevemar): As per the API, the headers have to be in the form
+ # of "X-Account-Meta-Book: MobyDick". In the case of a Container
+ # property we use: "X-Add-Container-Meta-Book: MobyDick", and the same
+ # logic applies for Object properties
+
+ headers = {}
+ for k, v in properties.iteritems():
+ header_name = header_tag % k
+ headers[header_name] = v
+ return headers
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index b6726bfa..51e2a2f9 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -94,12 +94,15 @@ def find_resource(manager, name_or_id, **kwargs):
if len(kwargs) == 0:
kwargs = {}
- # Prepare the kwargs for calling find
- if 'NAME_ATTR' in manager.resource_class.__dict__:
- # novaclient does this for oddball resources
- kwargs[manager.resource_class.NAME_ATTR] = name_or_id
- else:
- kwargs['name'] = name_or_id
+ try:
+ # Prepare the kwargs for calling find
+ if 'NAME_ATTR' in manager.resource_class.__dict__:
+ # novaclient does this for oddball resources
+ kwargs[manager.resource_class.NAME_ATTR] = name_or_id
+ else:
+ kwargs['name'] = name_or_id
+ except Exception:
+ pass
# finally try to find entity by name
try:
@@ -118,7 +121,24 @@ def find_resource(manager, name_or_id, **kwargs):
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg)
else:
- raise
+ pass
+
+ try:
+ for resource in manager.list():
+ # short circuit and return the first match
+ if (resource.get('id') == name_or_id or
+ resource.get('name') == name_or_id):
+ return resource
+ else:
+ # we found no match, keep going to bomb out
+ pass
+ except Exception:
+ # in case the list fails for some reason
+ pass
+
+ # if we hit here, we've failed, report back this error:
+ msg = "Could not find resource %s" % name_or_id
+ raise exceptions.CommandError(msg)
def format_dict(data):
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 8ac5f324..23a4deca 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -34,13 +34,8 @@ _compute_api_version = None
def make_client(instance):
"""Returns a compute service client."""
- # Defer client imports until we actually need them
+ # Defer client import until we actually need them
from novaclient import client as nova_client
- from novaclient import extension
- try:
- from novaclient.v2.contrib import list_extensions
- except ImportError:
- from novaclient.v1_1.contrib import list_extensions
if _compute_api_version is not None:
version = _compute_api_version
@@ -52,7 +47,8 @@ def make_client(instance):
# Set client http_log_debug to True if verbosity level is high enough
http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
- extensions = [extension.Extension('list_extensions', list_extensions)]
+ extensions = [ext for ext in nova_client.discover_extensions(version)
+ if ext.name == "list_extensions"]
# Remember interface only if it is set
kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface)
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 68c81cd5..81d384f7 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -213,6 +213,8 @@ class CreateImage(show.ShowOne):
if parsed_args.private:
kwargs['is_public'] = False
+ info = {}
+
if not parsed_args.location and not parsed_args.copy_from:
if parsed_args.volume:
volume_client = self.app.client_manager.volume
@@ -241,18 +243,18 @@ class CreateImage(show.ShowOne):
# do a chunked transfer
kwargs["data"] = sys.stdin
- # Wrap the call to catch exceptions in order to close files
- try:
- image = image_client.images.create(**kwargs)
- finally:
- # Clean up open files - make sure data isn't a string
- if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
- kwargs['data'] != sys.stdin):
- kwargs['data'].close()
-
- info = {}
- info.update(image._info)
- info['properties'] = utils.format_dict(info.get('properties', {}))
+ if not parsed_args.volume:
+ # Wrap the call to catch exceptions in order to close files
+ try:
+ image = image_client.images.create(**kwargs)
+ finally:
+ # Clean up open files - make sure data isn't a string
+ if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
+ kwargs['data'] != sys.stdin):
+ kwargs['data'].close()
+
+ info.update(image._info)
+ info['properties'] = utils.format_dict(info.get('properties', {}))
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py
new file mode 100644
index 00000000..4ff890ce
--- /dev/null
+++ b/openstackclient/object/v1/account.py
@@ -0,0 +1,83 @@
+# 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.
+#
+
+"""Account v1 action implementations"""
+
+import logging
+
+from cliff import command
+from cliff import show
+import six
+
+from openstackclient.common import parseractions
+from openstackclient.common import utils
+
+
+class SetAccount(command.Command):
+ """Set account properties"""
+
+ log = logging.getLogger(__name__ + '.SetAccount')
+
+ def get_parser(self, prog_name):
+ parser = super(SetAccount, self).get_parser(prog_name)
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ required=True,
+ action=parseractions.KeyValueAction,
+ help="Set a property on this account "
+ "(repeat option to set multiple properties)"
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.account_set(
+ properties=parsed_args.property,
+ )
+
+
+class ShowAccount(show.ShowOne):
+ """Display account details"""
+
+ log = logging.getLogger(__name__ + '.ShowAccount')
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ data = self.app.client_manager.object_store.account_show()
+ return zip(*sorted(six.iteritems(data)))
+
+
+class UnsetAccount(command.Command):
+ """Unset account properties"""
+
+ log = logging.getLogger(__name__ + '.UnsetAccount')
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetAccount, self).get_parser(prog_name)
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ required=True,
+ action='append',
+ default=[],
+ help='Property to remove from account '
+ '(repeat option to remove multiple properties)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.account_unset(
+ properties=parsed_args.property,
+ )
diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py
index 49173deb..b8eb4c25 100644
--- a/openstackclient/object/v1/container.py
+++ b/openstackclient/object/v1/container.py
@@ -23,6 +23,7 @@ from cliff import command
from cliff import lister
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -178,6 +179,36 @@ class SaveContainer(command.Command):
)
+class SetContainer(command.Command):
+ """Set container properties"""
+
+ log = logging.getLogger(__name__ + '.SetContainer')
+
+ def get_parser(self, prog_name):
+ parser = super(SetContainer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Container to modify',
+ )
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ required=True,
+ action=parseractions.KeyValueAction,
+ help="Set a property on this container "
+ "(repeat option to set multiple properties)"
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.container_set(
+ parsed_args.container,
+ properties=parsed_args.property,
+ )
+
+
class ShowContainer(show.ShowOne):
"""Display container details"""
@@ -200,3 +231,34 @@ class ShowContainer(show.ShowOne):
)
return zip(*sorted(six.iteritems(data)))
+
+
+class UnsetContainer(command.Command):
+ """Unset container properties"""
+
+ log = logging.getLogger(__name__ + '.UnsetContainer')
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetContainer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Container to modify',
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ required=True,
+ action='append',
+ default=[],
+ help='Property to remove from container '
+ '(repeat option to remove multiple properties)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.container_unset(
+ parsed_args.container,
+ properties=parsed_args.property,
+ )
diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py
index c90f0319..a023e3a0 100644
--- a/openstackclient/object/v1/object.py
+++ b/openstackclient/object/v1/object.py
@@ -23,6 +23,7 @@ from cliff import command
from cliff import lister
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -221,6 +222,42 @@ class SaveObject(command.Command):
)
+class SetObject(command.Command):
+ """Set object properties"""
+
+ log = logging.getLogger(__name__ + '.SetObject')
+
+ def get_parser(self, prog_name):
+ parser = super(SetObject, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Modify <object> from <container>',
+ )
+ parser.add_argument(
+ 'object',
+ metavar='<object>',
+ help='Object to modify',
+ )
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ required=True,
+ action=parseractions.KeyValueAction,
+ help="Set a property on this object "
+ "(repeat option to set multiple properties)"
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.object_set(
+ parsed_args.container,
+ parsed_args.object,
+ properties=parsed_args.property,
+ )
+
+
class ShowObject(show.ShowOne):
"""Display object details"""
@@ -249,3 +286,40 @@ class ShowObject(show.ShowOne):
)
return zip(*sorted(six.iteritems(data)))
+
+
+class UnsetObject(command.Command):
+ """Unset object properties"""
+
+ log = logging.getLogger(__name__ + '.UnsetObject')
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetObject, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Modify <object> from <container>',
+ )
+ parser.add_argument(
+ 'object',
+ metavar='<object>',
+ help='Object to modify',
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ required=True,
+ action='append',
+ default=[],
+ help='Property to remove from object '
+ '(repeat option to remove multiple properties)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.object_unset(
+ parsed_args.container,
+ parsed_args.object,
+ properties=parsed_args.property,
+ )
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index d3a7d8a5..5b36b8b2 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -79,6 +79,10 @@ class OpenStackShell(app.App):
help.HelpCommand.auth_required = False
complete.CompleteCommand.auth_required = False
+ # Slight change to the meaning of --debug
+ self.DEFAULT_DEBUG_VALUE = None
+ self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.'
+
super(OpenStackShell, self).__init__(
description=__doc__.strip(),
version=openstackclient.__version__,
diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py
index b18a003d..323bb8e0 100644
--- a/openstackclient/tests/api/test_object_store_v1.py
+++ b/openstackclient/tests/api/test_object_store_v1.py
@@ -157,6 +157,7 @@ class TestContainer(TestObjectAPIv1):
'container': 'qaz',
'object_count': '1',
'bytes_used': '577',
+ 'meta-owner': FAKE_ACCOUNT,
'read_acl': None,
'write_acl': None,
'sync_to': None,
@@ -322,6 +323,7 @@ class TestObject(TestObjectAPIv1):
'content-type': 'text/alpha',
'content-length': '577',
'last-modified': '20130101',
+ 'meta-owner': FAKE_ACCOUNT,
'etag': 'qaz',
'wife': 'Wilma',
'x-tra-header': 'yabba-dabba-do',
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
index a25a5ba5..373c0de4 100644
--- a/openstackclient/tests/common/test_utils.py
+++ b/openstackclient/tests/common/test_utils.py
@@ -20,6 +20,7 @@ import mock
from openstackclient.common import exceptions
from openstackclient.common import utils
+from openstackclient.tests import fakes
from openstackclient.tests import utils as test_utils
PASSWORD = "Pa$$w0rd"
@@ -27,6 +28,18 @@ WASSPORD = "Wa$$p0rd"
DROWSSAP = "dr0w$$aP"
+class FakeOddballResource(fakes.FakeResource):
+
+ def get(self, attr):
+ """get() is needed for utils.find_resource()"""
+ if attr == 'id':
+ return self.id
+ elif attr == 'name':
+ return self.name
+ else:
+ return None
+
+
class TestUtils(test_utils.TestCase):
def test_get_password_good(self):
@@ -242,6 +255,47 @@ class TestFindResource(test_utils.TestCase):
self.manager.get.assert_called_with(self.name)
self.manager.find.assert_called_with(name=self.name)
+ def test_find_resource_silly_resource(self):
+ # We need a resource with no resource_class for this test, start fresh
+ self.manager = mock.Mock()
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(
+ side_effect=AttributeError(
+ "'Controller' object has no attribute 'find'",
+ )
+ )
+ silly_resource = FakeOddballResource(
+ None,
+ {'id': '12345', 'name': self.name},
+ loaded=True,
+ )
+ self.manager.list = mock.Mock(
+ return_value=[silly_resource, ],
+ )
+ result = utils.find_resource(self.manager, self.name)
+ self.assertEqual(silly_resource, result)
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
+
+ def test_find_resource_silly_resource_not_found(self):
+ # We need a resource with no resource_class for this test, start fresh
+ self.manager = mock.Mock()
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(
+ side_effect=AttributeError(
+ "'Controller' object has no attribute 'find'",
+ )
+ )
+ self.manager.list = mock.Mock(return_value=[])
+ result = self.assertRaises(exceptions.CommandError,
+ utils.find_resource,
+ self.manager,
+ self.name)
+ self.assertEqual("Could not find resource legos",
+ str(result))
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
+
def test_format_dict(self):
expected = "a='b', c='d', e='f'"
self.assertEqual(expected,
diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py
index 8b200e09..4477f2e0 100644
--- a/openstackclient/tests/object/v1/test_container_all.py
+++ b/openstackclient/tests/object/v1/test_container_all.py
@@ -316,6 +316,7 @@ class TestContainerShow(TestContainerAll):
'account',
'bytes_used',
'container',
+ 'meta-owner',
'object_count',
'read_acl',
'sync_key',
@@ -327,6 +328,7 @@ class TestContainerShow(TestContainerAll):
object_fakes.ACCOUNT_ID,
'123',
'ernie',
+ object_fakes.ACCOUNT_ID,
'42',
'qaz',
'rfv',
diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py
index 7a76ab76..41fe6324 100644
--- a/openstackclient/tests/object/v1/test_object_all.py
+++ b/openstackclient/tests/object/v1/test_object_all.py
@@ -160,6 +160,7 @@ class TestObjectShow(TestObjectAll):
'content-type',
'etag',
'last-modified',
+ 'meta-owner',
'object',
'x-object-manifest',
)
@@ -171,6 +172,7 @@ class TestObjectShow(TestObjectAll):
'text/plain',
'4c4e39a763d58392724bccf76a58783a',
'yesterday',
+ object_fakes.ACCOUNT_ID,
object_fakes.object_name_1,
'manifest',
)
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index ad6215e4..758f312b 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -175,7 +175,7 @@ class DeleteVolume(command.Command):
action="store_true",
default=False,
help="Attempt forced removal of volume(s), regardless of state "
- "(defaults to False"
+ "(defaults to False)"
)
return parser