Remove deprecated image commands/API bindings

We deprecated the image proxy commands and APIs in Newton
due to the 2.36 microversion. We said after Ocata 15.0.0 we
would remove these, which we can do now in Pike.

Note that the list() method on the ImageManager has to be
moved to the GlanceManager since we still need to list images
for the --image-with option on the boot command. The _match_image
method in the shell has to be updated for a glance v2 response
where custom metadata properties are flat in the image body.

This needs to be released with a major version bump.

Change-Id: I2d9fd0243d42538bd1417a42357c17b09368d2a5
This commit is contained in:
Matt Riedemann
2017-02-22 07:19:07 -05:00
committed by Matt Riedemann
parent e7df023d31
commit 41f66d15aa
11 changed files with 44 additions and 448 deletions

View File

@@ -79,10 +79,6 @@ class SimpleReadOnlyNovaClientTest(base.ClientTestBase):
def test_admin_hypervisor_list(self): def test_admin_hypervisor_list(self):
self.nova('hypervisor-list') self.nova('hypervisor-list')
def test_admin_image_list(self):
out = self.nova('image-list', merge_stderr=True)
self.assertIn('Command image-list is deprecated', out)
@decorators.skip_because(bug="1157349") @decorators.skip_because(bug="1157349")
def test_admin_interface_list(self): def test_admin_interface_list(self):
self.nova('interface-list') self.nova('interface-list')

View File

@@ -21,15 +21,6 @@ class TestImageMetaV239(base.ClientTestBase):
# fallback to 2.35 and emit a warning. # fallback to 2.35 and emit a warning.
COMPUTE_API_VERSION = "2.39" COMPUTE_API_VERSION = "2.39"
def test_command_deprecation(self):
output = self.nova('image-meta %s set test_key=test_value' %
self.image.id, merge_stderr=True)
self.assertIn('is deprecated', output)
output = self.nova('image-meta %s delete test_key' %
self.image.id, merge_stderr=True)
self.assertIn('is deprecated', output)
def test_limits(self): def test_limits(self):
"""Tests that 2.39 won't return 'maxImageMeta' resource limit and """Tests that 2.39 won't return 'maxImageMeta' resource limit and
the CLI output won't show it. the CLI output won't show it.

View File

@@ -13,8 +13,6 @@
import random import random
import string import string
import novaclient
from novaclient import api_versions
from novaclient.tests.functional import base from novaclient.tests.functional import base
from novaclient.tests.functional.v2.legacy import test_servers from novaclient.tests.functional.v2.legacy import test_servers
from novaclient.v2 import shell from novaclient.v2 import shell
@@ -25,18 +23,6 @@ class TestServersBootNovaClient(test_servers.TestServersBootNovaClient):
COMPUTE_API_VERSION = "2.latest" COMPUTE_API_VERSION = "2.latest"
def test_boot_server_using_image_with(self):
# --image-with relies on listing images via the compute image proxy
# API which does not work after 2.35 so we have to cap for this test.
try:
self.COMPUTE_API_VERSION = (
min(novaclient.API_MAX_VERSION,
api_versions.APIVersion('2.35')).get_string())
super(TestServersBootNovaClient,
self).test_boot_server_using_image_with()
finally:
self.COMPUTE_API_VERSION = '2.latest'
class TestServersListNovaClient(test_servers.TestServersListNovaClient): class TestServersListNovaClient(test_servers.TestServersListNovaClient):
"""Servers list functional tests.""" """Servers list functional tests."""

View File

@@ -16,7 +16,7 @@ from novaclient.tests.unit.fixture_data import base
class V1(base.Fixture): class V1(base.Fixture):
base_url = 'images' base_url = 'v2/images'
def setUp(self): def setUp(self):
super(V1, self).setUp() super(V1, self).setUp()
@@ -78,8 +78,3 @@ class V1(base.Fixture):
for u in (1, '1/metadata/test_key'): for u in (1, '1/metadata/test_key'):
self.requests_mock.delete(self.url(u), status_code=204, self.requests_mock.delete(self.url(u), status_code=204,
headers=headers) headers=headers)
class V3(V1):
base_url = 'v1/images'

View File

