summaryrefslogtreecommitdiff
path: root/openstackclient/common
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient/common')
-rw-r--r--openstackclient/common/availability_zone.py2
-rw-r--r--openstackclient/common/clientmanager.py80
-rw-r--r--openstackclient/common/configuration.py2
-rw-r--r--openstackclient/common/extension.py2
-rw-r--r--openstackclient/common/limits.py2
-rw-r--r--openstackclient/common/module.py8
-rw-r--r--openstackclient/common/project_cleanup.py18
-rw-r--r--openstackclient/common/quota.py77
-rw-r--r--openstackclient/common/versions.py3
9 files changed, 133 insertions, 61 deletions
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py
index 61f77cc..6f5e4fd 100644
--- a/openstackclient/common/availability_zone.py
+++ b/openstackclient/common/availability_zone.py
@@ -17,9 +17,9 @@ import copy
import logging
from openstack import exceptions as sdk_exceptions
-from osc_lib.command import command
from osc_lib import utils
+from openstackclient import command
from openstackclient.i18n import _
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index a0455bf..51911aa 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -15,15 +15,25 @@
"""Manage access to the clients, including authenticating when needed."""
+import argparse
+from collections.abc import Callable
import importlib
import logging
import sys
import typing as ty
+from osc_lib.cli import client_config
from osc_lib import clientmanager
from osc_lib import shell
import stevedore
+if ty.TYPE_CHECKING:
+ from keystoneauth1 import access as ksa_access
+ from openstack.compute.v2 import _proxy as compute_proxy
+ from openstack.image.v2 import _proxy as image_proxy
+ from openstack.network.v2 import _proxy as network_proxy
+
+ from openstackclient.api import object_store_v1
LOG = logging.getLogger(__name__)
@@ -40,11 +50,23 @@ class ClientManager(clientmanager.ClientManager):
in osc-lib so we need to maintain a transition period.
"""
- # A simple incrementing version for the plugin to know what is available
- PLUGIN_INTERFACE_VERSION = "2"
-
- # Let the commands set this
- _auth_required = False
+ if ty.TYPE_CHECKING:
+ # we know this will be set by us and will not be nullable
+ auth_ref: ksa_access.AccessInfo
+
+ # this is a hack to keep mypy happy: the actual attributes are set in
+ # get_plugin_modules below
+ # TODO(stephenfin): Change the types of identity and volume once we've
+ # migrated everything to SDK. Hopefully by then we'll have figured out
+ # how to statically distinguish between the v2 and v3 versions of both
+ # services...
+ # TODO(stephenfin): We also need to migrate object storage...
+ compute: compute_proxy.Proxy
+ identity: ty.Any
+ image: image_proxy.Proxy
+ network: network_proxy.Proxy
+ object_store: object_store_v1.APIv1
+ volume: ty.Any
def __init__(
self,
@@ -81,6 +103,12 @@ class ClientManager(clientmanager.ClientManager):
self._auth_required
and self._cli_options._openstack_config is not None
):
+ if not isinstance(
+ self._cli_options._openstack_config, client_config.OSC_Config
+ ):
+ # programmer error
+ raise TypeError('unexpected type for _openstack_config')
+
self._cli_options._openstack_config._pw_callback = (
shell.prompt_for_password
)
@@ -107,6 +135,13 @@ class ClientManager(clientmanager.ClientManager):
self._cli_options.config['auth_type'] = self._original_auth_type
del self._cli_options.config['auth']['token']
del self._cli_options.config['auth']['endpoint']
+
+ if not isinstance(
+ self._cli_options._openstack_config, client_config.OSC_Config
+ ):
+ # programmer error
+ raise TypeError('unexpected type for _openstack_config')
+
self._cli_options._auth = (
self._cli_options._openstack_config.load_auth_plugin(
self._cli_options.config,
@@ -144,11 +179,25 @@ class ClientManager(clientmanager.ClientManager):
# Plugin Support
+ArgumentParserT = ty.TypeVar('ArgumentParserT', bound=argparse.ArgumentParser)
+
+
+@ty.runtime_checkable # Optional: allows usage with isinstance()
+class PluginModule(ty.Protocol):
+ DEFAULT_API_VERSION: str
+ API_VERSION_OPTION: str
+ API_NAME: str
+ API_VERSIONS: tuple[str]
+
+ make_client: Callable[..., ty.Any]
+ build_option_parser: Callable[[ArgumentParserT], ArgumentParserT]
+ check_api_version: Callable[[str], bool]
+
def _on_load_failure_callback(
manager: stevedore.ExtensionManager,
ep: importlib.metadata.EntryPoint,
- err: Exception,
+ err: BaseException,
) -> None:
sys.stderr.write(
f"WARNING: Failed to import plugin {ep.group}:{ep.name}: {err}.\n"
@@ -158,36 +207,25 @@ def _on_load_failure_callback(
def get_plugin_modules(group):
"""Find plugin entry points"""
mod_list = []
+ mgr: stevedore.ExtensionManager[PluginModule]
mgr = stevedore.ExtensionManager(
group, on_load_failure_callback=_on_load_failure_callback
)
for ep in mgr:
LOG.debug('Found plugin %s', ep.name)
- # Different versions of stevedore use different
- # implementations of EntryPoint from other libraries, which
- # are not API-compatible.
- try:
- module_name = ep.entry_point.module_name
- except AttributeError:
- try:
- module_name = ep.entry_point.module
- except AttributeError:
- module_name = ep.entry_point.value
+ module_name = ep.entry_point.module
try:
module = importlib.import_module(module_name)
except Exception as err:
sys.stderr.write(
- f"WARNING: Failed to import plugin {ep.group}:{ep.name}: "
- f"{err}.\n"
+ f"WARNING: Failed to import plugin "
+ f"{ep.module_name}:{ep.name}: {err}.\n"
)
continue
mod_list.append(module)
- init_func = getattr(module, 'Initialize', None)
- if init_func:
- init_func('x')
# Add the plugin to the ClientManager
setattr(
diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py
index 418f7fd..4637ad2 100644
--- a/openstackclient/common/configuration.py
+++ b/openstackclient/common/configuration.py
@@ -14,8 +14,8 @@
"""Configuration action implementations"""
from keystoneauth1.loading import base
-from osc_lib.command import command
+from openstackclient import command
from openstackclient.i18n import _
REDACTED = "<redacted>"
diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
index 42c2e9b..3f9b257 100644
--- a/openstackclient/common/extension.py
+++ b/openstackclient/common/extension.py
@@ -17,9 +17,9 @@
import logging
-from osc_lib.command import command
from osc_lib import utils
+from openstackclient import command
from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py
index 0c92805..6512e0f 100644
--- a/openstackclient/common/limits.py
+++ b/openstackclient/common/limits.py
@@ -17,9 +17,9 @@
import itertools
-from osc_lib.command import command
from osc_lib import utils
+from openstackclient import command
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py
index d4a5b95..0985107 100644
--- a/openstackclient/common/module.py
+++ b/openstackclient/common/module.py
@@ -17,9 +17,9 @@
import sys
-from osc_lib.command import command
from osc_lib import utils
+from openstackclient import command
from openstackclient.i18n import _
@@ -48,7 +48,9 @@ class ListCommand(command.Lister):
columns = ('Command Group', 'Commands')
if parsed_args.group:
- groups = (group for group in groups if parsed_args.group in group)
+ groups = sorted(
+ group for group in groups if parsed_args.group in group
+ )
commands = []
for group in groups:
@@ -59,7 +61,7 @@ class ListCommand(command.Lister):
# TODO(bapalm): Fix this when cliff properly supports
# handling the detection rather than using the hard-code below.
if parsed_args.formatter == 'table':
- command_names = utils.format_list(command_names, "\n")
+ command_names = utils.format_list(command_names, "\n") # type: ignore
commands.append((group, command_names))
diff --git a/openstackclient/common/project_cleanup.py b/openstackclient/common/project_cleanup.py
index 84a0735..444f23e 100644
--- a/openstackclient/common/project_cleanup.py
+++ b/openstackclient/common/project_cleanup.py
@@ -20,8 +20,8 @@ import queue
import typing as ty
from cliff.formatters import table
-from osc_lib.command import command
+from openstackclient import command
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
@@ -90,17 +90,19 @@ class ProjectCleanup(command.Command):
return parser
def take_action(self, parsed_args):
- sdk = self.app.client_manager.sdk_connection
+ connection = self.app.client_manager.sdk_connection
if parsed_args.auth_project:
- project_connect = sdk
+ # is we've got a project already configured, use the connection
+ # as-is
+ pass
elif parsed_args.project:
- project = sdk.identity.find_project(
+ project = connection.identity.find_project(
name_or_id=parsed_args.project, ignore_missing=False
)
- project_connect = sdk.connect_as_project(project)
+ connection = connection.connect_as_project(project)
- if project_connect:
+ if connection:
status_queue: queue.Queue[ty.Any] = queue.Queue()
parsed_args.max_width = int(
os.environ.get('CLIFF_MAX_TERM_WIDTH', 0)
@@ -120,7 +122,7 @@ class ProjectCleanup(command.Command):
if parsed_args.updated_before:
filters['updated_at'] = parsed_args.updated_before
- project_connect.project_cleanup(
+ connection.project_cleanup(
dry_run=True,
status_queue=status_queue,
filters=filters,
@@ -150,7 +152,7 @@ class ProjectCleanup(command.Command):
self.log.warning(_('Deleting resources'))
- project_connect.project_cleanup(
+ connection.project_cleanup(
dry_run=False,
status_queue=status_queue,
filters=filters,
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index f706a59..0e7edf4 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -21,10 +21,10 @@ import sys
import typing as ty
from openstack import exceptions as sdk_exceptions
-from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
+from openstackclient import command
from openstackclient.i18n import _
from openstackclient.network import common
@@ -272,7 +272,10 @@ class ListQuota(command.Lister):
sdk_exceptions.NotFoundException,
) as exc:
# Project not found, move on to next one
- LOG.warning(f"Project {project_id} not found: {exc}")
+ LOG.warning(
+ 'Project %(project_id)s not found: %(exc)s',
+ {'project_id': project_id, 'exc': exc},
+ )
continue
project_result = _xform_get_quota(
@@ -334,7 +337,10 @@ class ListQuota(command.Lister):
sdk_exceptions.NotFoundException,
) as exc:
# Project not found, move on to next one
- LOG.warning(f"Project {project_id} not found: {exc}")
+ LOG.warning(
+ 'Project %(project_id)s not found: %(exc)s',
+ {'project_id': project_id, 'exc': exc},
+ )
continue
project_result = _xform_get_quota(
@@ -389,7 +395,10 @@ class ListQuota(command.Lister):
sdk_exceptions.ForbiddenException,
) as exc:
# Project not found, move on to next one
- LOG.warning(f"Project {project_id} not found: {exc}")
+ LOG.warning(
+ 'Project %(project_id)s not found: %(exc)s',
+ {'project_id': project_id, 'exc': exc},
+ )
continue
project_result = _xform_get_quota(
@@ -746,21 +755,32 @@ and ``server-group-members`` output for a given quota class."""
# values if the project or class does not exist. This is expected
# behavior. However, we have already checked for the presence of the
# project above so it shouldn't be an issue.
- if parsed_args.service in {'all', 'compute'}:
+ if parsed_args.service == 'compute' or (
+ parsed_args.service == 'all'
+ and self.app.client_manager.is_compute_endpoint_enabled()
+ ):
compute_quota_info = get_compute_quotas(
self.app,
project,
detail=parsed_args.usage,
default=parsed_args.default,
)
- if parsed_args.service in {'all', 'volume'}:
+
+ if parsed_args.service == 'volume' or (
+ parsed_args.service == 'all'
+ and self.app.client_manager.is_volume_endpoint_enabled()
+ ):
volume_quota_info = get_volume_quotas(
self.app,
project,
detail=parsed_args.usage,
default=parsed_args.default,
)
- if parsed_args.service in {'all', 'network'}:
+
+ if parsed_args.service == 'network' or (
+ parsed_args.service == 'all'
+ and self.app.client_manager.is_network_endpoint_enabled()
+ ):
network_quota_info = get_network_quotas(
self.app,
project,
@@ -786,20 +806,25 @@ and ``server-group-members`` output for a given quota class."""
info.update(volume_quota_info)
info.update(network_quota_info)
- # Map the internal quota names to the external ones
- # COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips,
- # secgroup-rules and secgroups as dict value, so when
- # neutron is enabled, quotas of these three resources
- # in nova will be replaced by neutron's.
- for k, v in itertools.chain(
- COMPUTE_QUOTAS.items(),
- NOVA_NETWORK_QUOTAS.items(),
- VOLUME_QUOTAS.items(),
- NETWORK_QUOTAS.items(),
- ):
- if not k == v and info.get(k) is not None:
- info[v] = info[k]
- info.pop(k)
+ def _normalize_names(section: dict) -> None:
+ # Map the internal quota names to the external ones
+ # COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips,
+ # secgroup-rules and secgroups as dict value, so when
+ # neutron is enabled, quotas of these three resources
+ # in nova will be replaced by neutron's.
+ for k, v in itertools.chain(
+ COMPUTE_QUOTAS.items(),
+ NOVA_NETWORK_QUOTAS.items(),
+ VOLUME_QUOTAS.items(),
+ NETWORK_QUOTAS.items(),
+ ):
+ if not k == v and section.get(k) is not None:
+ section[v] = section.pop(k)
+
+ _normalize_names(info)
+ if parsed_args.usage:
+ _normalize_names(info["reservation"])
+ _normalize_names(info["usage"])
# Remove the 'id' field since it's not very useful
if 'id' in info:
@@ -906,12 +931,18 @@ class DeleteQuota(command.Command):
)
# compute quotas
- if parsed_args.service in {'all', 'compute'}:
+ if parsed_args.service == 'compute' or (
+ parsed_args.service == 'all'
+ and self.app.client_manager.is_compute_endpoint_enabled()
+ ):
compute_client = self.app.client_manager.compute
compute_client.revert_quota_set(project.id)
# volume quotas
- if parsed_args.service in {'all', 'volume'}:
+ if parsed_args.service == 'volume' or (
+ parsed_args.service == 'all'
+ and self.app.client_manager.is_volume_endpoint_enabled()
+ ):
volume_client = self.app.client_manager.sdk_connection.volume
volume_client.revert_quota_set(project.id)
diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py
index 6622338..dfd84e0 100644
--- a/openstackclient/common/versions.py
+++ b/openstackclient/common/versions.py
@@ -14,8 +14,7 @@
"""Versions Action Implementation"""
-from osc_lib.command import command
-
+from openstackclient import command
from openstackclient.i18n import _