Add support for registry API

Depends-On: https://review.openstack.org/#/c/628332/
Change-Id: Ieff4ccfc26bfbc04f1f90838a12660c26720367f
This commit is contained in:
Hongbin Lu 2019-01-14 00:04:58 +00:00
parent ed3d2a3ae3
commit 4d515a43e0
8 changed files with 751 additions and 1 deletions

View File

@ -77,6 +77,11 @@ openstack.container.v1 =
appcontainer_quota_update = zunclient.osc.v1.quotas:UpdateQuota
appcontainer_quota_class_update = zunclient.osc.v1.quota_classes:UpdateQuotaClass
appcontainer_quota_class_get = zunclient.osc.v1.quota_classes:GetQuotaClass
appcontainer_registry_create = zunclient.osc.v1.registries:CreateRegistry
appcontainer_registry_list = zunclient.osc.v1.registries:ListRegistry
appcontainer_registry_show = zunclient.osc.v1.registries:ShowRegistry
appcontainer_registry_update = zunclient.osc.v1.registries:UpdateRegistry
appcontainer_registry_delete = zunclient.osc.v1.registries:DeleteRegistry
[build_sphinx]
source-dir = doc/source

View File

@ -31,7 +31,7 @@ if not LOG.handlers:
HEADER_NAME = "OpenStack-API-Version"
SERVICE_TYPE = "container"
MIN_API_VERSION = '1.1'
MAX_API_VERSION = '1.29'
MAX_API_VERSION = '1.30'
DEFAULT_API_VERSION = '1.latest'
_SUBSTITUTIONS = {}

View File