@@ -1103,7 +1103,7 @@ class FakeSessionClient(base_client.SessionClient):
# #
# Images # Images
# #
def get_images_detail(self, **kw): def get_images(self, **kw):
return (200, {}, {'images': [ return (200, {}, {'images': [
{ {
"id": FAKE_IMAGE_UUID_SNAPSHOT, "id": FAKE_IMAGE_UUID_SNAPSHOT,
@@ -1131,9 +1131,7 @@ class FakeSessionClient(base_client.SessionClient):
"updated": "2010-10-10T12:00:00Z", "updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z", "created": "2010-08-10T12:00:00Z",
"status": "ACTIVE", "status": "ACTIVE",
"metadata": { "test_key": "test_value",
"test_key": "test_value",
},
"links": {}, "links": {},
}, },
{ {
@@ -1149,41 +1147,20 @@ class FakeSessionClient(base_client.SessionClient):
]}) ]})
def get_images_555cae93_fb41_4145_9c52_f5b923538a26(self, **kw): def get_images_555cae93_fb41_4145_9c52_f5b923538a26(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][0]}) return (200, {}, {'image': self.get_images()[2]['images'][0]})
def get_images_55bb23af_97a4_4068_bdf8_f10c62880ddf(self, **kw): def get_images_55bb23af_97a4_4068_bdf8_f10c62880ddf(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][1]}) return (200, {}, {'image': self.get_images()[2]['images'][1]})
def get_images_c99d7632_bd66_4be9_aed5_3dd14b223a76(self, **kw): def get_images_c99d7632_bd66_4be9_aed5_3dd14b223a76(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][2]}) return (200, {}, {'image': self.get_images()[2]['images'][2]})
def get_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw): def get_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][3]}) return (200, {}, {'image': self.get_images()[2]['images'][3]})
def get_images_3e861307_73a6_4d1f_8d68_f68b03223032(self): def get_images_3e861307_73a6_4d1f_8d68_f68b03223032(self):
raise exceptions.NotFound('404') raise exceptions.NotFound('404')
def post_images_c99d7632_bd66_4be9_aed5_3dd14b223a76_metadata(
self, body, **kw):
assert list(body) == ['metadata']
fakes.assert_has_keys(body['metadata'],
required=['test_key'])
get_image = self.get_images_c99d7632_bd66_4be9_aed5_3dd14b223a76
return (
200,
{},
{'metadata': get_image()[2]['image']['metadata']})
def delete_images_c99d7632_bd66_4be9_aed5_3dd14b223a76(self, **kw):
return (204, {}, None)
def delete_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw):
return (204, {}, None)
def delete_images_c99d7632_bd66_4be9_aed5_3dd14b223a76_metadata_test_key(
self, **kw):
return (204, {}, None)
# #
# Keypairs # Keypairs
# #

View File

