Add support for hide old images
Added --hidden argument to list, create and update call. Related to blueprint hidden-images Change-Id: I1f2dcaa545c9da883186b20a96a70c7df994b994
This commit is contained in:
parent
ccbd86ba13
commit
d7fbd0a516
glanceclient
@ -263,7 +263,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'sort_key': ['name', 'id'],
|
'sort_key': ['name', 'id'],
|
||||||
'sort_dir': ['desc', 'asc'],
|
'sort_dir': ['desc', 'asc'],
|
||||||
'sort': None,
|
'sort': None,
|
||||||
'verbose': False
|
'verbose': False,
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
args = self._make_args(input)
|
args = self._make_args(input)
|
||||||
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
||||||
@ -276,7 +277,44 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'member_status': 'Fake',
|
'member_status': 'Fake',
|
||||||
'visibility': True,
|
'visibility': True,
|
||||||
'checksum': 'fake_checksum',
|
'checksum': 'fake_checksum',
|
||||||
'tag': 'fake tag'
|
'tag': 'fake tag',
|
||||||
|
'os_hidden': False
|
||||||
|
}
|
||||||
|
mocked_list.assert_called_once_with(page_size=18,
|
||||||
|
sort_key=['name', 'id'],
|
||||||
|
sort_dir=['desc', 'asc'],
|
||||||
|
filters=exp_img_filters)
|
||||||
|
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
|
||||||
|
|
||||||
|
def test_do_image_list_with_hidden_true(self):
|
||||||
|
input = {
|
||||||
|
'limit': None,
|
||||||
|
'page_size': 18,
|
||||||
|
'visibility': True,
|
||||||
|
'member_status': 'Fake',
|
||||||
|
'owner': 'test',
|
||||||
|
'checksum': 'fake_checksum',
|
||||||
|
'tag': 'fake tag',
|
||||||
|
'properties': [],
|
||||||
|
'sort_key': ['name', 'id'],
|
||||||
|
'sort_dir': ['desc', 'asc'],
|
||||||
|
'sort': None,
|
||||||
|
'verbose': False,
|
||||||
|
'os_hidden': True
|
||||||
|
}
|
||||||
|
args = self._make_args(input)
|
||||||
|
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
||||||
|
mocked_list.return_value = {}
|
||||||
|
|
||||||
|
test_shell.do_image_list(self.gc, args)
|
||||||
|
|
||||||
|
exp_img_filters = {
|
||||||
|
'owner': 'test',
|
||||||
|
'member_status': 'Fake',
|
||||||
|
'visibility': True,
|
||||||
|
'checksum': 'fake_checksum',
|
||||||
|
'tag': 'fake tag',
|
||||||
|
'os_hidden': True
|
||||||
}
|
}
|
||||||
mocked_list.assert_called_once_with(page_size=18,
|
mocked_list.assert_called_once_with(page_size=18,
|
||||||
sort_key=['name', 'id'],
|
sort_key=['name', 'id'],
|
||||||
@ -297,7 +335,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'sort_key': ['name'],
|
'sort_key': ['name'],
|
||||||
'sort_dir': ['desc'],
|
'sort_dir': ['desc'],
|
||||||
'sort': None,
|
'sort': None,
|
||||||
'verbose': False
|
'verbose': False,
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
args = self._make_args(input)
|
args = self._make_args(input)
|
||||||
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
||||||
@ -310,7 +349,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'member_status': 'Fake',
|
'member_status': 'Fake',
|
||||||
'visibility': True,
|
'visibility': True,
|
||||||
'checksum': 'fake_checksum',
|
'checksum': 'fake_checksum',
|
||||||
'tag': 'fake tag'
|
'tag': 'fake tag',
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
mocked_list.assert_called_once_with(page_size=18,
|
mocked_list.assert_called_once_with(page_size=18,
|
||||||
sort_key=['name'],
|
sort_key=['name'],
|
||||||
@ -331,7 +371,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'sort': 'name:desc,size:asc',
|
'sort': 'name:desc,size:asc',
|
||||||
'sort_key': [],
|
'sort_key': [],
|
||||||
'sort_dir': [],
|
'sort_dir': [],
|
||||||
'verbose': False
|
'verbose': False,
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
args = self._make_args(input)
|
args = self._make_args(input)
|
||||||
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
||||||
@ -344,7 +385,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'member_status': 'Fake',
|
'member_status': 'Fake',
|
||||||
'visibility': True,
|
'visibility': True,
|
||||||
'checksum': 'fake_checksum',
|
'checksum': 'fake_checksum',
|
||||||
'tag': 'fake tag'
|
'tag': 'fake tag',
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
mocked_list.assert_called_once_with(
|
mocked_list.assert_called_once_with(
|
||||||
page_size=18,
|
page_size=18,
|
||||||
@ -365,7 +407,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'sort_key': ['name'],
|
'sort_key': ['name'],
|
||||||
'sort_dir': ['desc'],
|
'sort_dir': ['desc'],
|
||||||
'sort': None,
|
'sort': None,
|
||||||
'verbose': False
|
'verbose': False,
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
args = self._make_args(input)
|
args = self._make_args(input)
|
||||||
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
||||||
@ -380,7 +423,8 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'checksum': 'fake_checksum',
|
'checksum': 'fake_checksum',
|
||||||
'tag': 'fake tag',
|
'tag': 'fake tag',
|
||||||
'os_distro': 'NixOS',
|
'os_distro': 'NixOS',
|
||||||
'architecture': 'x86_64'
|
'architecture': 'x86_64',
|
||||||
|
'os_hidden': False
|
||||||
}
|
}
|
||||||
|
|
||||||
mocked_list.assert_called_once_with(page_size=1,
|
mocked_list.assert_called_once_with(page_size=1,
|
||||||
@ -527,6 +571,35 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@mock.patch('sys.stdin', autospec=True)
|
||||||
|
def test_do_image_create_hidden_image(self, mock_stdin):
|
||||||
|
args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'file': None,
|
||||||
|
'os_hidden': True})
|
||||||
|
with mock.patch.object(self.gc.images, 'create') as mocked_create:
|
||||||
|
ignore_fields = ['self', 'access', 'file', 'schema']
|
||||||
|
expect_image = dict([(field, field) for field in ignore_fields])
|
||||||
|
expect_image['id'] = 'pass'
|
||||||
|
expect_image['name'] = 'IMG-01'
|
||||||
|
expect_image['disk_format'] = 'vhd'
|
||||||
|
expect_image['container_format'] = 'bare'
|
||||||
|
expect_image['os_hidden'] = True
|
||||||
|
mocked_create.return_value = expect_image
|
||||||
|
|
||||||
|
# Ensure that the test stdin is not considered
|
||||||
|
# to be supplying image data
|
||||||
|
mock_stdin.isatty = lambda: True
|
||||||
|
test_shell.do_image_create(self.gc, args)
|
||||||
|
|
||||||
|
mocked_create.assert_called_once_with(name='IMG-01',
|
||||||
|
disk_format='vhd',
|
||||||
|
container_format='bare',
|
||||||
|
os_hidden=True)
|
||||||
|
utils.print_dict.assert_called_once_with({
|
||||||
|
'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
|
||||||
|
'container_format': 'bare', 'os_hidden': True})
|
||||||
|
|
||||||
def test_do_image_create_with_file(self):
|
def test_do_image_create_with_file(self):
|
||||||
self.mock_get_data_file.return_value = six.StringIO()
|
self.mock_get_data_file.return_value = six.StringIO()
|
||||||
try:
|
try:
|
||||||
@ -1256,6 +1329,48 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
|
'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
|
||||||
'container_format': 'bare'})
|
'container_format': 'bare'})
|
||||||
|
|
||||||
|
def test_do_image_update_hide_image(self):
|
||||||
|
args = self._make_args({'id': 'pass', 'os_hidden': 'true'})
|
||||||
|
with mock.patch.object(self.gc.images, 'update') as mocked_update:
|
||||||
|
ignore_fields = ['self', 'access', 'file', 'schema']
|
||||||
|
expect_image = dict([(field, field) for field in ignore_fields])
|
||||||
|
expect_image['id'] = 'pass'
|
||||||
|
expect_image['name'] = 'IMG-01'
|
||||||
|
expect_image['disk_format'] = 'vhd'
|
||||||
|
expect_image['container_format'] = 'bare'
|
||||||
|
expect_image['os_hidden'] = True
|
||||||
|
mocked_update.return_value = expect_image
|
||||||
|
|
||||||
|
test_shell.do_image_update(self.gc, args)
|
||||||
|
|
||||||
|
mocked_update.assert_called_once_with('pass',
|
||||||
|
None,
|
||||||
|
os_hidden='true')
|
||||||
|
utils.print_dict.assert_called_once_with({
|
||||||
|
'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
|
||||||
|
'container_format': 'bare', 'os_hidden': True})
|
||||||
|
|
||||||
|
def test_do_image_update_revert_hide_image(self):
|
||||||
|
args = self._make_args({'id': 'pass', 'os_hidden': 'false'})
|
||||||
|
with mock.patch.object(self.gc.images, 'update') as mocked_update:
|
||||||
|
ignore_fields = ['self', 'access', 'file', 'schema']
|
||||||
|
expect_image = dict([(field, field) for field in ignore_fields])
|
||||||
|
expect_image['id'] = 'pass'
|
||||||
|
expect_image['name'] = 'IMG-01'
|
||||||
|
expect_image['disk_format'] = 'vhd'
|
||||||
|
expect_image['container_format'] = 'bare'
|
||||||
|
expect_image['os_hidden'] = False
|
||||||
|
mocked_update.return_value = expect_image
|
||||||
|
|
||||||
|
test_shell.do_image_update(self.gc, args)
|
||||||
|
|
||||||
|
mocked_update.assert_called_once_with('pass',
|
||||||
|
None,
|
||||||
|
os_hidden='false')
|
||||||
|
utils.print_dict.assert_called_once_with({
|
||||||
|
'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
|
||||||
|
'container_format': 'bare', 'os_hidden': False})
|
||||||
|
|
||||||
def test_do_image_update_with_user_props(self):
|
def test_do_image_update_with_user_props(self):
|
||||||
args = self._make_args({'id': 'pass', 'name': 'IMG-01',
|
args = self._make_args({'id': 'pass', 'name': 'IMG-01',
|
||||||
'property': ['myprop=myval']})
|
'property': ['myprop=myval']})
|
||||||
|
@ -213,6 +213,11 @@ _BASE_SCHEMA = {
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, image will not be deletable."
|
"description": "If true, image will not be deletable."
|
||||||
},
|
},
|
||||||
|
"os_hidden": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, image will not appear in default "
|
||||||
|
"image list response."
|
||||||
|
},
|
||||||
"architecture": {
|
"architecture": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": ("Operating system architecture as specified "
|
"description": ("Operating system architecture as specified "
|
||||||
|
@ -13,8 +13,12 @@
|
|||||||
# 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 json
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
from glanceclient._i18n import _
|
from glanceclient._i18n import _
|
||||||
from glanceclient.common import progressbar
|
from glanceclient.common import progressbar
|
||||||
from glanceclient.common import utils
|
from glanceclient.common import utils
|
||||||
@ -25,8 +29,6 @@ from glanceclient.v2 import images
|
|||||||
from glanceclient.v2 import namespace_schema
|
from glanceclient.v2 import namespace_schema
|
||||||
from glanceclient.v2 import resource_type_schema
|
from glanceclient.v2 import resource_type_schema
|
||||||
from glanceclient.v2 import tasks
|
from glanceclient.v2 import tasks
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
MEMBER_STATUS_VALUES = image_members.MEMBER_STATUS_VALUES
|
MEMBER_STATUS_VALUES = image_members.MEMBER_STATUS_VALUES
|
||||||
IMAGE_SCHEMA = None
|
IMAGE_SCHEMA = None
|
||||||
@ -49,8 +51,17 @@ def get_image_schema():
|
|||||||
@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file',
|
@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file',
|
||||||
'checksum', 'virtual_size', 'size',
|
'checksum', 'virtual_size', 'size',
|
||||||
'status', 'schema', 'direct_url',
|
'status', 'schema', 'direct_url',
|
||||||
'locations', 'self',
|
'locations', 'self', 'os_hidden',
|
||||||
'os_hash_value', 'os_hash_algo'])
|
'os_hash_value', 'os_hash_algo'])
|
||||||
|
# NOTE(rosmaita): to make this option more intuitive for end users, we
|
||||||
|
# do not use the Glance image property name 'os_hidden' here. This means
|
||||||
|
# we must include 'os_hidden' in the 'omit' list above and handle the
|
||||||
|
# --hidden argument by hand
|
||||||
|
@utils.arg('--hidden', type=strutils.bool_from_string, metavar='[True|False]',
|
||||||
|
default=None,
|
||||||
|
dest='os_hidden',
|
||||||
|
help=("If true, image will not appear in default image list "
|
||||||
|
"response."))
|
||||||
@utils.arg('--property', metavar="<key=value>", action='append',
|
@utils.arg('--property', metavar="<key=value>", action='append',
|
||||||
default=[], help=_('Arbitrary property to associate with image.'
|
default=[], help=_('Arbitrary property to associate with image.'
|
||||||
' May be used multiple times.'))
|
' May be used multiple times.'))
|
||||||
@ -68,6 +79,7 @@ def do_image_create(gc, args):
|
|||||||
"""Create a new image."""
|
"""Create a new image."""
|
||||||
schema = gc.schemas.get("image")
|
schema = gc.schemas.get("image")
|
||||||
_args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()]
|
_args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()]
|
||||||
|
|
||||||
fields = dict(filter(lambda x: x[1] is not None and
|
fields = dict(filter(lambda x: x[1] is not None and
|
||||||
(x[0] == 'property' or
|
(x[0] == 'property' or
|
||||||
schema.is_core_property(x[0])),
|
schema.is_core_property(x[0])),
|
||||||
@ -108,8 +120,14 @@ def do_image_create(gc, args):
|
|||||||
@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file',
|
@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file',
|
||||||
'checksum', 'virtual_size', 'size',
|
'checksum', 'virtual_size', 'size',
|
||||||
'status', 'schema', 'direct_url',
|
'status', 'schema', 'direct_url',
|
||||||
'locations', 'self',
|
'locations', 'self', 'os_hidden',
|
||||||
'os_hash_value', 'os_hash_algo'])
|
'os_hash_value', 'os_hash_algo'])
|
||||||
|
# NOTE: --hidden requires special handling; see note at do_image_create
|
||||||
|
@utils.arg('--hidden', type=strutils.bool_from_string, metavar='[True|False]',
|
||||||
|
default=None,
|
||||||
|
dest='os_hidden',
|
||||||
|
help=("If true, image will not appear in default image list "
|
||||||
|
"response."))
|
||||||
@utils.arg('--property', metavar="<key=value>", action='append',
|
@utils.arg('--property', metavar="<key=value>", action='append',
|
||||||
default=[], help=_('Arbitrary property to associate with image.'
|
default=[], help=_('Arbitrary property to associate with image.'
|
||||||
' May be used multiple times.'))
|
' May be used multiple times.'))
|
||||||
@ -152,6 +170,7 @@ def do_image_create_via_import(gc, args):
|
|||||||
"""
|
"""
|
||||||
schema = gc.schemas.get("image")
|
schema = gc.schemas.get("image")
|
||||||
_args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()]
|
_args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()]
|
||||||
|
|
||||||
fields = dict(filter(lambda x: x[1] is not None and
|
fields = dict(filter(lambda x: x[1] is not None and
|
||||||
(x[0] == 'property' or
|
(x[0] == 'property' or
|
||||||
schema.is_core_property(x[0])),
|
schema.is_core_property(x[0])),
|
||||||
@ -258,8 +277,14 @@ def _validate_backend(backend, gc):
|
|||||||
'updated_at', 'file', 'checksum',
|
'updated_at', 'file', 'checksum',
|
||||||
'virtual_size', 'size', 'status',
|
'virtual_size', 'size', 'status',
|
||||||
'schema', 'direct_url', 'tags',
|
'schema', 'direct_url', 'tags',
|
||||||
'self', 'os_hash_value',
|
'self', 'os_hidden',
|
||||||
'os_hash_algo'])
|
'os_hash_value', 'os_hash_algo'])
|
||||||
|
# NOTE: --hidden requires special handling; see note at do_image_create
|
||||||
|
@utils.arg('--hidden', type=strutils.bool_from_string, metavar='[True|False]',
|
||||||
|
default=None,
|
||||||
|
dest='os_hidden',
|
||||||
|
help=("If true, image will not appear in default image list "
|
||||||
|
"response."))
|
||||||
@utils.arg('--property', metavar="<key=value>", action='append',
|
@utils.arg('--property', metavar="<key=value>", action='append',
|
||||||
default=[], help=_('Arbitrary property to associate with image.'
|
default=[], help=_('Arbitrary property to associate with image.'
|
||||||
' May be used multiple times.'))
|
' May be used multiple times.'))
|
||||||
@ -269,6 +294,7 @@ def do_image_update(gc, args):
|
|||||||
"""Update an existing image."""
|
"""Update an existing image."""
|
||||||
schema = gc.schemas.get("image")
|
schema = gc.schemas.get("image")
|
||||||
_args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()]
|
_args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()]
|
||||||
|
|
||||||
fields = dict(filter(lambda x: x[1] is not None and
|
fields = dict(filter(lambda x: x[1] is not None and
|
||||||
(x[0] in ['property', 'remove_property'] or
|
(x[0] in ['property', 'remove_property'] or
|
||||||
schema.is_core_property(x[0])),
|
schema.is_core_property(x[0])),
|
||||||
@ -314,10 +340,20 @@ def do_image_update(gc, args):
|
|||||||
help=(_("Comma-separated list of sort keys and directions in the "
|
help=(_("Comma-separated list of sort keys and directions in the "
|
||||||
"form of <key>[:<asc|desc>]. Valid keys: %s. OPTIONAL."
|
"form of <key>[:<asc|desc>]. Valid keys: %s. OPTIONAL."
|
||||||
) % ', '.join(images.SORT_KEY_VALUES)))
|
) % ', '.join(images.SORT_KEY_VALUES)))
|
||||||
|
@utils.arg('--hidden',
|
||||||
|
dest='os_hidden',
|
||||||
|
metavar='[True|False]',
|
||||||
|
default=None,
|
||||||
|
type=strutils.bool_from_string,
|
||||||
|
const=True,
|
||||||
|
nargs='?',
|
||||||
|
help="Filters results by hidden status. Default=None.")
|
||||||
def do_image_list(gc, args):
|
def do_image_list(gc, args):
|
||||||
"""List images you can access."""
|
"""List images you can access."""
|
||||||
filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag']
|
filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag',
|
||||||
|
'os_hidden']
|
||||||
filter_items = [(key, getattr(args, key)) for key in filter_keys]
|
filter_items = [(key, getattr(args, key)) for key in filter_keys]
|
||||||
|
|
||||||
if args.properties:
|
if args.properties:
|
||||||
filter_properties = [prop.split('=', 1) for prop in args.properties]
|
filter_properties = [prop.split('=', 1) for prop in args.properties]
|
||||||
if any(len(pair) != 2 for pair in filter_properties):
|
if any(len(pair) != 2 for pair in filter_properties):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user