@ -0,0 +1,232 @@
# 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 osc_lib.command import command
from osc_lib import utils
from oslo_log import log as logging
import six
from zunclient.common import utils as zun_utils
from zunclient import exceptions as exc
from zunclient.i18n import _
def _get_client(obj, parsed_args):
obj.log.debug("take_action(%s)" % parsed_args)
return obj.app.client_manager.container
class CreateRegistry(command.ShowOne):
"""Create a registry"""
log = logging.getLogger(__name__ + ".CreateRegistry")
def get_parser(self, prog_name):
parser = super(CreateRegistry, self).get_parser(prog_name)
parser.add_argument(
'--name',
metavar='<name>',
help='The name of the registry.')
parser.add_argument(
'--username',
metavar='<username>',
help='The username to login to the registry.')
parser.add_argument(
'--password',
metavar='<password>',
help='The password to login to the registry.')
parser.add_argument(
'--domain',
metavar='<domain>',
required=True,
help='The domain of the registry.')
return parser
def take_action(self, parsed_args):
client = _get_client(self, parsed_args)
opts = {}
opts['name'] = parsed_args.name
opts['domain'] = parsed_args.domain
opts['username'] = parsed_args.username
opts['password'] = parsed_args.password
opts = zun_utils.remove_null_parms(**opts)
registry = client.registries.create(**opts)
return zip(*sorted(six.iteritems(registry._info['registry'])))
class ShowRegistry(command.ShowOne):
"""Show a registry"""
log = logging.getLogger(__name__ + ".ShowRegistry")
def get_parser(self, prog_name):
parser = super(ShowRegistry, self).get_parser(prog_name)
parser.add_argument(
'registry',
metavar='<registry>',
help='ID or name of the registry to show.')
return parser
def take_action(self, parsed_args):
client = _get_client(self, parsed_args)
opts = {}
opts['id'] = parsed_args.registry
opts = zun_utils.remove_null_parms(**opts)
registry = client.registries.get(**opts)
return zip(*sorted(six.iteritems(registry._info['registry'])))
class ListRegistry(command.Lister):
"""List available registries"""
log = logging.getLogger(__name__ + ".ListRegistrys")
def get_parser(self, prog_name):
parser = super(ListRegistry, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action="store_true",
default=False,
help='List registries in all projects')
parser.add_argument(
'--marker',
metavar='<marker>',
help='The last registry UUID of the previous page; '
'displays list of registries after "marker".')
parser.add_argument(
'--limit',
metavar='<limit>',
type=int,
help='Maximum number of registries to return')
parser.add_argument(
'--sort-key',
metavar='<sort-key>',
help='Column to sort results by')
parser.add_argument(
'--sort-dir',
metavar='<sort-dir>',
choices=['desc', 'asc'],
help='Direction to sort. "asc" or "desc".')
parser.add_argument(
'--name',
metavar='<name>',
help='List registries according to their name.')
parser.add_argument(
'--domain',
metavar='<domain>',
help='List registries according to their domain.')
parser.add_argument(
'--project-id',
metavar='<project-id>',
help='List registries according to their project_id')
parser.add_argument(
'--user-id',
metavar='<user-id>',
help='List registries according to their user_id')
parser.add_argument(
'--username',
metavar='<username>',
help='List registries according to their username')
return parser
def take_action(self, parsed_args):
client = _get_client(self, parsed_args)
opts = {}
opts['all_projects'] = parsed_args.all_projects
opts['marker'] = parsed_args.marker
opts['limit'] = parsed_args.limit
opts['sort_key'] = parsed_args.sort_key
opts['sort_dir'] = parsed_args.sort_dir
opts['domain'] = parsed_args.domain
opts['name'] = parsed_args.name
opts['project_id'] = parsed_args.project_id
opts['user_id'] = parsed_args.user_id
opts['username'] = parsed_args.username
opts = zun_utils.remove_null_parms(**opts)
registries = client.registries.list(**opts)
columns = ('uuid', 'name', 'domain', 'username', 'password')
return (columns, (utils.get_item_properties(registry, columns)
for registry in registries))
class DeleteRegistry(command.Command):
"""Delete specified registry(s)"""
log = logging.getLogger(__name__ + ".Deleteregistry")
def get_parser(self, prog_name):
parser = super(DeleteRegistry, self).get_parser(prog_name)
parser.add_argument(
'registry',
metavar='<registry>',
nargs='+',
help='ID or name of the registry(s) to delete.')
return parser
def take_action(self, parsed_args):
client = _get_client(self, parsed_args)
registries = parsed_args.registry
for registry in registries:
opts = {}
opts['id'] = registry
opts = zun_utils.remove_null_parms(**opts)
try:
client.registries.delete(**opts)
print(_('Request to delete registry %s has been accepted.')
% registry)
except Exception as e:
print("Delete for registry %(registry)s failed: %(e)s" %
{'registry': registry, 'e': e})
class UpdateRegistry(command.ShowOne):
"""Update one or more attributes of the registry"""
log = logging.getLogger(__name__ + ".UpdateRegistry")
def get_parser(self, prog_name):
parser = super(UpdateRegistry, self).get_parser(prog_name)
parser.add_argument(
'registry',
metavar='<registry>',
help="ID or name of the registry to update.")
parser.add_argument(
'--username',
metavar='<username>',
help='The new username of registry to update.')
parser.add_argument(
'--password',
metavar='<password>',
help='The new password of registry to update.')
parser.add_argument(
'--name',
metavar='<name>',
help='The new name of registry to update.')
parser.add_argument(
'--domain',
metavar='<domain>',
help='The new domain of registry to update.')
return parser
def take_action(self, parsed_args):
client = _get_client(self, parsed_args)
registry = parsed_args.registry
opts = {}
opts['username'] = parsed_args.username
opts['password'] = parsed_args.password
opts['domain'] = parsed_args.domain
opts['name'] = parsed_args.name
opts = zun_utils.remove_null_parms(**opts)
if not opts:
raise exc.CommandError("You must update at least one property")
registry = client.registries.update(registry, **opts)
return zip(*sorted(six.iteritems(registry._info['registry'])))

View File