@@ -11,12 +11,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import warnings
import mock import mock
from novaclient import api_versions
from novaclient import exceptions
from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import images as data from novaclient.tests.unit.fixture_data import images as data
from novaclient.tests.unit import utils from novaclient.tests.unit import utils
@@ -29,95 +25,13 @@ class ImagesTest(utils.FixturedTestCase):
client_fixture_class = client.V1 client_fixture_class = client.V1
data_fixture_class = data.V1 data_fixture_class = data.V1
@mock.patch.object(warnings, 'warn') @mock.patch('novaclient.base.Manager.alternate_service_type')
def test_list_images(self, mock_warn): def test_list_images(self, mock_alternate_service_type):
il = self.cs.images.list() il = self.cs.glance.list()
self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images/detail') self.assert_called('GET', '/v2/images')
for i in il: for i in il:
self.assertIsInstance(i, images.Image) self.assertIsInstance(i, images.Image)
self.assertEqual(2, len(il)) self.assertEqual(2, len(il))
self.assertEqual(1, mock_warn.call_count) mock_alternate_service_type.assert_called_once_with(
'image', allowed_types=('image',))
def test_list_images_undetailed(self):
il = self.cs.images.list(detailed=False)
self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images')
for i in il:
self.assertIsInstance(i, images.Image)
def test_list_images_with_marker_limit(self):
il = self.cs.images.list(marker=1234, limit=4)
self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images/detail?limit=4&marker=1234')
@mock.patch.object(warnings, 'warn')
def test_get_image_details(self, mock_warn):
i = self.cs.images.get(1)
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images/1')
self.assertIsInstance(i, images.Image)
self.assertEqual(1, i.id)
self.assertEqual('CentOS 5.2', i.name)
self.assertEqual(1, mock_warn.call_count)
@mock.patch.object(warnings, 'warn')
def test_delete_image(self, mock_warn):
i = self.cs.images.delete(1)
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('DELETE', '/images/1')
self.assertEqual(1, mock_warn.call_count)
@mock.patch.object(warnings, 'warn')
def test_delete_meta(self, mock_warn):
i = self.cs.images.delete_meta(1, {'test_key': 'test_value'})
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('DELETE', '/images/1/metadata/test_key')
self.assertEqual(1, mock_warn.call_count)
@mock.patch.object(warnings, 'warn')
def test_set_meta(self, mock_warn):
i = self.cs.images.set_meta(1, {'test_key': 'test_value'})
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/images/1/metadata',
{"metadata": {'test_key': 'test_value'}})
self.assertEqual(1, mock_warn.call_count)
@mock.patch.object(warnings, 'warn')
def test_find(self, mock_warn):
i = self.cs.images.find(name="CentOS 5.2")
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assertEqual(1, i.id)
self.assert_called('GET', '/images/1')
# This is two warnings because find calls findall which calls list
# which is the first warning, which finds one results and then calls
# get on that, which is the second warning.
self.assertEqual(2, mock_warn.call_count)
iml = self.cs.images.findall(status='SAVING')
self.assert_request_id(iml, fakes.FAKE_REQUEST_ID_LIST)
self.assertEqual(1, len(iml))
self.assertEqual('My Server Backup', iml[0].name)
def test_find_2_36(self):
"""Tests that using the find method fails after microversion 2.35.
"""
self.cs.api_version = api_versions.APIVersion('2.36')
self.assertRaises(exceptions.VersionNotFoundForAPIMethod,
self.cs.images.find, name="CentOS 5.2")
def test_delete_meta_2_39(self):
"""Tests that 'delete_meta' method fails after microversion 2.39.
"""
self.cs.api_version = api_versions.APIVersion('2.39')
self.assertRaises(exceptions.VersionNotFoundForAPIMethod,
self.cs.images.delete_meta, 1,
{'test_key': 'test_value'})
def test_set_meta_2_39(self):
"""Tests that 'set_meta' method fails after microversion 2.39.
"""
self.cs.api_version = api_versions.APIVersion('2.39')
self.assertRaises(exceptions.VersionNotFoundForAPIMethod,
self.cs.images.set_meta, 1,
{'test_key': 'test_value'})

View File

