Add pre-commit, apply ruff

Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Change-Id: Iedc7653f27dbe9bb77b6d9bb03117a457e195e58
This commit is contained in:
Stephen Finucane
2025-05-30 13:41:26 +01:00
parent 964b7237e5
commit 15b72f2214
21 changed files with 911 additions and 607 deletions

32
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,32 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: mixed-line-ending
args: ['--fix', 'lf']
exclude: '.*\.(svg)$'
- id: check-byte-order-marker
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: debug-statements
- id: check-yaml
files: .*\.(yaml|yml)$
exclude: '^zuul.d/.*$'
- repo: https://github.com/PyCQA/doc8
rev: v1.1.2
hooks:
- id: doc8
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
hooks:
- id: ruff-check
args: ['--fix', '--unsafe-fixes']
- id: ruff-format
- repo: https://opendev.org/openstack/hacking
rev: 7.0.0
hooks:
- id: hacking
additional_dependencies: []
exclude: '^(doc|releasenotes|tools)/.*$'

View File

@@ -13,4 +13,4 @@ Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/os-client-config
https://bugs.launchpad.net/os-client-config

20
doc/source/conf.py Executable file → Normal file
View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# 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
@@ -43,8 +42,8 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'os-client-config'
copyright = u'2015, various OpenStack developers'
project = 'os-client-config'
copyright = '2015, various OpenStack developers'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
@@ -66,17 +65,20 @@ pygments_style = 'native'
html_theme = 'openstackdocs'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
htmlhelp_basename = f'{project}doc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
(
'index',
f'{project}.tex',
f'{project} Documentation',
'OpenStack Foundation',
'manual',
),
]
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
# intersphinx_mapping = {'http://docs.python.org/': None}

View File

@@ -26,15 +26,16 @@ _config = None
def get_config(
service_key=None, options=None,
app_name=None, app_version=None,
**kwargs):
service_key=None, options=None, app_name=None, app_version=None, **kwargs
):
load_yaml_config = kwargs.pop('load_yaml_config', True)
global _config
if not _config:
_config = OpenStackConfig(
load_yaml_config=load_yaml_config,
app_name=app_name, app_version=app_version)
app_name=app_name,
app_version=app_version,
)
if options:
_config.register_argparse_arguments(options, sys.argv, service_key)
parsed_options = options.parse_known_args(sys.argv)
@@ -45,9 +46,13 @@ def get_config(
def make_rest_client(
service_key, options=None,
app_name=None, app_version=None, version=None,
**kwargs):
service_key,
options=None,
app_name=None,
app_version=None,
version=None,
**kwargs,
):
"""Simple wrapper function. It has almost no features.
This will get you a raw requests Session Adapter that is mounted
@@ -61,9 +66,12 @@ def make_rest_client(
at OpenStack REST APIs with a properly configured keystone session.
"""
cloud = get_config(
service_key=service_key, options=options,
app_name=app_name, app_version=app_version,
**kwargs)
service_key=service_key,
options=options,
app_name=app_name,
app_version=app_version,
**kwargs,
)
return cloud.get_session_client(service_key, version=version)
@@ -98,6 +106,7 @@ def make_sdk(options=None, **kwargs):
:rtype: :class:`~openstack.connection.Connection`
"""
from openstack import connection
cloud = get_config(options=options, **kwargs)
return connection.from_config(cloud_config=cloud, options=options)
@@ -110,5 +119,6 @@ def make_shade(options=None, **kwargs):
:rtype: :class:`~shade.OpenStackCloud`
"""
import shade
cloud = get_config(options=options, **kwargs)
return shade.OpenStackCloud(cloud_config=cloud, **kwargs)

View File