@ -0,0 +1,233 @@
# 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 copy
import testtools
from testtools import matchers
from zunclient import exceptions
from zunclient.tests.unit import utils
from zunclient.v1 import registries
REGISTRY1 = {'uuid': 'cb9d9848-6c12-411b-a915-6eadc7bd2871',
'name': 'test1',
'domain': 'testdomain.io',
'username': 'testuser1',
'password': 'testpassword1',
}
REGISTRY2 = {'uuid': 'c2d923ef-e9b5-4e6b-a8aa-7c54a715b370',
'name': 'test2',
'domain': 'otherdomain.io',
'username': 'testuser2',
'password': 'testpassword2',
}
CREATE_REGISTRY1 = copy.deepcopy(REGISTRY1)
del CREATE_REGISTRY1['uuid']
UPDATE_REGISTRY1 = copy.deepcopy(REGISTRY1)
del UPDATE_REGISTRY1['uuid']
UPDATE_REGISTRY1['name'] = 'newname'
fake_responses = {
'/v1/registries':
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
'POST': (
{},
{'registry': CREATE_REGISTRY1},
),
},
'/v1/registries/?limit=2':
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
},
'/v1/registries/?marker=%s' % REGISTRY2['uuid']:
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
},
'/v1/registries/?limit=2&marker=%s' % REGISTRY2['uuid']:
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
},
'/v1/registries/?sort_dir=asc':
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
},
'/v1/registries/?sort_key=uuid':
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
},
'/v1/registries/?sort_key=uuid&sort_dir=desc':
{
'GET': (
{},
{'registries': [REGISTRY1, REGISTRY2]},
),
},
'/v1/registries/%s' % REGISTRY1['uuid']:
{
'GET': (
{},
{'registry': REGISTRY1}
),
'DELETE': (
{},
None,
),
'PATCH': (
{},
{'registry': UPDATE_REGISTRY1},
),
},
}
class RegistryManagerTest(testtools.TestCase):
def setUp(self):
super(RegistryManagerTest, self).setUp()
self.api = utils.FakeAPI(fake_responses)
self.mgr = registries.RegistryManager(self.api)
def test_registry_create(self):
registries = self.mgr.create(**CREATE_REGISTRY1)
expect = [
('POST', '/v1/registries', {}, {'registry': CREATE_REGISTRY1})
]
self.assertEqual(expect, self.api.calls)
self.assertTrue(registries)
def test_registry_create_fail(self):
create_registry_fail = copy.deepcopy(CREATE_REGISTRY1)
create_registry_fail["wrong_key"] = "wrong"
self.assertRaisesRegex(exceptions.InvalidAttribute,
("Key must be in %s" %
','.join(registries.CREATION_ATTRIBUTES)),
self.mgr.create, **create_registry_fail)
self.assertEqual([], self.api.calls)
def test_registries_list(self):
registries = self.mgr.list()
expect = [
('GET', '/v1/registries', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertThat(registries, matchers.HasLength(2))
def _test_registries_list_with_filters(self, limit=None, marker=None,
sort_key=None, sort_dir=None,
expect=[]):
registries_filter = self.mgr.list(limit=limit, marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
self.assertEqual(expect, self.api.calls)
self.assertThat(registries_filter, matchers.HasLength(2))
def test_registries_list_with_limit(self):
expect = [
('GET', '/v1/registries/?limit=2', {}, None),
]
self._test_registries_list_with_filters(
limit=2,
expect=expect)
def test_registries_list_with_marker(self):
expect = [
('GET', '/v1/registries/?marker=%s' % REGISTRY2['uuid'],
{}, None),
]
self._test_registries_list_with_filters(
marker=REGISTRY2['uuid'],
expect=expect)
def test_registries_list_with_marker_limit(self):
expect = [
('GET', '/v1/registries/?limit=2&marker=%s' % REGISTRY2['uuid'],
{}, None),
]
self._test_registries_list_with_filters(
limit=2, marker=REGISTRY2['uuid'],
expect=expect)
def test_coontainer_list_with_sort_dir(self):
expect = [
('GET', '/v1/registries/?sort_dir=asc', {}, None),
]
self._test_registries_list_with_filters(
sort_dir='asc',
expect=expect)
def test_registry_list_with_sort_key(self):
expect = [
('GET', '/v1/registries/?sort_key=uuid', {}, None),
]
self._test_registries_list_with_filters(
sort_key='uuid',
expect=expect)
def test_registry_list_with_sort_key_dir(self):
expect = [
('GET', '/v1/registries/?sort_key=uuid&sort_dir=desc', {}, None),
]
self._test_registries_list_with_filters(
sort_key='uuid', sort_dir='desc',
expect=expect)
def test_registry_show(self):
registry = self.mgr.get(REGISTRY1['uuid'])
expect = [
('GET', '/v1/registries/%s' % REGISTRY1['uuid'], {}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(REGISTRY1['name'], registry._info['registry']['name'])
self.assertEqual(REGISTRY1['uuid'], registry._info['registry']['uuid'])
def test_registry_update(self):
registry = self.mgr.update(REGISTRY1['uuid'], **UPDATE_REGISTRY1)
expect = [
('PATCH', '/v1/registries/%s' % REGISTRY1['uuid'],
{},
{'registry': UPDATE_REGISTRY1})
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(UPDATE_REGISTRY1['name'],
registry._info['registry']['name'])
def test_registries_delete(self):
registries = self.mgr.delete(REGISTRY1['uuid'])
expect = [
('DELETE', '/v1/registries/%s' % (REGISTRY1['uuid']),
{}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertIsNone(registries)

View File

@ -25,6 +25,7 @@ from zunclient.v1 import hosts
from zunclient.v1 import images
from zunclient.v1 import quota_classes
from zunclient.v1 import quotas
from zunclient.v1 import registries
from zunclient.v1 import services
from zunclient.v1 import versions
@ -139,6 +140,7 @@ class Client(object):
self.actions = actions.ActionManager(self.http_client)
self.quotas = quotas.QuotaManager(self.http_client)
self.quota_classes = quota_classes.QuotaClassManager(self.http_client)
self.registries = registries.RegistryManager(self.http_client)
@property
def api_version(self):

102
zunclient/v1/registries.py Normal file
View File

@ -0,0 +1,102 @@
# 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 zunclient.common import base
from zunclient.common import utils
from zunclient import exceptions
CREATION_ATTRIBUTES = ['name', 'domain', 'username', 'password']
class Registry(base.Resource):
def __repr__(self):
return "<Registry %s>" % self._info
class RegistryManager(base.Manager):
resource_class = Registry
@staticmethod
def _path(id=None):
if id:
return '/v1/registries/%s' % id
else:
return '/v1/registries'
def list(self, marker=None, limit=None, sort_key=None,
sort_dir=None, all_projects=False, **kwargs):
"""Retrieve a list of registries.
:param all_projects: Optional, list registries in all projects
:param marker: Optional, the UUID of a registries, eg the last
registries from a previous result set. Return
the next result set.
:param limit: The maximum number of results to return per
request, if:
1) limit > 0, the maximum number of registries to return.
2) limit param is NOT specified (None), the number of items
returned respect the maximum imposed by the ZUN API
(see Zun's api.max_limit option).
:param sort_key: Optional, field used for sorting.
:param sort_dir: Optional, direction of sorting, either 'asc' (the
default) or 'desc'.
:returns: A list of registries.
"""
if limit is not None:
limit = int(limit)
filters = utils.common_filters(marker, limit, sort_key,
sort_dir, all_projects)
path = ''
if filters:
path += '?' + '&'.join(filters)
if limit is None:
return self._list(self._path(path),
"registries", qparams=kwargs)
else:
return self._list_pagination(self._path(path),
"registries",
limit=limit)
def get(self, id, **kwargs):
try:
return self._list(self._path(id),
qparams=kwargs)[0]
except IndexError:
return None
def create(self, **kwargs):
new = {'registry': {}}
for (key, value) in kwargs.items():
if key in CREATION_ATTRIBUTES:
new['registry'][key] = value
else:
raise exceptions.InvalidAttribute(
"Key must be in %s" % ','.join(CREATION_ATTRIBUTES))
return self._create(self._path(), new)
def delete(self, id, **kwargs):
return self._delete(self._path(id),
qparams=kwargs)
def update(self, id, **patch):
kwargs = {'registry': patch}
return self._update(self._path(id), kwargs)

View File

@ -0,0 +1,174 @@
# 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 json
import yaml
from zunclient.common import cliutils as utils
from zunclient.common import utils as zun_utils
from zunclient import exceptions as exc
def _show_registry(registry):
utils.print_dict(registry._info['registry'])
@utils.arg('--name',
metavar='<name>',
help='The name of the registry.')
@utils.arg('--username',
metavar='<username>',
help='The username to login to the registry.')
@utils.arg('--password',
metavar='<password>',
help='The password to login to the registry.')
@utils.arg('--domain',
metavar='<domain>',
required=True,
help='The domain of the registry.')
def do_registry_create(cs, args):
"""Create a registry."""
opts = {}
opts['name'] = args.name
opts['domain'] = args.domain
opts['username'] = args.username
opts['password'] = args.password
opts = zun_utils.remove_null_parms(**opts)
_show_registry(cs.registries.create(**opts))
@utils.arg('--all-projects',
action="store_true",
default=False,
help='List registries in all projects')
@utils.arg('--marker',
metavar='<marker>',
default=None,
help='The last registry UUID of the previous page; '
'displays list of registries after "marker".')
@utils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of registries to return')
@utils.arg('--sort-key',
metavar='<sort-key>',
help='Column to sort results by')
@utils.arg('--sort-dir',
metavar='<sort-dir>',
choices=['desc', 'asc'],
help='Direction to sort. "asc" or "desc".')
@utils.arg('--name',
metavar='<name>',
help='List registries according to their name.')
@utils.arg('--domain',
metavar='<domain>',
help='List registries according to their domain.')
@utils.arg('--project-id',
metavar='<project-id>',
help='List registries according to their Project_id')
@utils.arg('--user-id',
metavar='<user-id>',
help='List registries according to their user_id')
@utils.arg('--username',
metavar='<username>',
help='List registries according to their username')
def do_registry_list(cs, args):
"""Print a list of available registries."""
opts = {}
opts['all_projects'] = args.all_projects
opts['marker'] = args.marker
opts['limit'] = args.limit
opts['sort_key'] = args.sort_key
opts['sort_dir'] = args.sort_dir
opts['domain'] = args.domain
opts['name'] = args.name
opts['project_id'] = args.project_id
opts['user_id'] = args.user_id
opts['username'] = args.username
opts = zun_utils.remove_null_parms(**opts)
registries = cs.registries.list(**opts)
columns = ('uuid', 'name', 'domain', 'username', 'password')
utils.print_list(registries, columns,
sortby_index=None)
@utils.arg('registries',
metavar='<registry>',
nargs='+',
help='ID or name of the (registry)s to delete.')
def do_registry_delete(cs, args):
"""Delete specified registries."""
for registry in args.registries:
opts = {}
opts['id'] = registry
opts = zun_utils.remove_null_parms(**opts)
try:
cs.registries.delete(**opts)
print("Request to delete registry %s has been accepted." %
registry)
except Exception as e:
print("Delete for registry %(registry)s failed: %(e)s" %
{'registry': registry, 'e': e})
@utils.arg('registry',
metavar='<registry>',
help='ID or name of the registry to show.')
@utils.arg('-f', '--format',
metavar='<format>',
action='store',
choices=['json', 'yaml', 'table'],
default='table',
help='Print representation of the container.'
'The choices of the output format is json,table,yaml.'
'Defaults to table.')
def do_registry_show(cs, args):
"""Show details of a registry."""
opts = {}
opts['id'] = args.registry
opts = zun_utils.remove_null_parms(**opts)
registry = cs.registries.get(**opts)
if args.format == 'json':
print(json.dumps(registry._info, indent=4, sort_keys=True))
elif args.format == 'yaml':
print(yaml.safe_dump(registry._info, default_flow_style=False))
elif args.format == 'table':
_show_registry(registry)
@utils.arg('registry',
metavar='<registry>',
help="ID or name of the registry to update.")
@utils.arg('--username',
metavar='<username>',
help='The username login to the registry.')
@utils.arg('--password',
metavar='<password>',
help='The domain login to the registry.')
@utils.arg('--domain',
metavar='<domain>',
help='The domain of the registry.')
@utils.arg('--name',
metavar='<name>',
help='The new name for the registry')
def do_registry_update(cs, args):
"""Update one or more attributes of the registry."""
opts = {}
opts['username'] = args.username
opts['password'] = args.password
opts['domain'] = args.domain
opts['name'] = args.name
opts = zun_utils.remove_null_parms(**opts)
if not opts:
raise exc.CommandError("You must update at least one property")
registry = cs.registries.update(args.registry, **opts)
_show_registry(registry)

View File

@ -21,6 +21,7 @@ from zunclient.v1 import hosts_shell
from zunclient.v1 import images_shell
from zunclient.v1 import quota_classes_shell
from zunclient.v1 import quotas_shell
from zunclient.v1 import registries_shell
from zunclient.v1 import services_shell
from zunclient.v1 import versions_shell
@ -35,4 +36,5 @@ COMMAND_MODULES = [
actions_shell,
quotas_shell,
quota_classes_shell,
registries_shell,
]