@@ -1199,35 +1199,6 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/flavors/2/action', self.assert_called('POST', '/flavors/2/action',
{'removeTenantAccess': {'tenant': 'proj2'}}) {'removeTenantAccess': {'tenant': 'proj2'}})
def test_image_show(self):
_out, err = self.run_command('image-show %s' % FAKE_UUID_1)
self.assertIn('Command image-show is deprecated', err)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1)
def test_image_meta_set(self):
_out, err = self.run_command('image-meta %s set test_key=test_value' %
FAKE_UUID_1)
self.assertIn('Command image-meta is deprecated', err)
self.assert_called('POST', '/images/%s/metadata' % FAKE_UUID_1,
{'metadata': {'test_key': 'test_value'}})
def test_image_meta_del(self):
_out, err = self.run_command('image-meta %s delete test_key' %
FAKE_UUID_1)
self.assertIn('Command image-meta is deprecated', err)
self.assert_called('DELETE', '/images/%s/metadata/test_key' %
FAKE_UUID_1)
@mock.patch('sys.stdout', six.StringIO())
@mock.patch('sys.stderr', six.StringIO())
def test_image_meta_bad_action(self):
self.assertRaises(SystemExit, self.run_command,
'image-meta 1 BAD_ACTION test_key=test_value')
def test_image_list(self):
self.run_command('image-list')
self.assert_called('GET', '/images/detail')
def test_create_image(self): def test_create_image(self):
self.run_command('image-create sample-server mysnapshot') self.run_command('image-create sample-server mysnapshot')
self.assert_called( self.assert_called(
@@ -1273,18 +1244,6 @@ class ShellTest(utils.TestCase):
exceptions.InstanceInDeletedState, self.run_command, exceptions.InstanceInDeletedState, self.run_command,
'image-create sample-server mysnapshot_deleted --poll') 'image-create sample-server mysnapshot_deleted --poll')
def test_image_delete(self):
_out, err = self.run_command('image-delete %s' % FAKE_UUID_1)
self.assertIn('Command image-delete is deprecated', err)
self.assert_called('DELETE', '/images/%s' % FAKE_UUID_1)
def test_image_delete_multiple(self):
self.run_command('image-delete %s %s' % (FAKE_UUID_1, FAKE_UUID_2))
self.assert_called('GET', '/v2/images/' + FAKE_UUID_1, pos=0)
self.assert_called('DELETE', '/images/' + FAKE_UUID_1, pos=1)
self.assert_called('GET', '/v2/images/' + FAKE_UUID_2, pos=2)
self.assert_called('DELETE', '/images/' + FAKE_UUID_2, pos=3)
def test_list(self): def test_list(self):
self.run_command('list') self.run_command('list')
self.assert_called('GET', '/servers/detail') self.assert_called('GET', '/servers/detail')
@@ -3545,39 +3504,3 @@ class ShellNetworkUtilTest(utils.TestCase):
# the deprecated_network decorator will set cs.client.api_version # the deprecated_network decorator will set cs.client.api_version
# after calling the wrapped function # after calling the wrapped function
self.assertEqual(cs.api_version, cs.api_version) self.assertEqual(cs.api_version, cs.api_version)
class ShellImageUtilTest(utils.TestCase):
def test_deprecated_image_newer(self):
@novaclient.v2.shell.deprecated_image
def tester(cs):
'foo'
self.assertEqual(api_versions.APIVersion('2.35'),
cs.api_version)
cs = mock.MagicMock()
cs.api_version = api_versions.APIVersion('2.9999')
tester(cs)
self.assertEqual('DEPRECATED: foo', tester.__doc__)
def test_deprecated_image_older(self):
@novaclient.v2.shell.deprecated_image
def tester(cs):
'foo'
# since we didn't need to adjust the api_version the mock won't
# have cs.client.api_version set on it
self.assertFalse(hasattr(cs, 'client'))
# we have to set the attribute back on cs so the decorator can
# set the value on it when we return from this wrapped function
setattr(cs, 'client', mock.MagicMock())
cs = mock.MagicMock()
cs.api_version = api_versions.APIVersion('2.1')
# we have to delete the cs.client attribute so hasattr won't return a
# false positive in the wrapped function
del cs.client
tester(cs)
self.assertEqual('DEPRECATED: foo', tester.__doc__)
# the deprecated_network decorator will set cs.client.api_version
# after calling the wrapped function
self.assertEqual(cs.api_version, cs.api_version)

View File

@@ -153,7 +153,6 @@ class Client(object):
self.user_id = user_id self.user_id = user_id
self.flavors = flavors.FlavorManager(self) self.flavors = flavors.FlavorManager(self)
self.flavor_access = flavor_access.FlavorAccessManager(self) self.flavor_access = flavor_access.FlavorAccessManager(self)
self.images = images.ImageManager(self)
self.glance = images.GlanceManager(self) self.glance = images.GlanceManager(self)
self.limits = limits.LimitsManager(self) self.limits = limits.LimitsManager(self)
self.servers = servers.ServerManager(self) self.servers = servers.ServerManager(self)

View File

