Identity: Migrate 'project' commands to SDK

Change-Id: I0f673658bc02423c18af82fe52ed9f0587763882
Signed-off-by: 0weng <oweng@osuosl.org>
This commit is contained in:
0weng
2025-10-29 10:50:45 -07:00
parent 911e643f2c
commit 85e731df47
5 changed files with 1047 additions and 635 deletions

View File

@@ -256,6 +256,37 @@ def find_project(identity_client, name_or_id, domain_name_or_id=None):
) )
def find_project_id_sdk(
identity_client,
name_or_id,
domain_name_or_id=None,
*,
validate_actor_existence=True,
validate_domain_actor_existence=None,
):
if domain_name_or_id is None:
return _find_sdk_id(
identity_client.find_project,
name_or_id=name_or_id,
validate_actor_existence=validate_actor_existence,
)
if validate_domain_actor_existence is None:
validate_domain_actor_existence = validate_actor_existence
domain_id = find_domain_id_sdk(
identity_client,
name_or_id=domain_name_or_id,
validate_actor_existence=validate_domain_actor_existence,
)
return _find_sdk_id(
identity_client.find_project,
name_or_id=name_or_id,
validate_actor_existence=validate_actor_existence,
domain_id=domain_id,
)
def find_user(identity_client, name_or_id, domain_name_or_id=None): def find_user(identity_client, name_or_id, domain_name_or_id=None):
if domain_name_or_id is None: if domain_name_or_id is None:
return _find_identity_resource( return _find_identity_resource(

View File

@@ -17,7 +17,7 @@
import logging import logging
from keystoneauth1 import exceptions as ks_exc from openstack import exceptions as sdk_exc
from osc_lib.cli import parseractions from osc_lib.cli import parseractions
from osc_lib import exceptions from osc_lib import exceptions
from osc_lib import utils from osc_lib import utils
@@ -30,6 +30,21 @@ from openstackclient.identity.v3 import tag
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def _format_project(project):
# NOTE(0weng): Projects allow unknown attributes in the body, so extract
# the column names separately.
(column_headers, columns) = utils.get_osc_show_columns_for_sdk_resource(
project,
{'is_enabled': 'enabled'},
['links', 'location', 'parents_as_ids', 'subtree_as_ids'],
)
return (
column_headers,
utils.get_item_properties(project, columns),
)
class CreateProject(command.ShowOne): class CreateProject(command.ShowOne):
_description = _("Create new project") _description = _("Create new project")
@@ -90,22 +105,13 @@ class CreateProject(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.sdk_connection.identity
domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client, parsed_args.domain).id
parent = None
if parsed_args.parent:
parent = utils.find_resource(
identity_client.projects,
parsed_args.parent,
).id
kwargs = {} kwargs = {}
if parsed_args.properties: if parsed_args.properties:
kwargs = parsed_args.properties.copy() kwargs = parsed_args.properties.copy()
if 'is_domain' in kwargs.keys(): if 'is_domain' in kwargs.keys():
if kwargs['is_domain'].lower() == "true": if kwargs['is_domain'].lower() == "true":
kwargs['is_domain'] = True kwargs['is_domain'] = True
@@ -114,35 +120,54 @@ class CreateProject(command.ShowOne):
elif kwargs['is_domain'].lower() == "none": elif kwargs['is_domain'].lower() == "none":
kwargs['is_domain'] = None kwargs['is_domain'] = None
kwargs['tags'] = list(set(parsed_args.tags)) if parsed_args.description:
kwargs['description'] = parsed_args.description
if parsed_args.name:
kwargs['name'] = parsed_args.name
domain = None
if parsed_args.domain:
domain = common.find_domain_id_sdk(
identity_client, parsed_args.domain
)
kwargs['domain_id'] = domain
if parsed_args.parent:
kwargs['parent_id'] = common.find_project_id_sdk(
identity_client,
parsed_args.parent,
)
kwargs['is_enabled'] = parsed_args.enabled
if parsed_args.tags:
kwargs['tags'] = list(set(parsed_args.tags))
options = {}
if parsed_args.immutable is not None: if parsed_args.immutable is not None:
options['immutable'] = parsed_args.immutable kwargs['options'] = {'immutable': parsed_args.immutable}
try: try:
project = identity_client.projects.create( project = identity_client.create_project(
name=parsed_args.name,
domain=domain,
parent=parent,
description=parsed_args.description,
enabled=parsed_args.enabled,
options=options,
**kwargs, **kwargs,
) )
except ks_exc.Conflict: except sdk_exc.ConflictException:
if parsed_args.or_show: if parsed_args.or_show:
project = utils.find_resource( if parsed_args.domain:
identity_client.projects, project = identity_client.find_project(
parsed_args.name, parsed_args.name,
domain_id=domain, domain_id=domain,
) ignore_missing=False,
)
else:
project = identity_client.find_project(
parsed_args.name, ignore_missing=False
)
LOG.info(_('Returning existing project %s'), project.name) LOG.info(_('Returning existing project %s'), project.name)
else: else:
raise raise
project._info.pop('links') return _format_project(project)
return zip(*sorted(project._info.items()))
class DeleteProject(command.Command): class DeleteProject(command.Command):
@@ -171,23 +196,19 @@ class DeleteProject(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.sdk_connection.identity
domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client, parsed_args.domain)
errors = 0 errors = 0
for project in parsed_args.projects: for project in parsed_args.projects:
try: try:
if domain is not None: project = common.find_project_id_sdk(
project_obj = utils.find_resource( identity_client,
identity_client.projects, project, domain_id=domain.id project,
) domain_name_or_id=parsed_args.domain,
else: validate_actor_existence=True,
project_obj = utils.find_resource( validate_domain_actor_existence=False,
identity_client.projects, project )
) identity_client.delete_project(project)
identity_client.projects.delete(project_obj.id)
except Exception as e: except Exception as e:
errors += 1 errors += 1
LOG.error( LOG.error(
@@ -268,38 +289,44 @@ class ListProject(command.Lister):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.sdk_connection.identity
columns: tuple[str, ...] = ('ID', 'Name')
column_headers: tuple[str, ...] = ('ID', 'Name')
if parsed_args.long: if parsed_args.long:
columns += ('Domain ID', 'Description', 'Enabled') column_headers += ('Domain ID', 'Description', 'Enabled')
columns: tuple[str, ...] = ('id', 'name')
if parsed_args.long:
columns += ('domain_id', 'description', 'is_enabled')
kwargs = {} kwargs = {}
domain_id = None domain_id = None
if parsed_args.domain: if parsed_args.domain:
domain_id = common.find_domain( domain_id = common.find_domain_id_sdk(
identity_client, parsed_args.domain identity_client, parsed_args.domain
).id )
kwargs['domain'] = domain_id kwargs['domain_id'] = domain_id
if parsed_args.parent: if parsed_args.parent:
parent_id = common.find_project( parent_id = common.find_project_id_sdk(
identity_client, parsed_args.parent identity_client, parsed_args.parent
).id )
kwargs['parent'] = parent_id kwargs['parent_id'] = parent_id
user = None
if parsed_args.user: if parsed_args.user:
if parsed_args.domain: if parsed_args.domain:
user_id = utils.find_resource( user = common.find_user_id_sdk(
identity_client.users, identity_client,
parsed_args.user, parsed_args.user,
domain_id=domain_id, domain_name_or_id=domain_id,
).id )
else: else:
user_id = utils.find_resource( user = common.find_user_id_sdk(
identity_client.users, parsed_args.user identity_client,
).id parsed_args.user,
)
kwargs['user'] = user_id
if parsed_args.is_enabled is not None: if parsed_args.is_enabled is not None:
kwargs['is_enabled'] = parsed_args.is_enabled kwargs['is_enabled'] = parsed_args.is_enabled
@@ -308,32 +335,29 @@ class ListProject(command.Lister):
if parsed_args.my_projects: if parsed_args.my_projects:
# NOTE(adriant): my-projects supersedes all the other filters. # NOTE(adriant): my-projects supersedes all the other filters.
kwargs = {'user': self.app.client_manager.auth_ref.user_id} kwargs = {}
user = self.app.client_manager.auth_ref.user_id
try: if user:
data = identity_client.projects.list(**kwargs) data = identity_client.user_projects(user, **kwargs)
except ks_exc.Forbidden: else:
# NOTE(adriant): if no filters, assume a forbidden is non-admin try:
# wanting their own project list. data = identity_client.projects(**kwargs)
if not kwargs: except sdk_exc.ForbiddenException:
user = self.app.client_manager.auth_ref.user_id # NOTE(adriant): if no filters, assume a forbidden is non-admin
data = identity_client.projects.list(user=user) # wanting their own project list.
else: if not kwargs:
raise user = self.app.client_manager.auth_ref.user_id
data = identity_client.user_projects(user)
else:
raise
if parsed_args.sort: if parsed_args.sort:
data = utils.sort_items(data, parsed_args.sort) data = utils.sort_items(data, parsed_args.sort)
return ( return (
columns, column_headers,
( (utils.get_item_properties(s, columns) for s in data),
utils.get_item_properties(
s,
columns,
formatters={},
)
for s in data
),
) )
@@ -392,11 +416,7 @@ class SetProject(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.sdk_connection.identity
project = common.find_project(
identity_client, parsed_args.project, parsed_args.domain
)
kwargs = {} kwargs = {}
if parsed_args.name: if parsed_args.name:
@@ -409,9 +429,50 @@ class SetProject(command.Command):
kwargs['options'] = {'immutable': parsed_args.immutable} kwargs['options'] = {'immutable': parsed_args.immutable}
if parsed_args.properties: if parsed_args.properties:
kwargs.update(parsed_args.properties) kwargs.update(parsed_args.properties)
tag.update_tags_in_args(parsed_args, project, kwargs)
identity_client.projects.update(project.id, **kwargs) if parsed_args.domain:
domain = common.find_domain_id_sdk(
identity_client,
parsed_args.domain,
validate_actor_existence=False,
)
project = identity_client.find_project(
parsed_args.project,
domain_id=domain,
ignore_missing=True,
)
else:
project = identity_client.find_project(
parsed_args.project,
ignore_missing=True,
)
if (
parsed_args.tags
or parsed_args.remove_tags
or parsed_args.clear_tags
):
existing_tags = []
if project:
existing_tags = project.tags
if parsed_args.clear_tags:
kwargs['tags'] = []
else:
existing_tags_set = set(existing_tags)
if parsed_args.remove_tags:
tags = sorted(
existing_tags_set - set(parsed_args.remove_tags)
)
if parsed_args.tags:
tags = sorted(
existing_tags_set.union(set(parsed_args.tags))
)
kwargs['tags'] = tags
project_id = project.id if project else parsed_args.project
identity_client.update_project(project_id, **kwargs)
class ShowProject(command.ShowOne): class ShowProject(command.ShowOne):
@@ -444,31 +505,36 @@ class ShowProject(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.sdk_connection.identity
project_str = common._get_token_resource( kwargs = {}
identity_client, 'project', parsed_args.project, parsed_args.domain
domain = None
if parsed_args.domain:
domain = common.find_domain_id_sdk(
identity_client, parsed_args.domain
)
kwargs['domain_id'] = domain
# Get project id first; otherwise, find_project() can't find
# parents/children if only project name was given
project = common.find_project_id_sdk(
identity_client,
parsed_args.project,
domain_name_or_id=domain,
validate_actor_existence=False,
validate_domain_actor_existence=False,
) )
if parsed_args.domain: # Include these options as query parameters if they are provided
domain = common.find_domain(identity_client, parsed_args.domain) if parsed_args.parents:
project = utils.find_resource( kwargs['parents_as_ids'] = True
identity_client.projects, project_str, domain_id=domain.id if parsed_args.children:
) kwargs['subtree_as_ids'] = True
else:
project = utils.find_resource(
identity_client.projects, project_str
)
if parsed_args.parents or parsed_args.children: project = identity_client.find_project(
# NOTE(RuiChen): utils.find_resource() can't pass kwargs, project, **kwargs, ignore_missing=False
# if id query hit the result at first, so call )
# identity manager.get() with kwargs directly.
project = identity_client.projects.get(
project.id,
parents_as_ids=parsed_args.parents,
subtree_as_ids=parsed_args.children,
)
project._info.pop('links') return _format_project(project)
return zip(*sorted(project._info.items()))

View File

@@ -123,14 +123,3 @@ def add_tag_option_to_parser_for_set(parser, resource_name):
) )
% resource_name, % resource_name,
) )
def update_tags_in_args(parsed_args, obj, args):
if parsed_args.clear_tags:
args['tags'] = []
obj.tags = []
if parsed_args.remove_tags:
args['tags'] = sorted(set(obj.tags) - set(parsed_args.remove_tags))
return
if parsed_args.tags:
args['tags'] = sorted(set(obj.tags).union(set(parsed_args.tags)))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
---
features:
- |
Migrate ``project`` commands from keystoneclient to SDK.
upgrade:
- |
Filtering in ``project`` commands is now case sensitive.