@@ -26,39 +26,35 @@ def _get_client(service_key):
class_mapping = constructors.get_constructor_mapping()
if service_key not in class_mapping:
raise exceptions.OpenStackConfigException(
"Service {service_key} is unkown. Please pass in a client"
" constructor or submit a patch to os-client-config".format(
service_key=service_key))
f"Service {service_key} is unkown. Please pass in a client"
" constructor or submit a patch to os-client-config"
)
mod_name, ctr_name = class_mapping[service_key].rsplit('.', 1)
lib_name = mod_name.split('.')[0]
try:
mod = importlib.import_module(mod_name)
except ImportError:
raise exceptions.OpenStackConfigException(
"Client for '{service_key}' was requested, but"
" {mod_name} was unable to be imported. Either import"
f"Client for '{service_key}' was requested, but"
f" {mod_name} was unable to be imported. Either import"
" the module yourself and pass the constructor in as an argument,"
" or perhaps you do not have python-{lib_name} installed.".format(
service_key=service_key,
mod_name=mod_name,
lib_name=lib_name))
f" or perhaps you do not have python-{lib_name} installed."
)
try:
ctr = getattr(mod, ctr_name)
except AttributeError:
raise exceptions.OpenStackConfigException(
"Client for '{service_key}' was requested, but although"
" {mod_name} imported fine, the constructor at {fullname}"
f"Client for '{service_key}' was requested, but although"
f" {mod_name} imported fine, the constructor at {class_mapping[service_key]}"
" as not found. Please check your installation, we have no"
" clue what is wrong with your computer.".format(
service_key=service_key,
mod_name=mod_name,
fullname=class_mapping[service_key]))
" clue what is wrong with your computer."
)
return ctr
class CloudConfig(cloud_region.CloudRegion):
def __init__(self, *args, **kwargs):
super(CloudConfig, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.log = _log.setup_logging(__name__)
def __getattr__(self, key):
@@ -74,8 +70,9 @@ class CloudConfig(cloud_region.CloudRegion):
def insert_user_agent(self):
self._keystone_session.additional_user_agent.append(
('os-client-config', os_client_config.__version__))
super(CloudConfig, self).insert_user_agent()
('os-client-config', os_client_config.__version__)
)
super().insert_user_agent()
@property
def region(self):
@@ -88,9 +85,16 @@ class CloudConfig(cloud_region.CloudRegion):
return self.get_cache_expirations()
def get_legacy_client(
self, service_key, client_class=None, interface_key=None,
pass_version_arg=True, version=None, min_version=None,
max_version=None, **kwargs):
self,
service_key,
client_class=None,
interface_key=None,
pass_version_arg=True,
version=None,
min_version=None,
max_version=None,
**kwargs,
):
"""Return a legacy OpenStack client object for the given config.
Most of the OpenStack python-*client libraries have the same
@@ -136,7 +140,8 @@ class CloudConfig(cloud_region.CloudRegion):
interface = self.get_interface(service_key)
# trigger exception on lack of service
endpoint = self.get_session_endpoint(
service_key, min_version=min_version, max_version=max_version)
service_key, min_version=min_version, max_version=max_version
)
endpoint_override = self.get_endpoint(service_key)
if service_key == 'object-store':
@@ -145,14 +150,17 @@ class CloudConfig(cloud_region.CloudRegion):
os_options=dict(
service_type=self.get_service_type(service_key),
object_storage_url=endpoint_override,
region_name=self.region))
region_name=self.region,
),
)
else:
constructor_kwargs = dict(
session=self.get_session(),
service_name=self.get_service_name(service_key),
service_type=self.get_service_type(service_key),
endpoint_override=endpoint_override,
region_name=self.region)
region_name=self.region,
)
if service_key == 'image':
# os-client-config does not depend on glanceclient, but if
@@ -160,6 +168,7 @@ class CloudConfig(cloud_region.CloudRegion):
# would need to do if they were requesting 'image' - then
# they necessarily have glanceclient installed
from glanceclient.common import utils as glance_utils
endpoint, detected_version = glance_utils.strip_version(endpoint)
# If the user has passed in a version, that's explicit, use it
if not version:
@@ -176,6 +185,7 @@ class CloudConfig(cloud_region.CloudRegion):
version = self.get_api_version(service_key)
if not version and service_key == 'volume':
from cinderclient import client as cinder_client
version = cinder_client.get_volume_api_from_url(endpoint)
# Temporary workaround while we wait for python-openstackclient
# to be able to handle 2.0 which is what neutronclient expects
@@ -198,14 +208,16 @@ class CloudConfig(cloud_region.CloudRegion):
constructor_kwargs['version'] = version
if min_version and min_version > float(version):
raise exceptions.OpenStackConfigVersionException(
"Minimum version {min_version} requested but {version}"
" found".format(min_version=min_version, version=version),
version=version)
f"Minimum version {min_version} requested but {version}"
" found",
version=version,
)
if max_version and max_version < float(version):
raise exceptions.OpenStackConfigVersionException(
"Maximum version {max_version} requested but {version}"
" found".format(max_version=max_version, version=version),
version=version)
f"Maximum version {max_version} requested but {version}"
" found",
version=version,
)
if service_key == 'database':
# TODO(mordred) Remove when https://review.openstack.org/314032
# has landed and released. We're passing in a Session, but the
@@ -217,8 +229,11 @@ class CloudConfig(cloud_region.CloudRegion):
if not interface_key:
if service_key in ('image', 'key-manager'):
interface_key = 'interface'
elif (service_key == 'identity'
and version and version.startswith('3')):
elif (
service_key == 'identity'
and version
and version.startswith('3')
):
interface_key = 'interface'
else:
interface_key = 'endpoint_type'

View File

@@ -23,7 +23,6 @@ from os_client_config import defaults
class OpenStackConfig(loader.OpenStackConfig):
_cloud_region_class = cloud_config.CloudConfig
_defaults_module = defaults
@@ -59,10 +58,10 @@ if __name__ == '__main__':
if len(sys.argv) == 1:
print_cloud = True
elif len(sys.argv) == 3 and (
sys.argv[1] == cloud.name and sys.argv[2] == cloud.region):
sys.argv[1] == cloud.name and sys.argv[2] == cloud.region
):
print_cloud = True
elif len(sys.argv) == 2 and (
sys.argv[1] == cloud.name):
elif len(sys.argv) == 2 and (sys.argv[1] == cloud.name):
print_cloud = True
if print_cloud:

View File

@@ -17,7 +17,8 @@ import os
import threading
_json_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'constructors.json')
os.path.dirname(os.path.realpath(__file__)), 'constructors.json'
)
_class_mapping = None
_class_mapping_lock = threading.Lock()
@@ -30,7 +31,7 @@ def get_constructor_mapping():
if _class_mapping is not None:
return _class_mapping.copy()
tmp_class_mapping = {}
with open(_json_path, 'r') as json_file:
with open(_json_path) as json_file:
tmp_class_mapping.update(json.load(json_file))
_class_mapping = tmp_class_mapping
return tmp_class_mapping.copy()

View File

@@ -17,7 +17,8 @@ import os
from openstack.config import defaults
_json_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'defaults.json')
os.path.dirname(os.path.realpath(__file__)), 'defaults.json'
)
def get_defaults():

View File

@@ -21,5 +21,5 @@ class OpenStackConfigVersionException(OpenStackConfigException):
"""A version was requested that is different than what was found."""
def __init__(self, version):
super(OpenStackConfigVersionException, self).__init__()
super().__init__()
self.version = version

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
@@ -98,30 +96,37 @@ USER_CONF = {
'domain_id': '6789',
'project_domain_id': '123456789',
},
'networks': [{
'name': 'a-public',
'routes_externally': True,
'nat_source': True,
}, {
'name': 'another-public',
'routes_externally': True,
'default_interface': True,
}, {
'name': 'a-private',
'routes_externally': False,
}, {
'name': 'another-private',
'routes_externally': False,
'nat_destination': True,
}, {
'name': 'split-default',
'routes_externally': True,
'routes_ipv4_externally': False,
}, {
'name': 'split-no-default',
'routes_ipv6_externally': False,
'routes_ipv4_externally': True,
}],
'networks': [
{
'name': 'a-public',
'routes_externally': True,
'nat_source': True,
},
{
'name': 'another-public',
'routes_externally': True,
'default_interface': True,
},
{
'name': 'a-private',
'routes_externally': False,
},
{
'name': 'another-private',
'routes_externally': False,
'nat_destination': True,
},
{
'name': 'split-default',
'routes_externally': True,
'routes_ipv4_externally': False,
},
{
'name': 'split-no-default',
'routes_ipv6_externally': False,
'routes_ipv4_externally': True,
},
],
'region_name': 'test-region',
},
'_test_cloud_regions': {
@@ -136,14 +141,14 @@ USER_CONF = {
'name': 'region1',
'values': {
'external_network': 'region1-network',
}
},
},
{
'name': 'region2',
'values': {
'external_network': 'my-network',
}
}
},
},
],
},
'_test_cloud_hyphenated': {
@@ -202,7 +207,7 @@ class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""
def setUp(self):
super(TestCase, self).setUp()
super().setUp()
self.useFixture(fixtures.NestedTempfile())
conf = copy.deepcopy(USER_CONF)
@@ -212,10 +217,12 @@ class TestCase(base.BaseTestCase):
self.secure_yaml = _write_yaml(SECURE_CONF)
self.vendor_yaml = _write_yaml(VENDOR_CONF)
self.no_yaml = _write_yaml(NO_CONF)
self.useFixture(fixtures.MonkeyPatch(
'os_client_config.__version__', '1.2.3'))
self.useFixture(fixtures.MonkeyPatch(
'openstack.version.__version__', '3.4.5'))
self.useFixture(
fixtures.MonkeyPatch('os_client_config.__version__', '1.2.3')
)
self.useFixture(
fixtures.MonkeyPatch('openstack.version.__version__', '3.4.5')
)
# Isolate the test runs from the environment
# Do this as two loops because you can't modify the dict in a loop

View File

@@ -34,15 +34,15 @@ fake_services_dict = {
class TestCloudConfig(base.TestCase):
@mock.patch.object(cloud_region.CloudRegion, 'get_api_version')
@mock.patch.object(cloud_region.CloudRegion, 'get_auth_args')
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_object_store_password(
self,
mock_get_session_endpoint,
mock_get_auth_args,
mock_get_api_version):
self,
mock_get_session_endpoint,
mock_get_auth_args,
mock_get_api_version,
):
mock_client = mock.Mock()
mock_get_session_endpoint.return_value = 'http://swift.example.com'
mock_get_api_version.return_value = '3'
@@ -55,7 +55,8 @@ class TestCloudConfig(base.TestCase):
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('object-store', mock_client)
mock_client.assert_called_with(
session=mock.ANY,
@@ -64,12 +65,14 @@ class TestCloudConfig(base.TestCase):
'service_type': 'object-store',
'object_storage_url': None,
'endpoint_type': 'public',
})
},
)
@mock.patch.object(cloud_region.CloudRegion, 'get_auth_args')
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_object_store_password_v2(
self, mock_get_session_endpoint, mock_get_auth_args):
self, mock_get_session_endpoint, mock_get_auth_args
):
mock_client = mock.Mock()
mock_get_session_endpoint.return_value = 'http://swift.example.com'
mock_get_auth_args.return_value = dict(
@@ -81,7 +84,8 @@ class TestCloudConfig(base.TestCase):
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('object-store', mock_client)
mock_client.assert_called_with(
session=mock.ANY,
@@ -90,19 +94,22 @@ class TestCloudConfig(base.TestCase):
'service_type': 'object-store',
'object_storage_url': None,
'endpoint_type': 'public',
})
},
)
@mock.patch.object(cloud_region.CloudRegion, 'get_auth_args')
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_object_store(
self, mock_get_session_endpoint, mock_get_auth_args):
self, mock_get_session_endpoint, mock_get_auth_args
):
mock_client = mock.Mock()
mock_get_session_endpoint.return_value = 'http://example.com/v2'
mock_get_auth_args.return_value = {}
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('object-store', mock_client)
mock_client.assert_called_with(
session=mock.ANY,
@@ -111,12 +118,14 @@ class TestCloudConfig(base.TestCase):
'service_type': 'object-store',
'object_storage_url': None,
'endpoint_type': 'public',
})
},
)
@mock.patch.object(cloud_region.CloudRegion, 'get_auth_args')
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_object_store_timeout(
self, mock_get_session_endpoint, mock_get_auth_args):
self, mock_get_session_endpoint, mock_get_auth_args
):
mock_client = mock.Mock()
mock_get_session_endpoint.return_value = 'http://example.com/v2'
mock_get_auth_args.return_value = {}
@@ -124,8 +133,11 @@ class TestCloudConfig(base.TestCase):
config_dict.update(fake_services_dict)
config_dict['api_timeout'] = 9
cc = cloud_config.CloudConfig(
name="test1", region_name="region-al", config=config_dict,
auth_plugin=mock.Mock())
name="test1",
region_name="region-al",
config=config_dict,
auth_plugin=mock.Mock(),
)
cc.get_legacy_client('object-store', mock_client)
mock_client.assert_called_with(
session=mock.ANY,
@@ -134,18 +146,19 @@ class TestCloudConfig(base.TestCase):
'service_type': 'object-store',
'object_storage_url': None,
'endpoint_type': 'public',
})
},
)
@mock.patch.object(cloud_region.CloudRegion, 'get_auth_args')
def test_legacy_client_object_store_endpoint(
self, mock_get_auth_args):
def test_legacy_client_object_store_endpoint(self, mock_get_auth_args):
mock_client = mock.Mock()
mock_get_auth_args.return_value = {}
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
config_dict['object_store_endpoint'] = 'http://example.com/swift'
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('object-store', mock_client)
mock_client.assert_called_with(
session=mock.ANY,
@@ -154,7 +167,8 @@ class TestCloudConfig(base.TestCase):
'service_type': 'object-store',
'object_storage_url': 'http://example.com/swift',
'endpoint_type': 'public',
})
},
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_image(self, mock_get_session_endpoint):
@@ -163,7 +177,8 @@ class TestCloudConfig(base.TestCase):
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('image', mock_client)
mock_client.assert_called_with(
version=2.0,
@@ -173,7 +188,7 @@ class TestCloudConfig(base.TestCase):
interface='public',
session=mock.ANY,
# Not a typo - the config dict above overrides this
service_type='mage'
service_type='mage',
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
@@ -184,7 +199,8 @@ class TestCloudConfig(base.TestCase):
config_dict.update(fake_services_dict)
config_dict['image_endpoint_override'] = 'http://example.com/override'
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('image', mock_client)
mock_client.assert_called_with(
version=2.0,
@@ -194,7 +210,7 @@ class TestCloudConfig(base.TestCase):
interface='public',
session=mock.ANY,
# Not a typo - the config dict above overrides this
service_type='mage'
service_type='mage',
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
@@ -206,7 +222,8 @@ class TestCloudConfig(base.TestCase):
# v2 endpoint was passed, 1 requested in config, endpoint wins
config_dict['image_api_version'] = '1'
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('image', mock_client)
mock_client.assert_called_with(
version=2.0,
@@ -216,7 +233,7 @@ class TestCloudConfig(base.TestCase):
interface='public',
session=mock.ANY,
# Not a typo - the config dict above overrides this
service_type='mage'
service_type='mage',
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
@@ -228,7 +245,8 @@ class TestCloudConfig(base.TestCase):
# Versionless endpoint, config wins
config_dict['image_api_version'] = '1'
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('image', mock_client)
mock_client.assert_called_with(
version='1',
@@ -238,7 +256,7 @@ class TestCloudConfig(base.TestCase):
interface='public',
session=mock.ANY,
# Not a typo - the config dict above overrides this
service_type='mage'
service_type='mage',
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
@@ -250,7 +268,8 @@ class TestCloudConfig(base.TestCase):
# Versionless endpoint, config wins
config_dict['image_api_version'] = '6'
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('image', mock_client, version='beef')
mock_client.assert_called_with(
version='beef',
@@ -260,7 +279,7 @@ class TestCloudConfig(base.TestCase):
interface='public',
session=mock.ANY,
# Not a typo - the config dict above overrides this
service_type='mage'
service_type='mage',
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
@@ -270,7 +289,8 @@ class TestCloudConfig(base.TestCase):
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('network', mock_client)
mock_client.assert_called_with(
api_version='2.0',
@@ -279,7 +299,8 @@ class TestCloudConfig(base.TestCase):
region_name='region-al',
service_type='network',
session=mock.ANY,
service_name=None)
service_name=None,
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_compute(self, mock_get_session_endpoint):
@@ -288,7 +309,8 @@ class TestCloudConfig(base.TestCase):
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('compute', mock_client)
mock_client.assert_called_with(
version='2',
@@ -297,7 +319,8 @@ class TestCloudConfig(base.TestCase):
region_name='region-al',
service_type='compute',
session=mock.ANY,
service_name=None)
service_name=None,
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_identity(self, mock_get_session_endpoint):
@@ -306,7 +329,8 @@ class TestCloudConfig(base.TestCase):
config_dict = defaults.get_defaults()
config_dict.update(fake_services_dict)
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('identity', mock_client)
mock_client.assert_called_with(
version='2.0',
@@ -316,7 +340,8 @@ class TestCloudConfig(base.TestCase):
region_name='region-al',
service_type='identity',
session=mock.ANY,
service_name='locks')
service_name='locks',
)
@mock.patch.object(cloud_region.CloudRegion, 'get_session_endpoint')
def test_legacy_client_identity_v3(self, mock_get_session_endpoint):
@@ -326,7 +351,8 @@ class TestCloudConfig(base.TestCase):
config_dict.update(fake_services_dict)
config_dict['identity_api_version'] = '3'
cc = cloud_config.CloudConfig(
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
"test1", "region-al", config_dict, auth_plugin=mock.Mock()
)
cc.get_legacy_client('identity', mock_client)
mock_client.assert_called_with(
version='3',
@@ -336,4 +362,5 @@ class TestCloudConfig(base.TestCase):
region_name='region-al',
service_type='identity',
session=mock.ANY,
service_name='locks')
service_name='locks',
)

File diff suppressed because it is too large Load Diff

View File

@@ -22,52 +22,66 @@ import fixtures
class TestEnviron(base.TestCase):
def setUp(self):
super(TestEnviron, self).setUp()
super().setUp()
self.useFixture(
fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com'))
fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com')
)
self.useFixture(
fixtures.EnvironmentVariable('OS_USERNAME', 'testuser'))
fixtures.EnvironmentVariable('OS_USERNAME', 'testuser')
)
self.useFixture(
fixtures.EnvironmentVariable('OS_PASSWORD', 'testpass'))
fixtures.EnvironmentVariable('OS_PASSWORD', 'testpass')
)
self.useFixture(
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject'))
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject')
)
self.useFixture(
fixtures.EnvironmentVariable('NOVA_PROJECT_ID', 'testnova'))
fixtures.EnvironmentVariable('NOVA_PROJECT_ID', 'testnova')
)
def test_get_one_cloud(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
c = config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
self.assertIsInstance(c.get_one_cloud(), cloud_config.CloudConfig)
def test_no_fallthrough(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
c = config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
self.assertRaises(
exceptions.OpenStackConfigException, c.get_one_cloud, 'openstack')
exceptions.OpenStackConfigException, c.get_one_cloud, 'openstack'
)
def test_envvar_name_override(self):
self.useFixture(
fixtures.EnvironmentVariable('OS_CLOUD_NAME', 'override'))
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
fixtures.EnvironmentVariable('OS_CLOUD_NAME', 'override')
)
c = config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
cc = c.get_one_cloud('override')
self._assert_cloud_details(cc)
def test_envvar_prefer_ipv6_override(self):
self.useFixture(
fixtures.EnvironmentVariable('OS_PREFER_IPV6', 'false'))
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
secure_files=[self.secure_yaml])
fixtures.EnvironmentVariable('OS_PREFER_IPV6', 'false')
)
c = config.OpenStackConfig(
config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
secure_files=[self.secure_yaml],
)
cc = c.get_one_cloud('_test-cloud_')
self.assertFalse(cc.prefer_ipv6)
def test_environ_exists(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
secure_files=[self.secure_yaml])
c = config.OpenStackConfig(
config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
secure_files=[self.secure_yaml],
)
cc = c.get_one_cloud('envvars')
self._assert_cloud_details(cc)
self.assertNotIn('auth_url', cc.config)
@@ -80,10 +94,12 @@ class TestEnviron(base.TestCase):
self._assert_cloud_details(cc)
def test_environ_prefix(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
envvar_prefix='NOVA_',
secure_files=[self.secure_yaml])
c = config.OpenStackConfig(
config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
envvar_prefix='NOVA_',
secure_files=[self.secure_yaml],
)
cc = c.get_one_cloud('envvars')
self._assert_cloud_details(cc)
self.assertNotIn('auth_url', cc.config)
@@ -96,9 +112,11 @@ class TestEnviron(base.TestCase):
self._assert_cloud_details(cc)
def test_get_one_cloud_with_config_files(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
secure_files=[self.secure_yaml])
c = config.OpenStackConfig(
config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
secure_files=[self.secure_yaml],
)
self.assertIsInstance(c.cloud_config, dict)
self.assertIn('cache', c.cloud_config)
self.assertIsInstance(c.cloud_config['cache'], dict)
@@ -112,40 +130,44 @@ class TestEnviron(base.TestCase):
def test_config_file_override(self):
self.useFixture(
fixtures.EnvironmentVariable(
'OS_CLIENT_CONFIG_FILE', self.cloud_yaml))
c = config.OpenStackConfig(config_files=[],
vendor_files=[self.vendor_yaml])
'OS_CLIENT_CONFIG_FILE', self.cloud_yaml
)
)
c = config.OpenStackConfig(
config_files=[], vendor_files=[self.vendor_yaml]
)
cc = c.get_one_cloud('_test-cloud_')
self._assert_cloud_details(cc)
class TestEnvvars(base.TestCase):
def test_no_envvars(self):
self.useFixture(
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
self.useFixture(fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
c = config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
self.assertRaises(
exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars')
exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars'
)
def test_test_envvars(self):
self.useFixture(fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
self.useFixture(
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
self.useFixture(
fixtures.EnvironmentVariable('OS_STDERR_CAPTURE', 'True'))
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
fixtures.EnvironmentVariable('OS_STDERR_CAPTURE', 'True')
)
c = config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
self.assertRaises(
exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars')
exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars'
)
def test_incomplete_envvars(self):
self.useFixture(
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
self.useFixture(
fixtures.EnvironmentVariable('OS_USERNAME', 'user'))
config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
self.useFixture(fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
self.useFixture(fixtures.EnvironmentVariable('OS_USERNAME', 'user'))
config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
# This is broken due to an issue that's fixed in a subsequent patch
# commenting it out in this patch to keep the patch size reasonable
# self.assertRaises(
@@ -153,33 +175,38 @@ class TestEnvvars(base.TestCase):
# c.get_one_cloud, 'envvars')
def test_have_envvars(self):
self.useFixture(fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
self.useFixture(
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
fixtures.EnvironmentVariable('OS_AUTH_URL', 'http://example.com')
)
self.useFixture(fixtures.EnvironmentVariable('OS_USERNAME', 'user'))
self.useFixture(
fixtures.EnvironmentVariable('OS_AUTH_URL', 'http://example.com'))
fixtures.EnvironmentVariable('OS_PASSWORD', 'password')
)
self.useFixture(
fixtures.EnvironmentVariable('OS_USERNAME', 'user'))
self.useFixture(
fixtures.EnvironmentVariable('OS_PASSWORD', 'password'))
self.useFixture(
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'project'))
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'project')
)
c = config.OpenStackConfig(
config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]
)
cc = c.get_one_cloud('envvars')
self.assertEqual(cc.config['auth']['username'], 'user')
def test_old_envvars(self):
self.useFixture(fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
self.useFixture(
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
fixtures.EnvironmentVariable('NOVA_AUTH_URL', 'http://example.com')
)
self.useFixture(
fixtures.EnvironmentVariable(
'NOVA_AUTH_URL', 'http://example.com'))
fixtures.EnvironmentVariable('NOVA_PASSWORD', 'password')
)
self.useFixture(
fixtures.EnvironmentVariable('NOVA_PASSWORD', 'password'))
self.useFixture(
fixtures.EnvironmentVariable('NOVA_PROJECT_NAME', 'project'))
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
envvar_prefix='NOVA_')
fixtures.EnvironmentVariable('NOVA_PROJECT_NAME', 'project')
)
c = config.OpenStackConfig(
config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml],
envvar_prefix='NOVA_',
)
cc = c.get_one_cloud('envvars')
self.assertEqual(cc.config['auth']['username'], 'nova')

View File

@@ -16,7 +16,7 @@ from os_client_config.tests import base
class TestImportVendors(base.TestCase):
def test_get_profile(self):
import os_client_config # noqa
os_client_config.vendors.get_profile(profile_name="dummy")

View File

@@ -19,17 +19,16 @@ from os_client_config.tests import base
class TestInit(base.TestCase):
def test_get_config_without_arg_parser(self):
cloud_config = os_client_config.get_config(
options=None, validate=False)
options=None, validate=False
)
self.assertIsInstance(
cloud_config,
os_client_config.cloud_config.CloudConfig
cloud_config, os_client_config.cloud_config.CloudConfig
)
def test_get_config_with_arg_parser(self):
cloud_config = os_client_config.get_config(
options=argparse.ArgumentParser(),
validate=False)
self.assertIsInstance(
cloud_config,
os_client_config.cloud_config.CloudConfig
options=argparse.ArgumentParser(), validate=False
)
self.assertIsInstance(
cloud_config, os_client_config.cloud_config.CloudConfig
)

14
pyproject.toml Normal file
View File

@@ -0,0 +1,14 @@
[tool.ruff]
line-length = 79
target-version = "py310"
[tool.ruff.format]
quote-style = "preserve"
docstring-code-format = true
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "S", "U"]
[tool.ruff.lint.per-file-ignores]
"os_client_config/tests/*" = ["S"]
"tools/*" = ["S"]

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Os-Client-Config Release Notes documentation build configuration file, created by
# sphinx-quickstart on Thu Nov 5 11:50:32 2015.
@@ -15,20 +14,17 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'reno.sphinxext',
'openstackdocstheme'
]
extensions = ['reno.sphinxext', 'openstackdocstheme']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -37,14 +33,14 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'os-client-config Release Notes'
copyright = u'2015, os-client-config developers'
project = 'os-client-config Release Notes'
copyright = '2015, os-client-config developers'
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/os-client-config'
@@ -60,13 +56,13 @@ release = ''
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -74,27 +70,27 @@ exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
@@ -106,23 +102,23 @@ html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -132,48 +128,48 @@ html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'OCCReleaseNotesdoc'
@@ -182,43 +178,46 @@ htmlhelp_basename = 'OCCReleaseNotesdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'OCCReleaseNotes.tex', u'os-client-config Release Notes Documentation',
u'os-client-config developers', 'manual'),
(
'index',
'OCCReleaseNotes.tex',
'os-client-config Release Notes Documentation',
'os-client-config developers',
'manual',
),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
@@ -226,12 +225,17 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'occreleasenotes', u'os-client-config Release Notes Documentation',
[u'os-client-config developers'], 1)
(
'index',
'occreleasenotes',
'os-client-config Release Notes Documentation',
['os-client-config developers'],
1,
)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -240,23 +244,28 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'OCCReleaseNotes', u'os-client-config Release Notes Documentation',
u'os-client-config developers', 'OCCReleaseNotes',
'One line description of project.',
'Miscellaneous'),
(
'index',
'OCCReleaseNotes',
'os-client-config Release Notes Documentation',
'os-client-config developers',
'OCCReleaseNotes',
'One line description of project.',
'Miscellaneous',
),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# texinfo_no_detailmenu = False
# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']

View File

@@ -15,6 +15,4 @@
import setuptools
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)
setuptools.setup(setup_requires=['pbr>=2.0.0'], pbr=True)

View File

@@ -35,8 +35,9 @@ def print_version(version):
if version['status'] in ('CURRENT', 'stable'):
print(
"\tVersion ID: {id} updated {updated}".format(
id=version.get('id'),
updated=version.get('updated')))
id=version.get('id'), updated=version.get('updated')
)
)
verbose = '-v' in sys.argv
@@ -55,7 +56,7 @@ for cloud in os_client_config.OpenStackConfig().get_all_clouds():
if verbose:
pprint.pprint(r)
except Exception as e:
print("Error with {cloud}: {e}".format(cloud=cloud.name, e=str(e)))
print(f"Error with {cloud.name}: {str(e)}")
continue
if 'version' in r:
print_version(r['version'])
@@ -68,10 +69,11 @@ for cloud in os_client_config.OpenStackConfig().get_all_clouds():
port = None
stripped = path.rsplit('/', 2)[0]
if port:
stripped = '{stripped}:{port}'.format(stripped=stripped, port=port)
stripped = f'{stripped}:{port}'
endpoint = urllib.parse.urlunsplit(
(url.scheme, url.netloc, stripped, url.params, url.query))
print(" also {endpoint}".format(endpoint=endpoint))
(url.scheme, url.netloc, stripped, url.params, url.query)
)
print(f" also {endpoint}")
try:
r = c.get(endpoint).json()
if verbose:
@@ -84,6 +86,6 @@ for cloud in os_client_config.OpenStackConfig().get_all_clouds():
elif 'versions' in r:
print_versions(r['versions'])
else:
print("\n\nUNKNOWN\n\n{r}".format(r=r))
print(f"\n\nUNKNOWN\n\n{r}")
else:
print_versions(r['versions'])

View File

@@ -28,29 +28,34 @@ for cloud in os_client_config.OpenStackConfig().get_all_clouds():
print(endpoint)
r = c.get(endpoint).json()
except Exception:
print("Error with %s" % cloud.name)
print(f"Error with {cloud.name}")
continue
for version in r['versions']:
if version['status'] == 'CURRENT':
have_current = True
print(
"\tVersion ID: {id} updated {updated}".format(
id=version.get('id'),
updated=version.get('updated')))
id=version.get('id'), updated=version.get('updated')
)
)
print("\tVersion Max: {max}".format(max=version.get('version')))
print(
"\tVersion Max: {max}".format(max=version.get('version')))
print(
"\tVersion Min: {min}".format(min=version.get('min_version')))
"\tVersion Min: {min}".format(min=version.get('min_version'))
)
if not have_current:
for version in r['versions']:
if version['status'] == 'SUPPORTED':
have_current = True
print(
"\tVersion ID: {id} updated {updated}".format(
id=version.get('id'),
updated=version.get('updated')))
id=version.get('id'), updated=version.get('updated')
)
)
print(
"\tVersion Max: {max}".format(max=version.get('version')))
"\tVersion Max: {max}".format(max=version.get('version'))
)
print(
"\tVersion Min: {min}".format(
min=version.get('min_version')))
min=version.get('min_version')
)
)

20
tox.ini
View File

@@ -22,17 +22,13 @@ commands =
stestr slowest
[testenv:pep8]
usedevelop = false
description =
Run style checks.
skip_install = true
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
doc8
hacking
pygments
readme
pre-commit
commands =
doc8 doc/source
flake8 os_client_config
pre-commit run --all-files --show-diff-on-failure
[testenv:venv]
commands = {posargs}
@@ -54,7 +50,7 @@ deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/doc/requirements.txt
commands =
sphinx-build -W -d doc/build/doctrees -b html doc/source/ doc/build/html
sphinx-build -W -d doc/build/doctrees -b html doc/source/ doc/build/html
[testenv:releasenotes]
skip_install = true
@@ -65,6 +61,12 @@ commands =
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[flake8]
# We only enable the hacking (H) checks
select = H
# H301 Black will put commas after imports that can't fit on one line
# H404 Docstrings don't always start with a newline
# H405 Multiline docstrings are okay
ignore = H301,H403,H404,H405
show-source = True
builtins = _
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes/source/conf.py