@@ -12,44 +12,25 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""
DEPRECATED: Image interface.
"""
import warnings
from oslo_utils import uuidutils from oslo_utils import uuidutils
from six.moves.urllib import parse
from novaclient import api_versions
from novaclient import base from novaclient import base
from novaclient import exceptions from novaclient import exceptions
from novaclient.i18n import _ from novaclient.i18n import _
class Image(base.Resource): class Image(base.Resource):
"""
DEPRECATED: An image is a collection of files used to create or rebuild a
server.
"""
HUMAN_ID = True HUMAN_ID = True
def __repr__(self): def __repr__(self):
return "<Image: %s>" % self.name return "<Image: %s>" % self.name
def delete(self):
"""
DEPRECATED: Delete this image.
:returns: An instance of novaclient.base.TupleWithMeta
"""
return self.manager.delete(self)
class GlanceManager(base.Manager): class GlanceManager(base.Manager):
"""Use glance directly from service catalog. """Use glance directly from service catalog.
This is used to do name to id lookups for images. Do not use it This is used to do name to id lookups for images and listing images for
the --image-with option to the 'boot' command. Do not use it
for anything else besides that. You have been warned. for anything else besides that. You have been warned.
""" """
@@ -85,110 +66,11 @@ class GlanceManager(base.Manager):
matches[0].append_request_ids(matches.request_ids) matches[0].append_request_ids(matches.request_ids)
return matches[0] return matches[0]
def list(self):
class ImageManager(base.ManagerWithFind):
"""
DEPRECATED: Manage :class:`Image` resources.
"""
resource_class = Image
@api_versions.wraps('2.0', '2.35')
def get(self, image):
""" """
DEPRECATED: Get an image. Get a detailed list of all images.
:param image: The ID of the image to get.
:rtype: :class:`Image`
"""
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
return self._get("/images/%s" % base.getid(image), "image")
def list(self, detailed=True, limit=None, marker=None):
"""
DEPRECATED: Get a list of all images.
:rtype: list of :class:`Image` :rtype: list of :class:`Image`
:param limit: maximum number of images to return.
:param marker: Begin returning images that appear later in the image
list than that represented by this image id (optional).
""" """
# FIXME(mriedem): Should use the api_versions.wraps decorator but that with self.alternate_service_type('image', allowed_types=('image',)):
# breaks the ManagerWithFind.findall method which checks the argspec return self._list('/v2/images', 'images')
# on this function looking for the 'detailed' arg, and it's getting
# tripped up if you use the wraps decorator. This is all deprecated for
# removal anyway so we probably don't care too much about this.
if self.api.api_version > api_versions.APIVersion('2.35'):
raise exceptions.VersionNotFoundForAPIMethod(
self.api.api_version, 'list')
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
params = {}
detail = ''
if detailed:
detail = '/detail'
if limit:
params['limit'] = int(limit)
if marker:
params['marker'] = str(marker)
params = sorted(params.items(), key=lambda x: x[0])
query = '?%s' % parse.urlencode(params) if params else ''
return self._list('/images%s%s' % (detail, query), 'images')
@api_versions.wraps('2.0', '2.35')
def delete(self, image):
"""
DEPRECATED: Delete an image.
It should go without saying that you can't delete an image
that you didn't create.
:param image: The :class:`Image` (or its ID) to delete.
:returns: An instance of novaclient.base.TupleWithMeta
"""
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
return self._delete("/images/%s" % base.getid(image))
@api_versions.wraps('2.0', '2.38')
def set_meta(self, image, metadata):
"""
DEPRECATED: Set an images metadata
:param image: The :class:`Image` to add metadata to
:param metadata: A dict of metadata to add to the image
"""
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
body = {'metadata': metadata}
return self._create("/images/%s/metadata" % base.getid(image),
body, "metadata")
@api_versions.wraps('2.0', '2.38')
def delete_meta(self, image, keys):
"""
DEPRECATED: Delete metadata from an image
:param image: The :class:`Image` to delete metadata
:param keys: A list of metadata keys to delete from the image
:returns: An instance of novaclient.base.TupleWithMeta
"""
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
result = base.TupleWithMeta((), None)
for k in keys:
ret = self._delete("/images/%s/metadata/%s" %
(base.getid(image), k))
result.append_request_ids(ret.request_ids)
return result

View File

@@ -72,10 +72,6 @@ msg_deprecate_net = ('WARNING: Command %s is deprecated and will be removed '
'after Nova 15.0.0 is released. Use python-neutronclient ' 'after Nova 15.0.0 is released. Use python-neutronclient '
'or openstackclient instead.') 'or openstackclient instead.')
msg_deprecate_img = ('WARNING: Command %s is deprecated and will be removed '
'after Nova 15.0.0 is released. Use python-glanceclient '
'or openstackclient instead')
def deprecated_proxy(fn, msg_format): def deprecated_proxy(fn, msg_format):
@functools.wraps(fn) @functools.wraps(fn)
@@ -100,9 +96,6 @@ def deprecated_proxy(fn, msg_format):
deprecated_network = functools.partial(deprecated_proxy, deprecated_network = functools.partial(deprecated_proxy,
msg_format=msg_deprecate_net) msg_format=msg_deprecate_net)
deprecated_image = functools.partial(deprecated_proxy,
msg_format=msg_deprecate_img)
def _key_value_pairing(text): def _key_value_pairing(text):
try: try:
@@ -118,15 +111,21 @@ def _meta_parsing(metadata):
def _match_image(cs, wanted_properties): def _match_image(cs, wanted_properties):
image_list = cs.images.list() image_list = cs.glance.list()
images_matched = [] images_matched = []
match = set(wanted_properties) match = set(wanted_properties)
for img in image_list: for img in image_list:
try: img_dict = {}
if match == match.intersection(set(img.metadata.items())): # exclude any unhashable entries
images_matched.append(img) for key, value in img.to_dict().items():
except AttributeError: try:
pass set([key, value])
except TypeError:
pass
else:
img_dict[key] = value
if match == match.intersection(set(img_dict.items())):
images_matched.append(img)
return images_matched return images_matched
@@ -1350,57 +1349,6 @@ def do_network_create(cs, args):
cs.networks.create(**kwargs) cs.networks.create(**kwargs)
@utils.arg(
'--limit',
dest="limit",
metavar="<limit>",
help=_('Number of images to return per request.'))
@deprecated_image
def do_image_list(cs, _args):
"""Print a list of available images to boot from."""
limit = _args.limit
image_list = cs.images.list(limit=limit)
def parse_server_name(image):
try:
return image.server['id']
except (AttributeError, KeyError):
return ''
fmts = {'Server': parse_server_name}
utils.print_list(image_list, ['ID', 'Name', 'Status', 'Server'],
fmts, sortby_index=1)
@utils.arg(
'image',
metavar='<image>',
help=_("Name or ID of image."))
@utils.arg(
'action',
metavar='<action>',
choices=['set', 'delete'],
help=_("Actions: 'set' or 'delete'."))
@utils.arg(
'metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help=_('Metadata to add/update or delete (only key is necessary on '
'delete).'))
@deprecated_image
def do_image_meta(cs, args):
"""Set or delete metadata on an image."""
image = _find_image(cs, args.image)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.images.set_meta(image, metadata)
elif args.action == 'delete':
cs.images.delete_meta(image, metadata.keys())
def _extract_metadata(args): def _extract_metadata(args):
metadata = {} metadata = {}
for metadatum in args.metadata[0]: for metadatum in args.metadata[0]:
@@ -1449,34 +1397,6 @@ def _print_flavor(flavor):
utils.print_dict(info) utils.print_dict(info)
@utils.arg(
'image',
metavar='<image>',
help=_("Name or ID of image."))
@deprecated_image
def do_image_show(cs, args):
"""Show details about the given image."""
image = _find_image(cs, args.image)
_print_image(image)
@utils.arg(
'image', metavar='<image>', nargs='+',
help=_('Name or ID of image(s).'))
@deprecated_image
def do_image_delete(cs, args):
"""Delete specified image(s)."""
for image in args.image:
try:
# _find_image is using the GlanceManager which doesn't implement
# the delete() method so use the ImagesManager for that.
image = _find_image(cs, image)
cs.images.delete(image)
except Exception as e:
print(_("Delete for image %(image)s failed: %(e)s") %
{'image': image, 'e': e})
@utils.arg( @utils.arg(
'--reservation-id', '--reservation-id',
dest='reservation_id', dest='reservation_id',

View File

@@ -0,0 +1,13 @@
---
prelude: >
Deprecated image commands and python API bindings have been removed.
upgrade:
- |
The following deprecated image commands have been removed::
* nova image-list
* nova image-show
* nova image-meta
* nova image-delete
Along with the related python API bindings in ``novaclient.v2.images``.