Blacken code

Another library down.

Change-Id: Id29f29331ba994a1f09376763702fcca82ec6f1c
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane
2023-08-30 19:27:40 +01:00
parent 6304b384bb
commit ab7cdb4c25
37 changed files with 1294 additions and 901 deletions

View File

@@ -31,12 +31,19 @@ def run_apidoc(app):
return return
run_already = True run_already = True
package_dir = path.abspath(path.join(app.srcdir, '..', '..', package_dir = path.abspath(path.join(app.srcdir, '..', '..', 'osc_lib'))
'osc_lib'))
source_dir = path.join(app.srcdir, 'api') source_dir = path.join(app.srcdir, 'api')
apidoc.main(['apidoc', package_dir, '-f', apidoc.main(
'-H', 'osc-lib Modules', [
'-o', source_dir]) 'apidoc',
package_dir,
'-f',
'-H',
'osc-lib Modules',
'-o',
source_dir,
]
)
def setup(app): def setup(app):

View File

@@ -17,21 +17,24 @@ import sys
# NOTE(blk-u): Path for our Sphinx extension, remove when # NOTE(blk-u): Path for our Sphinx extension, remove when
# https://launchpad.net/bugs/1260495 is fixed. # https://launchpad.net/bugs/1260495 is fixed.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
)
# -- General configuration ---------------------------------------------------- # -- General configuration ----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # 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 # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', extensions = [
'sphinx.ext.doctest', 'sphinx.ext.autodoc',
'sphinx.ext.todo', 'sphinx.ext.doctest',
'openstackdocstheme', 'sphinx.ext.todo',
'sphinxcontrib.apidoc', 'openstackdocstheme',
] 'sphinxcontrib.apidoc',
]
# openstackdocstheme options # openstackdocstheme options
openstackdocs_repo_name = 'openstack/osc-lib' openstackdocs_repo_name = 'openstack/osc-lib'
@@ -39,13 +42,13 @@ openstackdocs_auto_name = False
openstackdocs_use_storyboard = True openstackdocs_use_storyboard = True
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
#templates_path = ['_templates'] # templates_path = ['_templates']
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = '.rst'
# The encoding of source files. # The encoding of source files.
#source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
@@ -55,13 +58,13 @@ project = 'OpenStackClient CLI Base'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
#language = None # language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
#today = '' # today = ''
# Else, today_fmt is used as the format for a strftime call. # 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 # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
@@ -69,18 +72,18 @@ exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all # The reST default role (used for this markup: `text`) to use for all
# documents. # documents.
#default_role = None # default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text. # 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 # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
#add_module_names = True # add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native' pygments_style = 'native'
@@ -93,76 +96,76 @@ modindex_common_prefix = ['osc_lib.']
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
#html_theme_path = ["."] # html_theme_path = ["."]
#html_theme = '_theme' # html_theme = '_theme'
html_theme = 'openstackdocs' html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} # html_theme_options = {}
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
#html_title = None # html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # 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 # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # 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 # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
#html_favicon = None # html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static'] # html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # 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 # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True # html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # 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 # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
#html_additional_pages = {} # html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True # html_domain_indices = True
# If false, no index is generated. # 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. # 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. # 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. # 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. # 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 # 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 # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # 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"). # 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. # Output file base name for HTML help builder.
htmlhelp_basename = 'OpenStackCommandLineClientdoc' htmlhelp_basename = 'OpenStackCommandLineClientdoc'
@@ -171,54 +174,55 @@ htmlhelp_basename = 'OpenStackCommandLineClientdoc'
# -- Options for LaTeX output ------------------------------------------------- # -- Options for LaTeX output -------------------------------------------------
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt',
#'pointsize': '10pt', # Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
} }
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]) # (source start file, target name, title, author, documentclass [howto/manual])
# . # .
latex_documents = [ latex_documents = [
('index', 'OpenStackCommandLineClient.tex', (
'OpenStack Command Line Client Documentation', 'index',
'OpenStack'), 'OpenStackCommandLineClient.tex',
'OpenStack Command Line Client Documentation',
'OpenStack',
),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
#latex_logo = None # latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
#latex_use_parts = False # latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
#latex_show_pagerefs = False # latex_show_pagerefs = False
# If true, show URL addresses after external links. # 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. # Documents to append as an appendix to all manuals.
#latex_appendices = [] # latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#latex_domain_indices = True # latex_domain_indices = True
# -- Options for manual page output ------------------------------------------- # -- Options for manual page output -------------------------------------------
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
#man_pages = [] # man_pages = []
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#man_show_urls = False # man_show_urls = False
# -- Options for Texinfo output ----------------------------------------------- # -- Options for Texinfo output -----------------------------------------------
@@ -227,21 +231,25 @@ latex_documents = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'OpenStackCommandLineClient', (
'OpenStack Command Line Client Documentation', 'index',
'OpenStack', 'OpenStackCommandLineClient', 'OpenStackCommandLineClient',
'One line description of project.', 'OpenStack Command Line Client Documentation',
'Miscellaneous'), 'OpenStack',
'OpenStackCommandLineClient',
'One line description of project.',
'Miscellaneous',
),
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#texinfo_appendices = [] # texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#texinfo_domain_indices = True # texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote' # texinfo_show_urls = 'footnote'
# -- Options for sphinxcontrib.apidoc ---------------------------------------- # -- Options for sphinxcontrib.apidoc ----------------------------------------

View File

@@ -42,11 +42,7 @@ class BaseAPI(object):
HEADER_NAME = "OpenStack-API-Version" HEADER_NAME = "OpenStack-API-Version"
def __init__( def __init__(
self, self, session=None, service_type=None, endpoint=None, **kwargs
session=None,
service_type=None,
endpoint=None,
**kwargs
): ):
"""Base object that contains some common API objects and methods """Base object that contains some common API objects and methods
@@ -140,13 +136,7 @@ class BaseAPI(object):
# The basic action methods all take a Session and return dict/lists # The basic action methods all take a Session and return dict/lists
def create( def create(self, url, session=None, method=None, **params):
self,
url,
session=None,
method=None,
**params
):
"""Create a new resource """Create a new resource
:param string url: :param string url:
@@ -166,12 +156,7 @@ class BaseAPI(object):
except json.JSONDecodeError: except json.JSONDecodeError:
return ret return ret
def delete( def delete(self, url, session=None, **params):
self,
url,
session=None,
**params
):
"""Delete a resource """Delete a resource
:param string url: :param string url:
@@ -292,9 +277,7 @@ class BaseAPI(object):
if len(data) > 1: if len(data) > 1:
msg = _("Multiple %(resource)s exist with %(attr)s='%(value)s'") msg = _("Multiple %(resource)s exist with %(attr)s='%(value)s'")
raise exceptions.CommandError( raise exceptions.CommandError(
msg % {'resource': resource, msg % {'resource': resource, 'attr': attr, 'value': value}
'attr': attr,
'value': value}
) )
# Search by id # Search by id
@@ -304,17 +287,10 @@ class BaseAPI(object):
return data[0] return data[0]
msg = _("No %(resource)s with a %(attr)s or ID of '%(value)s' found") msg = _("No %(resource)s with a %(attr)s or ID of '%(value)s' found")
raise exceptions.CommandError( raise exceptions.CommandError(
msg % {'resource': resource, msg % {'resource': resource, 'attr': attr, 'value': value}
'attr': attr,
'value': value}
) )
def find_bulk( def find_bulk(self, path, headers=None, **kwargs):
self,
path,
headers=None,
**kwargs
):
"""Bulk load and filter locally """Bulk load and filter locally
:param string path: :param string path:
@@ -342,11 +318,7 @@ class BaseAPI(object):
return ret return ret
def find_one( def find_one(self, path, **kwargs):
self,
path,
**kwargs
):
"""Find a resource by name or ID """Find a resource by name or ID
:param string path: :param string path:
@@ -390,7 +362,8 @@ class BaseAPI(object):
try: try:
ret = self._request( ret = self._request(
'GET', "/%s/%s" % (path, value), 'GET',
"/%s/%s" % (path, value),
headers=headers, headers=headers,
).json() ).json()
if isinstance(ret, dict): if isinstance(ret, dict):
@@ -404,11 +377,7 @@ class BaseAPI(object):
if attr: if attr:
kwargs = {attr: value} kwargs = {attr: value}
try: try:
ret = self.find_one( ret = self.find_one(path, headers=headers, **kwargs)
path,
headers=headers,
**kwargs
)
except ( except (
exceptions.NotFound, exceptions.NotFound,
ksa_exceptions.NotFound, ksa_exceptions.NotFound,

View File

@@ -53,7 +53,8 @@ def get_options_list():
os_name = o.name.lower().replace('_', '-') os_name = o.name.lower().replace('_', '-')
os_env_name = 'OS_' + os_name.upper().replace('-', '_') os_env_name = 'OS_' + os_name.upper().replace('-', '_')
OPTIONS_LIST.setdefault( OPTIONS_LIST.setdefault(
os_name, {'env': os_env_name, 'help': ''}, os_name,
{'env': os_env_name, 'help': ''},
) )
# TODO(mhu) simplistic approach, would be better to only add # TODO(mhu) simplistic approach, would be better to only add
# help texts if they vary from one auth plugin to another # help texts if they vary from one auth plugin to another
@@ -67,19 +68,23 @@ def get_options_list():
def check_valid_authorization_options(options, auth_plugin_name): def check_valid_authorization_options(options, auth_plugin_name):
"""Validate authorization options, and provide helpful error messages.""" """Validate authorization options, and provide helpful error messages."""
if (options.auth.get('project_id') and not if (
options.auth.get('domain_id') and not options.auth.get('project_id')
options.auth.get('domain_name') and not and not options.auth.get('domain_id')
options.auth.get('project_name') and not and not options.auth.get('domain_name')
options.auth.get('tenant_id') and not and not options.auth.get('project_name')
options.auth.get('tenant_name')): and not options.auth.get('tenant_id')
raise exc.CommandError(_( and not options.auth.get('tenant_name')
'Missing parameter(s): ' ):
'Set either a project or a domain scope, but not both. Set a ' raise exc.CommandError(
'project scope with --os-project-name, OS_PROJECT_NAME, or ' _(
'auth.project_name. Alternatively, set a domain scope with ' 'Missing parameter(s): '
'--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.' 'Set either a project or a domain scope, but not both. Set a '
)) 'project scope with --os-project-name, OS_PROJECT_NAME, or '
'auth.project_name. Alternatively, set a domain scope with '
'--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.'
)
)
def check_valid_authentication_options(options, auth_plugin_name): def check_valid_authentication_options(options, auth_plugin_name):
@@ -102,31 +107,34 @@ def check_valid_authentication_options(options, auth_plugin_name):
# when no auth params are passed in, user advised to use os-cloud # when no auth params are passed in, user advised to use os-cloud
if not options.auth and auth_plugin_name != 'none': if not options.auth and auth_plugin_name != 'none':
msgs.append(_( msgs.append(_('Set a cloud-name with --os-cloud or OS_CLOUD'))
'Set a cloud-name with --os-cloud or OS_CLOUD'
))
else: else:
if ('password' in plugin_opts and not if 'password' in plugin_opts and not (
(options.auth.get('username') or options.auth.get('user_id'))): options.auth.get('username') or options.auth.get('user_id')
msgs.append(_( ):
'Set a username with --os-username, OS_USERNAME,' msgs.append(
' or auth.username' _(
' or set a user-id with --os-user-id, OS_USER_ID,' 'Set a username with --os-username, OS_USERNAME,'
' or auth.user_id' ' or auth.username'
)) ' or set a user-id with --os-user-id, OS_USER_ID,'
' or auth.user_id'
)
)
if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): if 'auth_url' in plugin_opts and not options.auth.get('auth_url'):
msgs.append(_( msgs.append(
'Set an authentication URL, with --os-auth-url,' _(
' OS_AUTH_URL or auth.auth_url' 'Set an authentication URL, with --os-auth-url,'
)) ' OS_AUTH_URL or auth.auth_url'
)
)
if 'url' in plugin_opts and not options.auth.get('url'): if 'url' in plugin_opts and not options.auth.get('url'):
msgs.append(_( msgs.append(
'Set a service URL, with --os-url, OS_URL or auth.url' _('Set a service URL, with --os-url, OS_URL or auth.url')
)) )
if 'token' in plugin_opts and not options.auth.get('token'): if 'token' in plugin_opts and not options.auth.get('token'):
msgs.append(_( msgs.append(
'Set a token with --os-token, OS_TOKEN or auth.token' _('Set a token with --os-token, OS_TOKEN or auth.token')
)) )
if msgs: if msgs:
raise exc.CommandError( raise exc.CommandError(
@@ -147,20 +155,21 @@ def build_auth_plugins_option_parser(parser):
metavar='<auth-type>', metavar='<auth-type>',
dest='auth_type', dest='auth_type',
default=utils.env('OS_AUTH_TYPE'), default=utils.env('OS_AUTH_TYPE'),
help=_('Select an authentication type. Available types: %s.' help=_(
' Default: selected based on --os-username/--os-token' 'Select an authentication type. Available types: %s.'
' (Env: OS_AUTH_TYPE)') % ', '.join(available_plugins), ' Default: selected based on --os-username/--os-token'
choices=available_plugins ' (Env: OS_AUTH_TYPE)'
)
% ', '.join(available_plugins),
choices=available_plugins,
) )
# Maintain compatibility with old tenant env vars # Maintain compatibility with old tenant env vars
envs = { envs = {
'OS_PROJECT_NAME': utils.env( 'OS_PROJECT_NAME': utils.env(
'OS_PROJECT_NAME', 'OS_PROJECT_NAME', default=utils.env('OS_TENANT_NAME')
default=utils.env('OS_TENANT_NAME')
), ),
'OS_PROJECT_ID': utils.env( 'OS_PROJECT_ID': utils.env(
'OS_PROJECT_ID', 'OS_PROJECT_ID', default=utils.env('OS_TENANT_ID')
default=utils.env('OS_TENANT_ID')
), ),
} }
for o in get_options_list(): for o in get_options_list():
@@ -174,7 +183,8 @@ def build_auth_plugins_option_parser(parser):
OPTIONS_LIST[o]['env'], OPTIONS_LIST[o]['env'],
utils.env(OPTIONS_LIST[o]['env']), utils.env(OPTIONS_LIST[o]['env']),
), ),
help=_('%(help)s\n(Env: %(env)s)') % { help=_('%(help)s\n(Env: %(env)s)')
% {
'help': OPTIONS_LIST[o]['help'], 'help': OPTIONS_LIST[o]['help'],
'env': OPTIONS_LIST[o]['env'], 'env': OPTIONS_LIST[o]['env'],
}, },
@@ -196,10 +206,14 @@ def build_auth_plugins_option_parser(parser):
return parser return parser
def get_keystone2keystone_auth(local_auth, service_provider, def get_keystone2keystone_auth(
project_id=None, project_name=None, local_auth,
project_domain_id=None, service_provider,
project_domain_name=None): project_id=None,
project_name=None,
project_domain_id=None,
project_domain_name=None,
):
"""Return Keystone 2 Keystone authentication for service provider. """Return Keystone 2 Keystone authentication for service provider.
:param local_auth: authentication to use with the local Keystone :param local_auth: authentication to use with the local Keystone
@@ -210,9 +224,11 @@ def get_keystone2keystone_auth(local_auth, service_provider,
:param project_domain_name: name of domain to in the service provider :param project_domain_name: name of domain to in the service provider
:return: Keystone2Keystone auth object for service provider :return: Keystone2Keystone auth object for service provider
""" """
return k2k.Keystone2Keystone(local_auth, return k2k.Keystone2Keystone(
service_provider, local_auth,
project_id=project_id, service_provider,
project_name=project_name, project_id=project_id,
project_domain_id=project_domain_id, project_name=project_name,
project_domain_name=project_domain_name) project_domain_id=project_domain_id,
project_domain_name=project_domain_name,
)

View File

@@ -60,8 +60,11 @@ def simple_filter(
if attr in d: if attr in d:
# Searching data fields # Searching data fields
search_value = d[attr] search_value = d[attr]
elif (property_field and property_field in d and elif (
isinstance(d[property_field], dict)): property_field
and property_field in d
and isinstance(d[property_field], dict)
):
# Searching a properties field - do this separately because # Searching a properties field - do this separately because
# we don't want to fail over to checking the fields if a # we don't want to fail over to checking the fields if a
# property name is given. # property name is given.

View File

@@ -26,7 +26,6 @@ LOG = logging.getLogger(__name__)
# Sublcass OpenStackConfig in order to munge config values # Sublcass OpenStackConfig in order to munge config values
# before auth plugins are loaded # before auth plugins are loaded
class OSC_Config(config.OpenStackConfig): class OSC_Config(config.OpenStackConfig):
def _auth_select_default_plugin(self, config): def _auth_select_default_plugin(self, config):
"""Select a default plugin based on supplied arguments """Select a default plugin based on supplied arguments
@@ -66,7 +65,7 @@ class OSC_Config(config.OpenStackConfig):
Migrated from auth.build_auth_params() Migrated from auth.build_auth_params()
""" """
if ('auth_type' in config and config['auth_type'].startswith("v2")): if 'auth_type' in config and config['auth_type'].startswith("v2"):
if 'project_id' in config['auth']: if 'project_id' in config['auth']:
config['auth']['tenant_id'] = config['auth']['project_id'] config['auth']['tenant_id'] = config['auth']['project_id']
if 'project_name' in config['auth']: if 'project_name' in config['auth']:
@@ -82,8 +81,9 @@ class OSC_Config(config.OpenStackConfig):
# NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID
# or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then
# ignore all domain related configs. # ignore all domain related configs.
if (str(config.get('identity_api_version', '')).startswith('2') and if str(config.get('identity_api_version', '')).startswith(
config.get('auth_type').endswith('password')): '2'
) and config.get('auth_type').endswith('password'):
domain_props = [ domain_props = [
'project_domain_id', 'project_domain_id',
'project_domain_name', 'project_domain_name',
@@ -95,12 +95,14 @@ class OSC_Config(config.OpenStackConfig):
if config.get('cloud'): if config.get('cloud'):
LOG.warning( LOG.warning(
"Ignoring domain related config %s for %s" "Ignoring domain related config %s for %s"
"because identity API version is 2.0" % ( "because identity API version is 2.0"
prop, config['cloud'])) % (prop, config['cloud'])
)
else: else:
LOG.warning( LOG.warning(
"Ignoring domain related config %s because" "Ignoring domain related config %s because"
" identity API version is 2.0" % prop) " identity API version is 2.0" % prop
)
return config return config
def _auth_default_domain(self, config): def _auth_default_domain(self, config):
@@ -115,17 +117,18 @@ class OSC_Config(config.OpenStackConfig):
# TODO(mordred): This is a usability improvement that's broadly useful # TODO(mordred): This is a usability improvement that's broadly useful
# We should port it back up into os-client-config. # We should port it back up into os-client-config.
default_domain = config.get('default_domain', None) default_domain = config.get('default_domain', None)
if (identity_version == '3' and if (
not auth_type.startswith('v2') and identity_version == '3'
default_domain): and not auth_type.startswith('v2')
and default_domain
):
# NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
# present, then do not change the behaviour. Otherwise, set the # present, then do not change the behaviour. Otherwise, set the
# PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
if ( if (
auth_type in ("password", "v3password", "v3totp") and auth_type in ("password", "v3password", "v3totp")
not config['auth'].get('project_domain_id') and and not config['auth'].get('project_domain_id')
not config['auth'].get('project_domain_name') and not config['auth'].get('project_domain_name')
): ):
config['auth']['project_domain_id'] = default_domain config['auth']['project_domain_id'] = default_domain
@@ -136,9 +139,9 @@ class OSC_Config(config.OpenStackConfig):
# TODO(dtroyer): Move this to os-client-config after the plugin has # TODO(dtroyer): Move this to os-client-config after the plugin has
# been loaded so we can check directly if the options are accepted. # been loaded so we can check directly if the options are accepted.
if ( if (
auth_type in ("password", "v3password", "v3totp") and auth_type in ("password", "v3password", "v3totp")
not config['auth'].get('user_domain_id') and and not config['auth'].get('user_domain_id')
not config['auth'].get('user_domain_name') and not config['auth'].get('user_domain_name')
): ):
config['auth']['user_domain_id'] = default_domain config['auth']['user_domain_id'] = default_domain
return config return config
@@ -156,8 +159,9 @@ class OSC_Config(config.OpenStackConfig):
config = self._auth_default_domain(config) config = self._auth_default_domain(config)
if LOG.isEnabledFor(logging.DEBUG): if LOG.isEnabledFor(logging.DEBUG):
LOG.debug("auth_config_hook(): %s", LOG.debug(
strutils.mask_password(str(config))) "auth_config_hook(): %s", strutils.mask_password(str(config))
)
return config return config
def _validate_auth(self, config, loader, fixed_argparse=None): def _validate_auth(self, config, loader, fixed_argparse=None):
@@ -176,7 +180,8 @@ class OSC_Config(config.OpenStackConfig):
winning_value = self._find_winning_auth_value(p_opt, config) winning_value = self._find_winning_auth_value(p_opt, config)
if not winning_value: if not winning_value:
winning_value = self._find_winning_auth_value( winning_value = self._find_winning_auth_value(
p_opt, config['auth']) p_opt, config['auth']
)
# if the plugin tells us that this value is required # if the plugin tells us that this value is required
# then error if it's doesn't exist now # then error if it's doesn't exist now
@@ -184,7 +189,8 @@ class OSC_Config(config.OpenStackConfig):
msgs.append( msgs.append(
'Missing value {auth_key}' 'Missing value {auth_key}'
' required for auth plugin {plugin}'.format( ' required for auth plugin {plugin}'.format(
auth_key=p_opt.name, plugin=config.get('auth_type'), auth_key=p_opt.name,
plugin=config.get('auth_type'),
) )
) )
@@ -198,17 +204,18 @@ class OSC_Config(config.OpenStackConfig):
# Prefer the plugin configuration dest value if the value's key # Prefer the plugin configuration dest value if the value's key
# is marked as depreciated. # is marked as depreciated.
if p_opt.dest is None: if p_opt.dest is None:
config['auth'][p_opt.name.replace('-', '_')] = ( config['auth'][
winning_value) p_opt.name.replace('-', '_')
] = winning_value
else: else:
config['auth'][p_opt.dest] = winning_value config['auth'][p_opt.dest] = winning_value
# See if this needs a prompting # See if this needs a prompting
if ( if (
'prompt' in vars(p_opt) and 'prompt' in vars(p_opt)
p_opt.prompt is not None and and p_opt.prompt is not None
p_opt.dest not in config['auth'] and and p_opt.dest not in config['auth']
self._pw_callback is not None and self._pw_callback is not None
): ):
# Defer these until we know all required opts are present # Defer these until we know all required opts are present
prompt_options.append(p_opt) prompt_options.append(p_opt)

View File

@@ -25,14 +25,16 @@ def add_project_owner_option_to_parser(parser):
parser.add_argument( parser.add_argument(
'--project', '--project',
metavar='<project>', metavar='<project>',
help=_("Owner's project (name or ID)") help=_("Owner's project (name or ID)"),
) )
parser.add_argument( parser.add_argument(
'--project-domain', '--project-domain',
metavar='<project-domain>', metavar='<project-domain>',
help=_('Domain the project belongs to (name or ID). ' help=_(
'This can be used in case collisions between project names ' 'Domain the project belongs to (name or ID). '
'exist.'), 'This can be used in case collisions between project names '
'exist.'
),
) )
@@ -55,14 +57,15 @@ def find_project(sdk_connection, name_or_id, domain_name_or_id=None):
""" """
try: try:
if domain_name_or_id: if domain_name_or_id:
domain = sdk_connection.identity.find_domain(domain_name_or_id, domain = sdk_connection.identity.find_domain(
ignore_missing=False) domain_name_or_id, ignore_missing=False
)
domain_id = domain.id domain_id = domain.id
else: else:
domain_id = None domain_id = None
return sdk_connection.identity.find_project(name_or_id, return sdk_connection.identity.find_project(
ignore_missing=False, name_or_id, ignore_missing=False, domain_id=domain_id
domain_id=domain_id) )
# NOTE: OpenStack SDK raises HttpException for 403 response code. # NOTE: OpenStack SDK raises HttpException for 403 response code.
# There is no specific exception class at now, so we need to catch # There is no specific exception class at now, so we need to catch
# HttpException and check the status code. # HttpException and check the status code.

View File

@@ -84,8 +84,15 @@ class MultiKeyValueAction(argparse.Action):
And comma(',') and equal('=') may not be used in the key or value. And comma(',') and equal('=') may not be used in the key or value.
""" """
def __init__(self, option_strings, dest, nargs=None, def __init__(
required_keys=None, optional_keys=None, **kwargs): self,
option_strings,
dest,
nargs=None,
required_keys=None,
optional_keys=None,
**kwargs
):
"""Initialize the action object, and parse customized options """Initialize the action object, and parse customized options
Required keys and optional keys can be specified when initializing Required keys and optional keys can be specified when initializing
@@ -99,8 +106,9 @@ class MultiKeyValueAction(argparse.Action):
msg = _("Parameter 'nargs' is not allowed, but got %s") msg = _("Parameter 'nargs' is not allowed, but got %s")
raise ValueError(msg % nargs) raise ValueError(msg % nargs)
super(MultiKeyValueAction, self).__init__(option_strings, super(MultiKeyValueAction, self).__init__(
dest, **kwargs) option_strings, dest, **kwargs
)
# required_keys: A list of keys that is required. None by default. # required_keys: A list of keys that is required. None by default.
if required_keys and not isinstance(required_keys, list): if required_keys and not isinstance(required_keys, list):
@@ -128,10 +136,13 @@ class MultiKeyValueAction(argparse.Action):
"Invalid keys %(invalid_keys)s specified.\n" "Invalid keys %(invalid_keys)s specified.\n"
"Valid keys are: %(valid_keys)s" "Valid keys are: %(valid_keys)s"
) )
raise argparse.ArgumentTypeError(msg % { raise argparse.ArgumentTypeError(
'invalid_keys': ', '.join(invalid_keys), msg
'valid_keys': ', '.join(valid_keys), % {
}) 'invalid_keys': ', '.join(invalid_keys),
'valid_keys': ', '.join(valid_keys),
}
)
if self.required_keys: if self.required_keys:
missing_keys = [k for k in self.required_keys if k not in keys] missing_keys = [k for k in self.required_keys if k not in keys]
@@ -140,10 +151,13 @@ class MultiKeyValueAction(argparse.Action):
"Missing required keys %(missing_keys)s.\n" "Missing required keys %(missing_keys)s.\n"
"Required keys are: %(required_keys)s" "Required keys are: %(required_keys)s"
) )
raise argparse.ArgumentTypeError(msg % { raise argparse.ArgumentTypeError(
'missing_keys': ', '.join(missing_keys), msg
'required_keys': ', '.join(self.required_keys), % {
}) 'missing_keys': ', '.join(missing_keys),
'required_keys': ', '.join(self.required_keys),
}
)
def __call__(self, parser, namespace, values, metavar=None): def __call__(self, parser, namespace, values, metavar=None):
# Make sure we have an empty list rather than None # Make sure we have an empty list rather than None
@@ -245,10 +259,14 @@ class RangeAction(argparse.Action):
setattr(namespace, self.dest, (int(range[0]), int(range[1]))) setattr(namespace, self.dest, (int(range[0]), int(range[1])))
else: else:
msg = _("Invalid range, %(min)s is not less than %(max)s") msg = _("Invalid range, %(min)s is not less than %(max)s")
raise argparse.ArgumentError(self, msg % { raise argparse.ArgumentError(
'min': range[0], self,
'max': range[1], msg
}) % {
'min': range[0],
'max': range[1],
},
)
else: else:
# Too many values # Too many values
msg = _("Invalid range, too many values") msg = _("Invalid range, too many values")

View File

@@ -18,7 +18,7 @@
import copy import copy
import logging import logging
from openstack.config import loader as config # noqa from openstack.config import loader as config # noqa
from openstack import connection from openstack import connection
from oslo_utils import strutils from oslo_utils import strutils
@@ -145,13 +145,16 @@ class ClientManager(object):
# Horrible hack alert...must handle prompt for null password if # Horrible hack alert...must handle prompt for null password if
# password auth is requested. # password auth is requested.
if (self.auth_plugin_name.endswith('password') and if self.auth_plugin_name.endswith(
not self._cli_options.auth.get('password')): 'password'
) and not self._cli_options.auth.get('password'):
self._cli_options.auth['password'] = self._pw_callback() self._cli_options.auth['password'] = self._pw_callback()
LOG.info('Using auth plugin: %s', self.auth_plugin_name) LOG.info('Using auth plugin: %s', self.auth_plugin_name)
LOG.debug('Using parameters %s', LOG.debug(
strutils.mask_password(self._cli_options.auth)) 'Using parameters %s',
strutils.mask_password(self._cli_options.auth),
)
self.auth = self._cli_options.get_auth() self.auth = self._cli_options.get_auth()
if self._cli_options.service_provider: if self._cli_options.service_provider:
@@ -161,7 +164,7 @@ class ClientManager(object):
self._cli_options.remote_project_id, self._cli_options.remote_project_id,
self._cli_options.remote_project_name, self._cli_options.remote_project_name,
self._cli_options.remote_project_domain_id, self._cli_options.remote_project_domain_id,
self._cli_options.remote_project_domain_name self._cli_options.remote_project_domain_name,
) )
self.session = self._cli_options.get_session() self.session = self._cli_options.get_session()
@@ -193,8 +196,10 @@ class ClientManager(object):
@property @property
def auth_ref(self): def auth_ref(self):
"""Dereference will trigger an auth if it hasn't already""" """Dereference will trigger an auth if it hasn't already"""
if (not self._auth_required or if (
self._cli_options.config['auth_type'] == 'none'): not self._auth_required
or self._cli_options.config['auth_type'] == 'none'
):
# Forcibly skip auth if we know we do not need it # Forcibly skip auth if we know we do not need it
return None return None
if not self._auth_ref: if not self._auth_ref:
@@ -230,8 +235,9 @@ class ClientManager(object):
LOG.debug("No service catalog") LOG.debug("No service catalog")
return service_available return service_available
def get_endpoint_for_service_type(self, service_type, region_name=None, def get_endpoint_for_service_type(
interface='public'): self, service_type, region_name=None, interface='public'
):
"""Return the endpoint URL for the service type.""" """Return the endpoint URL for the service type."""
# Overrides take priority unconditionally # Overrides take priority unconditionally
override = self._override_for(service_type) override = self._override_for(service_type)

View File

@@ -24,25 +24,26 @@ from osc_lib.i18n import _
class CommandMeta(abc.ABCMeta): class CommandMeta(abc.ABCMeta):
def __new__(mcs, name, bases, cls_dict): def __new__(mcs, name, bases, cls_dict):
if 'log' not in cls_dict: if 'log' not in cls_dict:
cls_dict['log'] = logging.getLogger( cls_dict['log'] = logging.getLogger(
cls_dict['__module__'] + '.' + name) cls_dict['__module__'] + '.' + name
)
return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict) return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict)
class Command(command.Command, metaclass=CommandMeta): class Command(command.Command, metaclass=CommandMeta):
def run(self, parsed_args): def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args) self.log.debug('run(%s)', parsed_args)
return super(Command, self).run(parsed_args) return super(Command, self).run(parsed_args)
def validate_os_beta_command_enabled(self): def validate_os_beta_command_enabled(self):
if not self.app.options.os_beta_command: if not self.app.options.os_beta_command:
msg = _('Caution: This is a beta command and subject to ' msg = _(
'change. Use global option --os-beta-command ' 'Caution: This is a beta command and subject to '
'to enable this command.') 'change. Use global option --os-beta-command '
'to enable this command.'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
def deprecated_option_warning(self, old_option, new_option): def deprecated_option_warning(self, old_option, new_option):

View File

@@ -18,4 +18,5 @@ import cliff.commandmanager
class CommandManager(cliff.commandmanager.CommandManager): class CommandManager(cliff.commandmanager.CommandManager):
"""Noop subclass for transition purposes.""" """Noop subclass for transition purposes."""
pass pass

View File

@@ -26,6 +26,7 @@ class AuthorizationFailure(Exception):
class PluginAttributeError(Exception): class PluginAttributeError(Exception):
"""A plugin threw an AttributeError while being lazily loaded.""" """A plugin threw an AttributeError while being lazily loaded."""
# This *must not* inherit from AttributeError; # This *must not* inherit from AttributeError;
# that would defeat the whole purpose. # that would defeat the whole purpose.
pass pass
@@ -33,21 +34,25 @@ class PluginAttributeError(Exception):
class NoTokenLookupException(Exception): class NoTokenLookupException(Exception):
"""This does not support looking up endpoints from an existing token.""" """This does not support looking up endpoints from an existing token."""
pass pass
class EndpointNotFound(Exception): class EndpointNotFound(Exception):
"""Could not find Service or Region in Service Catalog.""" """Could not find Service or Region in Service Catalog."""
pass pass
class UnsupportedVersion(Exception): class UnsupportedVersion(Exception):
"""The user is trying to use an unsupported version of the API""" """The user is trying to use an unsupported version of the API"""
pass pass
class InvalidValue(Exception): class InvalidValue(Exception):
"""An argument value is not valid: wrong type, out of range, etc""" """An argument value is not valid: wrong type, out of range, etc"""
message = "Supplied value is not valid" message = "Supplied value is not valid"
@@ -68,36 +73,42 @@ class ClientException(Exception):
class BadRequest(ClientException): class BadRequest(ClientException):
"""HTTP 400 - Bad request: you sent some malformed data.""" """HTTP 400 - Bad request: you sent some malformed data."""
http_status = 400 http_status = 400
message = "Bad request" message = "Bad request"
class Unauthorized(ClientException): class Unauthorized(ClientException):
"""HTTP 401 - Unauthorized: bad credentials.""" """HTTP 401 - Unauthorized: bad credentials."""
http_status = 401 http_status = 401
message = "Unauthorized" message = "Unauthorized"
class Forbidden(ClientException): class Forbidden(ClientException):
"""HTTP 403 - Forbidden: not authorized to access to this resource.""" """HTTP 403 - Forbidden: not authorized to access to this resource."""
http_status = 403 http_status = 403
message = "Forbidden" message = "Forbidden"
class NotFound(ClientException): class NotFound(ClientException):
"""HTTP 404 - Not found""" """HTTP 404 - Not found"""
http_status = 404 http_status = 404
message = "Not found" message = "Not found"
class Conflict(ClientException): class Conflict(ClientException):
"""HTTP 409 - Conflict""" """HTTP 409 - Conflict"""
http_status = 409 http_status = 409
message = "Conflict" message = "Conflict"
class OverLimit(ClientException): class OverLimit(ClientException):
"""HTTP 413 - Over limit: reached the API limits for this time period.""" """HTTP 413 - Over limit: reached the API limits for this time period."""
http_status = 413 http_status = 413
message = "Over limit" message = "Over limit"
@@ -105,6 +116,7 @@ class OverLimit(ClientException):
# NotImplemented is a python keyword. # NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException): class HTTPNotImplemented(ClientException):
"""HTTP 501 - Not Implemented: server does not support this operation.""" """HTTP 501 - Not Implemented: server does not support this operation."""
http_status = 501 http_status = 501
message = "Not Implemented" message = "Not Implemented"
@@ -115,11 +127,14 @@ class HTTPNotImplemented(ClientException):
# for c in ClientException.__subclasses__()) # for c in ClientException.__subclasses__())
# #
# Instead, we have to hardcode it: # Instead, we have to hardcode it:
_code_map = dict((c.http_status, c) for c in [ _code_map = dict(
BadRequest, (c.http_status, c)
Unauthorized, for c in [
Forbidden, BadRequest,
NotFound, Unauthorized,
OverLimit, Forbidden,
HTTPNotImplemented NotFound,
]) OverLimit,
HTTPNotImplemented,
]
)

View File

@@ -81,8 +81,10 @@ def set_warning_filter(log_level):
class _FileFormatter(logging.Formatter): class _FileFormatter(logging.Formatter):
"""Customize the logging format for logging handler""" """Customize the logging format for logging handler"""
_LOG_MESSAGE_BEGIN = ( _LOG_MESSAGE_BEGIN = (
'%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ') '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s '
)
_LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] ' _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] '
_LOG_MESSAGE_END = '%(message)s' _LOG_MESSAGE_END = '%(message)s'
_LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
@@ -102,16 +104,17 @@ class _FileFormatter(logging.Formatter):
'username': config.auth.get('username', ''), 'username': config.auth.get('username', ''),
} }
if context: if context:
self.fmt = (self._LOG_MESSAGE_BEGIN + self.fmt = (
(self._LOG_MESSAGE_CONTEXT % context) + self._LOG_MESSAGE_BEGIN
self._LOG_MESSAGE_END) + (self._LOG_MESSAGE_CONTEXT % context)
+ self._LOG_MESSAGE_END
)
else: else:
self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END
logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT) logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT)
class LogConfigurator(object): class LogConfigurator(object):
_CONSOLE_MESSAGE_FORMAT = '%(message)s' _CONSOLE_MESSAGE_FORMAT = '%(message)s'
def __init__(self, options): def __init__(self, options):
@@ -183,7 +186,7 @@ class LogConfigurator(object):
for k in logconfig.keys(): for k in logconfig.keys():
level = log_level_from_string(logconfig[k]) level = log_level_from_string(logconfig[k])
logging.getLogger(k).setLevel(level) logging.getLogger(k).setLevel(level)
if (highest_level < level): if highest_level < level:
highest_level = level highest_level = level
self.console_logger.setLevel(highest_level) self.console_logger.setLevel(highest_level)
if self.file_logger: if self.file_logger:

View File

@@ -63,29 +63,32 @@ def prompt_for_password(prompt=None):
pass pass
# No password because we did't have a tty or nothing was entered # No password because we did't have a tty or nothing was entered
if not pw: if not pw:
raise exc.CommandError(_("No password entered, or found via" raise exc.CommandError(
" --os-password or OS_PASSWORD"),) _(
"No password entered, or found via"
" --os-password or OS_PASSWORD"
),
)
return pw return pw
class OpenStackShell(app.App): class OpenStackShell(app.App):
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
timing_data = [] timing_data = []
def __init__( def __init__(
self, self,
description=None, description=None,
version=None, version=None,
command_manager=None, command_manager=None,
stdin=None, stdin=None,
stdout=None, stdout=None,
stderr=None, stderr=None,
interactive_app_factory=None, interactive_app_factory=None,
deferred_help=False, deferred_help=False,
): ):
# Patch command.Command to add a default auth_required = True # Patch command.Command to add a default auth_required = True
command.Command.auth_required = True command.Command.auth_required = True
@@ -163,10 +166,14 @@ class OpenStackShell(app.App):
# bigger than most big default one (CRITICAL) or something like # bigger than most big default one (CRITICAL) or something like
# that (PROFILE = 60 for instance), but not sure we need it here. # that (PROFILE = 60 for instance), but not sure we need it here.
self.log.warning("Trace ID: %s" % trace_id) self.log.warning("Trace ID: %s" % trace_id)
self.log.warning("Short trace ID " self.log.warning(
"for OpenTracing-based drivers: %s" % short_id) "Short trace ID "
self.log.warning("Display trace data with command:\n" "for OpenTracing-based drivers: %s" % short_id
"osprofiler trace show --html %s " % trace_id) )
self.log.warning(
"Display trace data with command:\n"
"osprofiler trace show --html %s " % trace_id
)
def run_subcommand(self, argv): def run_subcommand(self, argv):
self.init_profile() self.init_profile()
@@ -186,8 +193,8 @@ class OpenStackShell(app.App):
def build_option_parser(self, description, version): def build_option_parser(self, description, version):
parser = super(OpenStackShell, self).build_option_parser( parser = super(OpenStackShell, self).build_option_parser(
description, description, version
version) )
# service token auth argument # service token auth argument
parser.add_argument( parser.add_argument(
@@ -243,11 +250,11 @@ class OpenStackShell(app.App):
'--os-default-domain', '--os-default-domain',
metavar='<auth-domain>', metavar='<auth-domain>',
dest='default_domain', dest='default_domain',
default=utils.env( default=utils.env('OS_DEFAULT_DOMAIN', default=DEFAULT_DOMAIN),
'OS_DEFAULT_DOMAIN', help=_(
default=DEFAULT_DOMAIN), 'Default domain ID, default=%s. ' '(Env: OS_DEFAULT_DOMAIN)'
help=_('Default domain ID, default=%s. ' )
'(Env: OS_DEFAULT_DOMAIN)') % DEFAULT_DOMAIN, % DEFAULT_DOMAIN,
) )
parser.add_argument( parser.add_argument(
'--os-interface', '--os-interface',
@@ -258,18 +265,23 @@ class OpenStackShell(app.App):
# config key 'interface' will be ignored. Use OSC_Config's ctor # config key 'interface' will be ignored. Use OSC_Config's ctor
# option 'override_defaults' below instead. # option 'override_defaults' below instead.
default=utils.env('OS_INTERFACE'), default=utils.env('OS_INTERFACE'),
help=_('Select an interface type.' help=_(
' Valid interface types: [admin, public, internal].' 'Select an interface type.'
' default=%s, (Env: OS_INTERFACE)') % DEFAULT_INTERFACE, ' Valid interface types: [admin, public, internal].'
' default=%s, (Env: OS_INTERFACE)'
)
% DEFAULT_INTERFACE,
) )
parser.add_argument( parser.add_argument(
'--os-service-provider', '--os-service-provider',
metavar='<service_provider>', metavar='<service_provider>',
dest='service_provider', dest='service_provider',
default=utils.env('OS_SERVICE_PROVIDER'), default=utils.env('OS_SERVICE_PROVIDER'),
help=_('Authenticate with and perform the command on a service' help=_(
' provider using Keystone-to-keystone federation. Must' 'Authenticate with and perform the command on a service'
' also specify the remote project option.') ' provider using Keystone-to-keystone federation. Must'
' also specify the remote project option.'
),
) )
remote_project_group = parser.add_mutually_exclusive_group() remote_project_group = parser.add_mutually_exclusive_group()
remote_project_group.add_argument( remote_project_group.add_argument(
@@ -277,16 +289,20 @@ class OpenStackShell(app.App):
metavar='<remote_project_name>', metavar='<remote_project_name>',
dest='remote_project_name', dest='remote_project_name',
default=utils.env('OS_REMOTE_PROJECT_NAME'), default=utils.env('OS_REMOTE_PROJECT_NAME'),
help=_('Project name when authenticating to a service provider' help=_(
' if using Keystone-to-Keystone federation.') 'Project name when authenticating to a service provider'
' if using Keystone-to-Keystone federation.'
),
) )
remote_project_group.add_argument( remote_project_group.add_argument(
'--os-remote-project-id', '--os-remote-project-id',
metavar='<remote_project_id>', metavar='<remote_project_id>',
dest='remote_project_id', dest='remote_project_id',
default=utils.env('OS_REMOTE_PROJECT_ID'), default=utils.env('OS_REMOTE_PROJECT_ID'),
help=_('Project ID when authenticating to a service provider' help=_(
' if using Keystone-to-Keystone federation.') 'Project ID when authenticating to a service provider'
' if using Keystone-to-Keystone federation.'
),
) )
remote_project_domain_group = parser.add_mutually_exclusive_group() remote_project_domain_group = parser.add_mutually_exclusive_group()
remote_project_domain_group.add_argument( remote_project_domain_group.add_argument(
@@ -294,18 +310,22 @@ class OpenStackShell(app.App):
metavar='<remote_project_domain_name>', metavar='<remote_project_domain_name>',
dest='remote_project_domain_name', dest='remote_project_domain_name',
default=utils.env('OS_REMOTE_PROJECT_DOMAIN_NAME'), default=utils.env('OS_REMOTE_PROJECT_DOMAIN_NAME'),
help=_('Domain name of the project when authenticating to a' help=_(
' service provider if using Keystone-to-Keystone' 'Domain name of the project when authenticating to a'
' federation.') ' service provider if using Keystone-to-Keystone'
' federation.'
),
) )
remote_project_domain_group.add_argument( remote_project_domain_group.add_argument(
'--os-remote-project-domain-id', '--os-remote-project-domain-id',
metavar='<remote_project_domain_id>', metavar='<remote_project_domain_id>',
dest='remote_project_domain_id', dest='remote_project_domain_id',
default=utils.env('OS_REMOTE_PROJECT_DOMAIN_ID'), default=utils.env('OS_REMOTE_PROJECT_DOMAIN_ID'),
help=_('Domain ID of the project when authenticating to a' help=_(
' service provider if using Keystone-to-Keystone' 'Domain ID of the project when authenticating to a'
' federation.') ' service provider if using Keystone-to-Keystone'
' federation.'
),
) )
parser.add_argument( parser.add_argument(
'--timing', '--timing',
@@ -345,6 +365,7 @@ class OpenStackShell(app.App):
* ClientManager * ClientManager
""" """
def _final_defaults(self): def _final_defaults(self):
# Set the default plugin to None # Set the default plugin to None
# NOTE(dtroyer): This is here to set up for setting it to a default # NOTE(dtroyer): This is here to set up for setting it to a default
@@ -396,10 +417,11 @@ class OpenStackShell(app.App):
# Parent __init__ parses argv into self.options # Parent __init__ parses argv into self.options
super(OpenStackShell, self).initialize_app(argv) super(OpenStackShell, self).initialize_app(argv)
self.log.info("START with options: %s", self.log.info(
strutils.mask_password(" ".join(self.command_options))) "START with options: %s",
self.log.debug("options: %s", strutils.mask_password(" ".join(self.command_options)),
strutils.mask_password(self.options)) )
self.log.debug("options: %s", strutils.mask_password(self.options))
# Callout for stuff between superclass init and o-c-c # Callout for stuff between superclass init and o-c-c
self._final_defaults() self._final_defaults()
@@ -434,8 +456,9 @@ class OpenStackShell(app.App):
self.log_configurator.configure(self.cloud) self.log_configurator.configure(self.cloud)
self.dump_stack_trace = self.log_configurator.dump_trace self.dump_stack_trace = self.log_configurator.dump_trace
self.log.debug("defaults: %s", self.cloud_config.defaults) self.log.debug("defaults: %s", self.cloud_config.defaults)
self.log.debug("cloud cfg: %s", self.log.debug(
strutils.mask_password(self.cloud.config)) "cloud cfg: %s", strutils.mask_password(self.cloud.config)
)
# Callout for stuff between o-c-c and ClientManager # Callout for stuff between o-c-c and ClientManager
# self._initialize_app_2(self.options) # self._initialize_app_2(self.options)
@@ -489,8 +512,9 @@ class OpenStackShell(app.App):
# let the command decide whether we need a scoped token # let the command decide whether we need a scoped token
self.client_manager.validate_scope() self.client_manager.validate_scope()
# Trigger the Identity client to initialize # Trigger the Identity client to initialize
self.client_manager.session.auth.auth_ref = \ self.client_manager.session.auth.auth_ref = (
self.client_manager.auth_ref self.client_manager.auth_ref
)
return return
def clean_up(self, cmd, result, err): def clean_up(self, cmd, result, err):
@@ -518,8 +542,10 @@ class OpenStackShell(app.App):
# If anything other than prettytable is specified, force csv # If anything other than prettytable is specified, force csv
format = 'table' format = 'table'
# Check the formatter used in the actual command # Check the formatter used in the actual command
if hasattr(cmd, 'formatter') \ if (
and cmd.formatter != cmd._formatter_plugins['table'].obj: hasattr(cmd, 'formatter')
and cmd.formatter != cmd._formatter_plugins['table'].obj
):
format = 'csv' format = 'csv'
sys.stdout.write('\n') sys.stdout.write('\n')

View File

@@ -47,7 +47,6 @@ LIST_BODY = {
class TestSession(utils.TestCase): class TestSession(utils.TestCase):
BASE_URL = 'https://api.example.com:1234/test' BASE_URL = 'https://api.example.com:1234/test'
def setUp(self): def setUp(self):

View File

@@ -22,7 +22,6 @@ from osc_lib.tests.api import fakes as api_fakes
class TestBaseAPIDefault(api_fakes.TestSession): class TestBaseAPIDefault(api_fakes.TestSession):
def setUp(self): def setUp(self):
super(TestBaseAPIDefault, self).setUp() super(TestBaseAPIDefault, self).setUp()
self.api = api.BaseAPI() self.api = api.BaseAPI()
@@ -89,7 +88,6 @@ class TestBaseAPIDefault(api_fakes.TestSession):
class TestBaseAPIEndpointArg(api_fakes.TestSession): class TestBaseAPIEndpointArg(api_fakes.TestSession):
def test_baseapi_endpoint_no_endpoint(self): def test_baseapi_endpoint_no_endpoint(self):
x_api = api.BaseAPI( x_api = api.BaseAPI(
session=self.sess, session=self.sess,
@@ -187,7 +185,6 @@ class TestBaseAPIEndpointArg(api_fakes.TestSession):
class TestBaseAPIArgs(api_fakes.TestSession): class TestBaseAPIArgs(api_fakes.TestSession):
def setUp(self): def setUp(self):
super(TestBaseAPIArgs, self).setUp() super(TestBaseAPIArgs, self).setUp()
self.api = api.BaseAPI( self.api = api.BaseAPI(
@@ -222,7 +219,6 @@ class TestBaseAPIArgs(api_fakes.TestSession):
class TestBaseAPICreate(api_fakes.TestSession): class TestBaseAPICreate(api_fakes.TestSession):
def setUp(self): def setUp(self):
super(TestBaseAPICreate, self).setUp() super(TestBaseAPICreate, self).setUp()
self.api = api.BaseAPI( self.api = api.BaseAPI(
@@ -261,7 +257,6 @@ class TestBaseAPICreate(api_fakes.TestSession):
class TestBaseAPIFind(api_fakes.TestSession): class TestBaseAPIFind(api_fakes.TestSession):
def setUp(self): def setUp(self):
super(TestBaseAPIFind, self).setUp() super(TestBaseAPIFind, self).setUp()
self.api = api.BaseAPI( self.api = api.BaseAPI(
@@ -283,14 +278,9 @@ class TestBaseAPIFind(api_fakes.TestSession):
self.BASE_URL + '/qaz/1', self.BASE_URL + '/qaz/1',
status_code=404, status_code=404,
) )
self.assertRaises( self.assertRaises(exceptions.NotFound, self.api.find, 'qaz', '1')
exceptions.NotFound,
self.api.find,
'qaz',
'1')
def test_baseapi_find_attr_by_id(self): def test_baseapi_find_attr_by_id(self):
# All first requests (by name) will fail in this test # All first requests (by name) will fail in this test
self.requests_mock.register_uri( self.requests_mock.register_uri(
'GET', 'GET',
@@ -382,7 +372,6 @@ class TestBaseAPIFind(api_fakes.TestSession):
self.assertEqual(api_fakes.RESP_ITEM_1, ret) self.assertEqual(api_fakes.RESP_ITEM_1, ret)
def test_baseapi_find_attr_path_resource(self): def test_baseapi_find_attr_path_resource(self):
# Test resource different than path # Test resource different than path
self.requests_mock.register_uri( self.requests_mock.register_uri(
'GET', 'GET',
@@ -462,7 +451,6 @@ class TestBaseAPIFind(api_fakes.TestSession):
class TestBaseAPIList(api_fakes.TestSession): class TestBaseAPIList(api_fakes.TestSession):
def setUp(self): def setUp(self):
super(TestBaseAPIList, self).setUp() super(TestBaseAPIList, self).setUp()
self.api = api.BaseAPI( self.api = api.BaseAPI(

View File

@@ -37,8 +37,7 @@ class TestBaseAPIFilter(api_fakes.TestSession):
] ]
def test_simple_filter_none(self): def test_simple_filter_none(self):
output = api_utils.simple_filter( output = api_utils.simple_filter()
)
self.assertIsNone(output) self.assertIsNone(output)
def test_simple_filter_no_attr(self): def test_simple_filter_no_attr(self):

View File

@@ -16,7 +16,6 @@ from osc_lib.tests import utils
class TestOSCConfig(utils.TestCase): class TestOSCConfig(utils.TestCase):
def setUp(self): def setUp(self):
super(TestOSCConfig, self).setUp() super(TestOSCConfig, self).setUp()
@@ -209,7 +208,7 @@ class TestOSCConfig(utils.TestCase):
'username': 'fred', 'username': 'fred',
'project_id': 'id', 'project_id': 'id',
'project_domain_id': 'proj', 'project_domain_id': 'proj',
'user_domain_id': 'use' 'user_domain_id': 'use',
}, },
} }
ret_config = self.cloud._auth_default_domain(config) ret_config = self.cloud._auth_default_domain(config)

View File

@@ -20,7 +20,6 @@ from osc_lib.tests import utils
class TestDictColumn(utils.TestCase): class TestDictColumn(utils.TestCase):
def test_dict_column(self): def test_dict_column(self):
data = { data = {
'key1': 'value1', 'key1': 'value1',
@@ -42,7 +41,6 @@ class TestDictColumn(utils.TestCase):
class TestDictListColumn(utils.TestCase): class TestDictListColumn(utils.TestCase):
def test_dict_list_column(self): def test_dict_list_column(self):
data = { data = {
'public': ['2001:db8::8', '172.24.4.6'], 'public': ['2001:db8::8', '172.24.4.6'],
@@ -67,7 +65,6 @@ class TestDictListColumn(utils.TestCase):
class TestListColumn(utils.TestCase): class TestListColumn(utils.TestCase):
def test_list_column(self): def test_list_column(self):
data = [ data = [
'key1', 'key1',
@@ -87,7 +84,6 @@ class TestListColumn(utils.TestCase):
class TestListDictColumn(utils.TestCase): class TestListDictColumn(utils.TestCase):
def test_list_dict_column(self): def test_list_dict_column(self):
data = [ data = [
{'key1': 'value1'}, {'key1': 'value1'},
@@ -112,7 +108,6 @@ class TestListDictColumn(utils.TestCase):
class TestSizeColumn(utils.TestCase): class TestSizeColumn(utils.TestCase):
def test_size_column(self): def test_size_column(self):
content = 1576395005 content = 1576395005
col = format_columns.SizeColumn(content) col = format_columns.SizeColumn(content)

View File

@@ -22,12 +22,12 @@ from osc_lib.tests import utils as test_utils
class IdentityUtilsTestCase(test_utils.TestCase): class IdentityUtilsTestCase(test_utils.TestCase):
def test_add_project_owner_option_to_parser(self): def test_add_project_owner_option_to_parser(self):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
cli_identity.add_project_owner_option_to_parser(parser) cli_identity.add_project_owner_option_to_parser(parser)
parsed_args = parser.parse_args(['--project', 'project1', parsed_args = parser.parse_args(
'--project-domain', 'domain1']) ['--project', 'project1', '--project-domain', 'domain1']
)
self.assertEqual('project1', parsed_args.project) self.assertEqual('project1', parsed_args.project)
self.assertEqual('domain1', parsed_args.project_domain) self.assertEqual('domain1', parsed_args.project_domain)
@@ -39,7 +39,8 @@ class IdentityUtilsTestCase(test_utils.TestCase):
ret = cli_identity.find_project(sdk_connection, 'project1') ret = cli_identity.find_project(sdk_connection, 'project1')
self.assertEqual(mock.sentinel.project1, ret) self.assertEqual(mock.sentinel.project1, ret)
sdk_find_project.assert_called_once_with( sdk_find_project.assert_called_once_with(
'project1', ignore_missing=False, domain_id=None) 'project1', ignore_missing=False, domain_id=None
)
def test_find_project_with_domain(self): def test_find_project_with_domain(self):
domain1 = mock.Mock() domain1 = mock.Mock()
@@ -54,9 +55,11 @@ class IdentityUtilsTestCase(test_utils.TestCase):
ret = cli_identity.find_project(sdk_connection, 'project1', 'domain1') ret = cli_identity.find_project(sdk_connection, 'project1', 'domain1')
self.assertEqual(mock.sentinel.project1, ret) self.assertEqual(mock.sentinel.project1, ret)
sdk_find_domain.assert_called_once_with( sdk_find_domain.assert_called_once_with(
'domain1', ignore_missing=False) 'domain1', ignore_missing=False
)
sdk_find_project.assert_called_once_with( sdk_find_project.assert_called_once_with(
'project1', ignore_missing=False, domain_id='id-domain1') 'project1', ignore_missing=False, domain_id='id-domain1'
)
def test_find_project_with_forbidden_exception(self): def test_find_project_with_forbidden_exception(self):
sdk_connection = mock.Mock() sdk_connection = mock.Mock()

View File

@@ -20,7 +20,6 @@ from osc_lib.tests import utils
class TestKeyValueAction(utils.TestCase): class TestKeyValueAction(utils.TestCase):
def setUp(self): def setUp(self):
super(TestKeyValueAction, self).setUp() super(TestKeyValueAction, self).setUp()
@@ -33,15 +32,20 @@ class TestKeyValueAction(utils.TestCase):
action=parseractions.KeyValueAction, action=parseractions.KeyValueAction,
default={'green': '20%', 'format': '#rgb'}, default={'green': '20%', 'format': '#rgb'},
help='Property to store for this volume ' help='Property to store for this volume '
'(repeat option to set multiple properties)', '(repeat option to set multiple properties)',
) )
def test_good_values(self): def test_good_values(self):
results = self.parser.parse_args([ results = self.parser.parse_args(
'--property', 'red=', [
'--property', 'green=100%', '--property',
'--property', 'blue=50%', 'red=',
]) '--property',
'green=100%',
'--property',
'blue=50%',
]
)
actual = getattr(results, 'property', {}) actual = getattr(results, 'property', {})
# All should pass through unmolested # All should pass through unmolested
@@ -50,17 +54,26 @@ class TestKeyValueAction(utils.TestCase):
def test_error_values(self): def test_error_values(self):
data_list = [ data_list = [
['--property', 'red', ], [
['--property', '=', ], '--property',
['--property', '=red', ] 'red',
],
[
'--property',
'=',
],
[
'--property',
'=red',
],
] ]
for data in data_list: for data in data_list:
self.assertRaises(argparse.ArgumentTypeError, self.assertRaises(
self.parser.parse_args, data) argparse.ArgumentTypeError, self.parser.parse_args, data
)
class TestKeyValueAppendAction(utils.TestCase): class TestKeyValueAppendAction(utils.TestCase):
def setUp(self): def setUp(self):
super(TestKeyValueAppendAction, self).setUp() super(TestKeyValueAppendAction, self).setUp()
@@ -72,16 +85,21 @@ class TestKeyValueAppendAction(utils.TestCase):
metavar='<key=value>', metavar='<key=value>',
action=parseractions.KeyValueAppendAction, action=parseractions.KeyValueAppendAction,
help='Arbitrary key/value pairs to be sent to the scheduler for ' help='Arbitrary key/value pairs to be sent to the scheduler for '
'custom use', 'custom use',
) )
def test_good_values(self): def test_good_values(self):
print(self.parser._get_optional_actions()) print(self.parser._get_optional_actions())
results = self.parser.parse_args([ results = self.parser.parse_args(
'--hint', 'same_host=a0cf03a5-d921-4877-bb5c-86d26cf818e1', [
'--hint', 'same_host=8c19174f-4220-44f0-824a-cd1eeef10287', '--hint',
'--hint', 'query=[>=,$free_ram_mb,1024]', 'same_host=a0cf03a5-d921-4877-bb5c-86d26cf818e1',
]) '--hint',
'same_host=8c19174f-4220-44f0-824a-cd1eeef10287',
'--hint',
'query=[>=,$free_ram_mb,1024]',
]
)
actual = getattr(results, 'hint', {}) actual = getattr(results, 'hint', {})
expect = { expect = {
@@ -97,17 +115,26 @@ class TestKeyValueAppendAction(utils.TestCase):
def test_error_values(self): def test_error_values(self):
data_list = [ data_list = [
['--hint', 'red', ], [
['--hint', '=', ], '--hint',
['--hint', '=red', ] 'red',
],
[
'--hint',
'=',
],
[
'--hint',
'=red',
],
] ]
for data in data_list: for data in data_list:
self.assertRaises(argparse.ArgumentTypeError, self.assertRaises(
self.parser.parse_args, data) argparse.ArgumentTypeError, self.parser.parse_args, data
)
class TestMultiKeyValueAction(utils.TestCase): class TestMultiKeyValueAction(utils.TestCase):
def setUp(self): def setUp(self):
super(TestMultiKeyValueAction, self).setUp() super(TestMultiKeyValueAction, self).setUp()
@@ -122,14 +149,18 @@ class TestMultiKeyValueAction(utils.TestCase):
default=None, default=None,
required_keys=['req1', 'req2'], required_keys=['req1', 'req2'],
optional_keys=['opt1', 'opt2'], optional_keys=['opt1', 'opt2'],
help='Test' help='Test',
) )
def test_good_values(self): def test_good_values(self):
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,req2=bbb', [
'--test', 'req1=,req2=', '--test',
]) 'req1=aaa,req2=bbb',
'--test',
'req1=,req2=',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [ expect = [
@@ -147,13 +178,17 @@ class TestMultiKeyValueAction(utils.TestCase):
default=None, default=None,
required_keys=[], required_keys=[],
optional_keys=[], optional_keys=[],
help='Test' help='Test',
) )
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test-empty', 'req1=aaa,req2=bbb', [
'--test-empty', 'req1=,req2=', '--test-empty',
]) 'req1=aaa,req2=bbb',
'--test-empty',
'req1=,req2=',
]
)
actual = getattr(results, 'test_empty', []) actual = getattr(results, 'test_empty', [])
expect = [ expect = [
@@ -164,21 +199,32 @@ class TestMultiKeyValueAction(utils.TestCase):
def test_error_values_with_comma(self): def test_error_values_with_comma(self):
data_list = [ data_list = [
['--test', 'mmm,nnn=zzz', ], [
['--test', 'nnn=zzz,=', ], '--test',
['--test', 'nnn=zzz,=zzz', ] 'mmm,nnn=zzz',
],
[
'--test',
'nnn=zzz,=',
],
[
'--test',
'nnn=zzz,=zzz',
],
] ]
for data in data_list: for data in data_list:
self.assertRaises(argparse.ArgumentTypeError, self.assertRaises(
self.parser.parse_args, data) argparse.ArgumentTypeError, self.parser.parse_args, data
)
def test_error_values_without_comma(self): def test_error_values_without_comma(self):
self.assertRaises( self.assertRaises(
argparse.ArgumentTypeError, argparse.ArgumentTypeError,
self.parser.parse_args, self.parser.parse_args,
[ [
'--test', 'mmmnnn', '--test',
] 'mmmnnn',
],
) )
def test_missing_key(self): def test_missing_key(self):
@@ -186,8 +232,9 @@ class TestMultiKeyValueAction(utils.TestCase):
argparse.ArgumentTypeError, argparse.ArgumentTypeError,
self.parser.parse_args, self.parser.parse_args,
[ [
'--test', 'req2=ddd', '--test',
] 'req2=ddd',
],
) )
def test_invalid_key(self): def test_invalid_key(self):
@@ -195,8 +242,9 @@ class TestMultiKeyValueAction(utils.TestCase):
argparse.ArgumentTypeError, argparse.ArgumentTypeError,
self.parser.parse_args, self.parser.parse_args,
[ [
'--test', 'req1=aaa,req2=bbb,aaa=req1', '--test',
] 'req1=aaa,req2=bbb,aaa=req1',
],
) )
def test_required_keys_not_list(self): def test_required_keys_not_list(self):
@@ -210,7 +258,7 @@ class TestMultiKeyValueAction(utils.TestCase):
default=None, default=None,
required_keys={'aaa': 'bbb'}, required_keys={'aaa': 'bbb'},
optional_keys=['opt1', 'opt2'], optional_keys=['opt1', 'opt2'],
help='Test' help='Test',
) )
def test_optional_keys_not_list(self): def test_optional_keys_not_list(self):
@@ -224,12 +272,11 @@ class TestMultiKeyValueAction(utils.TestCase):
default=None, default=None,
required_keys=['req1', 'req2'], required_keys=['req1', 'req2'],
optional_keys={'aaa': 'bbb'}, optional_keys={'aaa': 'bbb'},
help='Test' help='Test',
) )
class TestMultiKeyValueCommaAction(utils.TestCase): class TestMultiKeyValueCommaAction(utils.TestCase):
def setUp(self): def setUp(self):
super(TestMultiKeyValueCommaAction, self).setUp() super(TestMultiKeyValueCommaAction, self).setUp()
self.parser = argparse.ArgumentParser() self.parser = argparse.ArgumentParser()
@@ -247,28 +294,38 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
) )
def test_mkvca_required(self): def test_mkvca_required(self):
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,bbb', [
]) '--test',
'req1=aaa,bbb',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [ expect = [
{'req1': 'aaa,bbb'}, {'req1': 'aaa,bbb'},
] ]
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=', [
]) '--test',
'req1=',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [ expect = [
{'req1': ''}, {'req1': ''},
] ]
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,bbb', [
'--test', 'req1=', '--test',
]) 'req1=aaa,bbb',
'--test',
'req1=',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [ expect = [
{'req1': 'aaa,bbb'}, {'req1': 'aaa,bbb'},
@@ -277,19 +334,26 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
def test_mkvca_optional(self): def test_mkvca_optional(self):
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,bbb', [
]) '--test',
'req1=aaa,bbb',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [ expect = [
{'req1': 'aaa,bbb'}, {'req1': 'aaa,bbb'},
] ]
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,bbb', [
'--test', 'req1=,opt2=ccc', '--test',
]) 'req1=aaa,bbb',
'--test',
'req1=,opt2=ccc',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [ expect = [
{'req1': 'aaa,bbb'}, {'req1': 'aaa,bbb'},
@@ -298,10 +362,14 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
try: try:
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,bbb', [
'--test', 'opt2=ccc', '--test',
]) 'req1=aaa,bbb',
'--test',
'opt2=ccc',
]
)
self.fail('ArgumentTypeError should be raised') self.fail('ArgumentTypeError should be raised')
except argparse.ArgumentTypeError as e: except argparse.ArgumentTypeError as e:
self.assertEqual( self.assertEqual(
@@ -310,14 +378,19 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
) )
def test_mkvca_multiples(self): def test_mkvca_multiples(self):
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test', 'req1=aaa,bbb,opt2=ccc', [
]) '--test',
'req1=aaa,bbb,opt2=ccc',
]
)
actual = getattr(results, 'test', []) actual = getattr(results, 'test', [])
expect = [{ expect = [
'req1': 'aaa,bbb', {
'opt2': 'ccc', 'req1': 'aaa,bbb',
}] 'opt2': 'ccc',
}
]
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
def test_mkvca_no_required_optional(self): def test_mkvca_no_required_optional(self):
@@ -332,18 +405,24 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
help='Test', help='Test',
) )
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test-empty', 'req1=aaa,bbb', [
]) '--test-empty',
'req1=aaa,bbb',
]
)
actual = getattr(results, 'test_empty', []) actual = getattr(results, 'test_empty', [])
expect = [ expect = [
{'req1': 'aaa,bbb'}, {'req1': 'aaa,bbb'},
] ]
self.assertCountEqual(expect, actual) self.assertCountEqual(expect, actual)
results = self.parser.parse_args([ results = self.parser.parse_args(
'--test-empty', 'xyz=aaa,bbb', [
]) '--test-empty',
'xyz=aaa,bbb',
]
)
actual = getattr(results, 'test_empty', []) actual = getattr(results, 'test_empty', [])
expect = [ expect = [
@@ -353,9 +432,12 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
def test_mkvca_invalid_key(self): def test_mkvca_invalid_key(self):
try: try:
self.parser.parse_args([ self.parser.parse_args(
'--test', 'req1=aaa,bbb=', [
]) '--test',
'req1=aaa,bbb=',
]
)
self.fail('ArgumentTypeError should be raised') self.fail('ArgumentTypeError should be raised')
except argparse.ArgumentTypeError as e: except argparse.ArgumentTypeError as e:
self.assertIn( self.assertIn(
@@ -364,9 +446,12 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
) )
try: try:
self.parser.parse_args([ self.parser.parse_args(
'--test', 'nnn=aaa', [
]) '--test',
'nnn=aaa',
]
)
self.fail('ArgumentTypeError should be raised') self.fail('ArgumentTypeError should be raised')
except argparse.ArgumentTypeError as e: except argparse.ArgumentTypeError as e:
self.assertIn( self.assertIn(
@@ -376,9 +461,12 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
def test_mkvca_value_no_key(self): def test_mkvca_value_no_key(self):
try: try:
self.parser.parse_args([ self.parser.parse_args(
'--test', 'req1=aaa,=bbb', [
]) '--test',
'req1=aaa,=bbb',
]
)
self.fail('ArgumentTypeError should be raised') self.fail('ArgumentTypeError should be raised')
except argparse.ArgumentTypeError as e: except argparse.ArgumentTypeError as e:
self.assertEqual( self.assertEqual(
@@ -386,9 +474,12 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
str(e), str(e),
) )
try: try:
self.parser.parse_args([ self.parser.parse_args(
'--test', '=nnn', [
]) '--test',
'=nnn',
]
)
self.fail('ArgumentTypeError should be raised') self.fail('ArgumentTypeError should be raised')
except argparse.ArgumentTypeError as e: except argparse.ArgumentTypeError as e:
self.assertEqual( self.assertEqual(
@@ -397,9 +488,12 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
) )
try: try:
self.parser.parse_args([ self.parser.parse_args(
'--test', 'nnn', [
]) '--test',
'nnn',
]
)
self.fail('ArgumentTypeError should be raised') self.fail('ArgumentTypeError should be raised')
except argparse.ArgumentTypeError as e: except argparse.ArgumentTypeError as e:
self.assertIn( self.assertIn(
@@ -437,7 +531,6 @@ class TestMultiKeyValueCommaAction(utils.TestCase):
class TestNonNegativeAction(utils.TestCase): class TestNonNegativeAction(utils.TestCase):
def setUp(self): def setUp(self):
super(TestNonNegativeAction, self).setUp() super(TestNonNegativeAction, self).setUp()
@@ -455,21 +548,17 @@ class TestNonNegativeAction(utils.TestCase):
self.assertRaises( self.assertRaises(
argparse.ArgumentTypeError, argparse.ArgumentTypeError,
self.parser.parse_args, self.parser.parse_args,
"--foo -1".split() "--foo -1".split(),
) )
def test_zero_values(self): def test_zero_values(self):
results = self.parser.parse_args( results = self.parser.parse_args('--foo 0'.split())
'--foo 0'.split()
)
actual = getattr(results, 'foo', None) actual = getattr(results, 'foo', None)
self.assertEqual(actual, 0) self.assertEqual(actual, 0)
def test_positive_values(self): def test_positive_values(self):
results = self.parser.parse_args( results = self.parser.parse_args('--foo 1'.split())
'--foo 1'.split()
)
actual = getattr(results, 'foo', None) actual = getattr(results, 'foo', None)
self.assertEqual(actual, 1) self.assertEqual(actual, 1)

View File

@@ -21,13 +21,11 @@ from osc_lib.tests import utils as test_utils
class FakeCommand(command.Command): class FakeCommand(command.Command):
def take_action(self, parsed_args): def take_action(self, parsed_args):
pass pass
class TestCommand(test_utils.TestCase): class TestCommand(test_utils.TestCase):
def test_command_has_logger(self): def test_command_has_logger(self):
cmd = FakeCommand(mock.Mock(), mock.Mock()) cmd = FakeCommand(mock.Mock(), mock.Mock())
self.assertTrue(hasattr(cmd, 'log')) self.assertTrue(hasattr(cmd, 'log'))

View File

@@ -28,14 +28,12 @@ timing_elapsed = 0.872809
class FakeGenericClient(object): class FakeGenericClient(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.auth_token = kwargs['token'] self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint'] self.management_url = kwargs['endpoint']
class TestTiming(utils.TestCommand): class TestTiming(utils.TestCommand):
columns = ( columns = (
'URL', 'URL',
'Seconds', 'Seconds',
@@ -71,16 +69,23 @@ class TestTiming(utils.TestCommand):
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
datalist = [ datalist = [
('Total', 0.0,) (
'Total',
0.0,
)
] ]
self.assertEqual(datalist, data) self.assertEqual(datalist, data)
def test_timing_list(self): def test_timing_list(self):
self.app.timing_data = [session.RequestTiming( self.app.timing_data = [
method=timing_method, session.RequestTiming(
url=timing_url, method=timing_method,
elapsed=datetime.timedelta(microseconds=timing_elapsed * 1000000), url=timing_url,
)] elapsed=datetime.timedelta(
microseconds=timing_elapsed * 1000000
),
)
]
arglist = [] arglist = []
verifylist = [] verifylist = []

View File

@@ -32,8 +32,7 @@ VERSION = "3"
SERVICE_PROVIDER_ID = "bob" SERVICE_PROVIDER_ID = "bob"
TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME)
user_name=USERNAME)
_s = TEST_RESPONSE_DICT.add_service('identity', name='keystone') _s = TEST_RESPONSE_DICT.add_service('identity', name='keystone')
_s.add_endpoint(AUTH_URL + ':5000/v2.0') _s.add_endpoint(AUTH_URL + ':5000/v2.0')
_s = TEST_RESPONSE_DICT.add_service('network', name='neutron') _s = TEST_RESPONSE_DICT.add_service('network', name='neutron')
@@ -54,8 +53,10 @@ TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL)
def to_unicode_dict(catalog_dict): def to_unicode_dict(catalog_dict):
"""Converts dict to unicode dict""" """Converts dict to unicode dict"""
if isinstance(catalog_dict, dict): if isinstance(catalog_dict, dict):
return {to_unicode_dict(key): to_unicode_dict(value) return {
for key, value in catalog_dict.items()} to_unicode_dict(key): to_unicode_dict(value)
for key, value in catalog_dict.items()
}
elif isinstance(catalog_dict, list): elif isinstance(catalog_dict, list):
return [to_unicode_dict(element) for element in catalog_dict] return [to_unicode_dict(element) for element in catalog_dict]
elif isinstance(catalog_dict, str): elif isinstance(catalog_dict, str):
@@ -65,7 +66,6 @@ def to_unicode_dict(catalog_dict):
class FakeStdout(object): class FakeStdout(object):
def __init__(self): def __init__(self):
self.content = [] self.content = []
@@ -80,7 +80,6 @@ class FakeStdout(object):
class FakeLog(object): class FakeLog(object):
def __init__(self): def __init__(self):
self.messages = {} self.messages = {}
@@ -101,7 +100,6 @@ class FakeLog(object):
class FakeApp(object): class FakeApp(object):
def __init__(self, _stdout, _log): def __init__(self, _stdout, _log):
self.stdout = _stdout self.stdout = _stdout
self.client_manager = None self.client_manager = None
@@ -118,7 +116,6 @@ class FakeOptions(object):
class FakeClientManager(object): class FakeClientManager(object):
def __init__(self): def __init__(self):
self.compute = None self.compute = None
self.identity = None self.identity = None
@@ -143,7 +140,6 @@ class FakeClientManager(object):
class FakeModule(object): class FakeModule(object):
def __init__(self, name, version): def __init__(self, name, version):
self.name = name self.name = name
self.__version__ = version self.__version__ = version
@@ -153,7 +149,6 @@ class FakeModule(object):
class FakeResource(object): class FakeResource(object):
def __init__(self, manager=None, info=None, loaded=False, methods=None): def __init__(self, manager=None, info=None, loaded=False, methods=None):
"""Set attributes and methods for a resource. """Set attributes and methods for a resource.
@@ -177,7 +172,7 @@ class FakeResource(object):
self._loaded = loaded self._loaded = loaded
def _add_details(self, info): def _add_details(self, info):
for (k, v) in info.items(): for k, v in info.items():
setattr(self, k, v) setattr(self, k, v)
def _add_methods(self, methods): def _add_methods(self, methods):
@@ -188,13 +183,14 @@ class FakeResource(object):
@value. When users access the attribute with (), @value will be @value. When users access the attribute with (), @value will be
returned, which looks like a function call. returned, which looks like a function call.
""" """
for (name, ret) in methods.items(): for name, ret in methods.items():
method = mock.MagicMock(return_value=ret) method = mock.MagicMock(return_value=ret)
setattr(self, name, method) setattr(self, name, method)
def __repr__(self): def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and reprkeys = sorted(
k != 'manager') k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager'
)
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info) return "<%s %s>" % (self.__class__.__name__, info)

View File

@@ -41,7 +41,7 @@ AUTH_DICT = {
'auth_url': fakes.AUTH_URL, 'auth_url': fakes.AUTH_URL,
'username': fakes.USERNAME, 'username': fakes.USERNAME,
'password': fakes.PASSWORD, 'password': fakes.PASSWORD,
'project_name': fakes.PROJECT_NAME 'project_name': fakes.PROJECT_NAME,
} }
@@ -58,7 +58,6 @@ class Container(object):
class TestClientCache(utils.TestCase): class TestClientCache(utils.TestCase):
def test_singleton(self): def test_singleton(self):
# NOTE(dtroyer): Verify that the ClientCache descriptor only invokes # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes
# the factory one time and always returns the same value after that. # the factory one time and always returns the same value after that.
@@ -67,14 +66,14 @@ class TestClientCache(utils.TestCase):
def test_attribute_error_propagates(self): def test_attribute_error_propagates(self):
c = Container() c = Container()
err = self.assertRaises(exc.PluginAttributeError, err = self.assertRaises(
getattr, c, 'buggy_attr') exc.PluginAttributeError, getattr, c, 'buggy_attr'
)
self.assertNotIsInstance(err, AttributeError) self.assertNotIsInstance(err, AttributeError)
self.assertEqual("'Container' object has no attribute 'foo'", str(err)) self.assertEqual("'Container' object has no attribute 'foo'", str(err))
class TestClientManager(utils.TestClientManager): class TestClientManager(utils.TestClientManager):
def test_client_manager_none(self): def test_client_manager_none(self):
none_auth = { none_auth = {
'endpoint': fakes.AUTH_URL, 'endpoint': fakes.AUTH_URL,
@@ -268,10 +267,12 @@ class TestClientManager(utils.TestClientManager):
) )
auth_args = copy.deepcopy(self.default_password_auth) auth_args = copy.deepcopy(self.default_password_auth)
auth_args.update({ auth_args.update(
'user_domain_name': 'default', {
'project_domain_name': 'default', 'user_domain_name': 'default',
}) 'project_domain_name': 'default',
}
)
self._make_clientmanager( self._make_clientmanager(
auth_args=auth_args, auth_args=auth_args,
identity_api_version='3', identity_api_version='3',
@@ -292,10 +293,12 @@ class TestClientManager(utils.TestClientManager):
# Use v3 auth args # Use v3 auth args
auth_args = copy.deepcopy(self.default_password_auth) auth_args = copy.deepcopy(self.default_password_auth)
auth_args.update({ auth_args.update(
'user_domain_name': 'default', {
'project_domain_name': 'default', 'user_domain_name': 'default',
}) 'project_domain_name': 'default',
}
)
self._make_clientmanager( self._make_clientmanager(
auth_args=auth_args, auth_args=auth_args,
identity_api_version='3', identity_api_version='3',
@@ -303,9 +306,11 @@ class TestClientManager(utils.TestClientManager):
auth_args = copy.deepcopy(self.default_password_auth) auth_args = copy.deepcopy(self.default_password_auth)
auth_args.pop('username') auth_args.pop('username')
auth_args.update({ auth_args.update(
'user_id': fakes.USER_ID, {
}) 'user_id': fakes.USER_ID,
}
)
self._make_clientmanager( self._make_clientmanager(
auth_args=auth_args, auth_args=auth_args,
identity_api_version='3', identity_api_version='3',
@@ -342,12 +347,14 @@ class TestClientManager(utils.TestClientManager):
loader = loading.get_plugin_loader('password') loader = loading.get_plugin_loader('password')
auth_plugin = loader.load_from_options(**AUTH_DICT) auth_plugin = loader.load_from_options(**AUTH_DICT)
cli_options = defaults.get_defaults() cli_options = defaults.get_defaults()
cli_options.update({ cli_options.update(
'auth_type': 'password', {
'auth': AUTH_DICT, 'auth_type': 'password',
'interface': fakes.INTERFACE, 'auth': AUTH_DICT,
'region_name': fakes.REGION_NAME, 'interface': fakes.INTERFACE,
}) 'region_name': fakes.REGION_NAME,
}
)
client_manager = self._clientmanager_class()( client_manager = self._clientmanager_class()(
cli_options=cloud_config.CloudConfig( cli_options=cloud_config.CloudConfig(
name='t1', name='t1',
@@ -372,10 +379,12 @@ class TestClientManager(utils.TestClientManager):
def test_client_manager_endpoint_disabled(self): def test_client_manager_endpoint_disabled(self):
auth_args = copy.deepcopy(self.default_password_auth) auth_args = copy.deepcopy(self.default_password_auth)
auth_args.update({ auth_args.update(
'user_domain_name': 'default', {
'project_domain_name': 'default', 'user_domain_name': 'default',
}) 'project_domain_name': 'default',
}
)
# v3 fake doesn't have network endpoint # v3 fake doesn't have network endpoint
client_manager = self._make_clientmanager( client_manager = self._make_clientmanager(
auth_args=auth_args, auth_args=auth_args,
@@ -389,14 +398,16 @@ class TestClientManager(utils.TestClientManager):
loader = loading.get_plugin_loader('password') loader = loading.get_plugin_loader('password')
auth_plugin = loader.load_from_options(**AUTH_DICT) auth_plugin = loader.load_from_options(**AUTH_DICT)
cli_options = defaults.get_defaults() cli_options = defaults.get_defaults()
cli_options.update({ cli_options.update(
'auth_type': 'password', {
'auth': AUTH_DICT, 'auth_type': 'password',
'interface': fakes.INTERFACE, 'auth': AUTH_DICT,
'region_name': fakes.REGION_NAME, 'interface': fakes.INTERFACE,
'service_provider': fakes.SERVICE_PROVIDER_ID, 'region_name': fakes.REGION_NAME,
'remote_project_id': fakes.PROJECT_ID 'service_provider': fakes.SERVICE_PROVIDER_ID,
}) 'remote_project_id': fakes.PROJECT_ID,
}
)
client_manager = self._clientmanager_class()( client_manager = self._clientmanager_class()(
cli_options=cloud_config.CloudConfig( cli_options=cloud_config.CloudConfig(
name='t1', name='t1',
@@ -425,27 +436,31 @@ class TestClientManager(utils.TestClientManager):
auth_plugin_name='none', auth_plugin_name='none',
) )
self.assertIsNone( self.assertIsNone(
client_manager.get_endpoint_for_service_type('compute')) client_manager.get_endpoint_for_service_type('compute')
)
def test_client_manager_endpoint_override(self): def test_client_manager_endpoint_override(self):
# test token auth # test token auth
client_manager = self._make_clientmanager( client_manager = self._make_clientmanager(
auth_args={}, auth_args={},
config_args={'compute_endpoint_override': 'http://example.com', config_args={
'foo_bar_endpoint_override': 'http://example2.com'}, 'compute_endpoint_override': 'http://example.com',
'foo_bar_endpoint_override': 'http://example2.com',
},
auth_plugin_name='none', auth_plugin_name='none',
) )
self.assertEqual( self.assertEqual(
'http://example.com', 'http://example.com',
client_manager.get_endpoint_for_service_type('compute')) client_manager.get_endpoint_for_service_type('compute'),
)
self.assertEqual( self.assertEqual(
'http://example2.com', 'http://example2.com',
client_manager.get_endpoint_for_service_type('foo-bar')) client_manager.get_endpoint_for_service_type('foo-bar'),
)
self.assertTrue(client_manager.is_service_available('compute')) self.assertTrue(client_manager.is_service_available('compute'))
class TestClientManagerSDK(utils.TestClientManager): class TestClientManagerSDK(utils.TestClientManager):
def test_client_manager_connection(self): def test_client_manager_connection(self):
client_manager = self._make_clientmanager( client_manager = self._make_clientmanager(
auth_required=True, auth_required=True,

View File

@@ -19,7 +19,6 @@ from osc_lib.tests import utils
class TestContext(utils.TestCase): class TestContext(utils.TestCase):
def test_log_level_from_options(self): def test_log_level_from_options(self):
opts = mock.Mock() opts = mock.Mock()
opts.verbose_level = 0 opts.verbose_level = 0
@@ -66,35 +65,47 @@ class TestContext(utils.TestCase):
class TestFileFormatter(utils.TestCase): class TestFileFormatter(utils.TestCase):
def test_nothing(self): def test_nothing(self):
formatter = logs._FileFormatter() formatter = logs._FileFormatter()
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' self.assertEqual(
'%(name)s %(message)s'), formatter.fmt) (
'%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s %(message)s'
),
formatter.fmt,
)
def test_options(self): def test_options(self):
class Opts(object): class Opts(object):
cloud = 'cloudy' cloud = 'cloudy'
os_project_name = 'projecty' os_project_name = 'projecty'
username = 'usernamey' username = 'usernamey'
options = Opts() options = Opts()
formatter = logs._FileFormatter(options=options) formatter = logs._FileFormatter(options=options)
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' self.assertEqual(
'%(name)s [cloudy usernamey projecty] %(message)s'), (
formatter.fmt) '%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [cloudy usernamey projecty] %(message)s'
),
formatter.fmt,
)
def test_config(self): def test_config(self):
config = mock.Mock() config = mock.Mock()
config.config = {'cloud': 'cloudy'} config.config = {'cloud': 'cloudy'}
config.auth = {'project_name': 'projecty', 'username': 'usernamey'} config.auth = {'project_name': 'projecty', 'username': 'usernamey'}
formatter = logs._FileFormatter(config=config) formatter = logs._FileFormatter(config=config)
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' self.assertEqual(
'%(name)s [cloudy usernamey projecty] %(message)s'), (
formatter.fmt) '%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [cloudy usernamey projecty] %(message)s'
),
formatter.fmt,
)
class TestLogConfigurator(utils.TestCase): class TestLogConfigurator(utils.TestCase):
def setUp(self): def setUp(self):
super(TestLogConfigurator, self).setUp() super(TestLogConfigurator, self).setUp()
self.options = mock.Mock() self.options = mock.Mock()
@@ -117,7 +128,8 @@ class TestLogConfigurator(utils.TestCase):
self.requests_log, self.requests_log,
self.cliff_log, self.cliff_log,
self.stevedore_log, self.stevedore_log,
self.iso8601_log] self.iso8601_log,
]
@mock.patch('logging.StreamHandler') @mock.patch('logging.StreamHandler')
@mock.patch('logging.getLogger') @mock.patch('logging.getLogger')
@@ -186,7 +198,8 @@ class TestLogConfigurator(utils.TestCase):
cloud_config.config = { cloud_config.config = {
'log_file': config_log, 'log_file': config_log,
'verbose_level': 1, 'verbose_level': 1,
'log_level': 'info'} 'log_level': 'info',
}
file_logger = mock.Mock() file_logger = mock.Mock()
file_logger.setFormatter = mock.Mock() file_logger.setFormatter = mock.Mock()
file_logger.setLevel = mock.Mock() file_logger.setLevel = mock.Mock()

View File

@@ -113,7 +113,7 @@ global_options = {
'--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True),
'--os-cacert': ('/dev/null', True, True), '--os-cacert': ('/dev/null', True, True),
'--timing': (True, True, False), '--timing': (True, True, False),
'--os-interface': (DEFAULT_INTERFACE, True, True) '--os-interface': (DEFAULT_INTERFACE, True, True),
} }
if shell.osprofiler_profiler: if shell.osprofiler_profiler:
global_options['--os-profile'] = ('SECRET_KEY', True, True) global_options['--os-profile'] = ('SECRET_KEY', True, True)
@@ -138,8 +138,8 @@ class TestShellArgV(utils.TestShell):
""" """
with mock.patch( with mock.patch(
"osc_lib.shell.OpenStackShell.run", "osc_lib.shell.OpenStackShell.run",
self.app, self.app,
): ):
# Ensure type gets through unmolested through shell.main() # Ensure type gets through unmolested through shell.main()
argv = sys.argv argv = sys.argv
@@ -220,8 +220,8 @@ class TestShellCli(utils.TestShell):
def test_shell_args_no_options(self): def test_shell_args_no_options(self):
_shell = utils.make_shell() _shell = utils.make_shell()
with mock.patch( with mock.patch(
"osc_lib.shell.OpenStackShell.initialize_app", "osc_lib.shell.OpenStackShell.initialize_app",
self.app, self.app,
): ):
utils.fake_execute(_shell, "list user") utils.fake_execute(_shell, "list user")
self.app.assert_called_with(["list", "user"]) self.app.assert_called_with(["list", "user"])
@@ -306,8 +306,7 @@ class TestShellCli(utils.TestShell):
# --os-cert and --os-key # --os-cert and --os-key
utils.fake_execute( utils.fake_execute(
_shell, _shell, "--os-cert mycert --os-key mickey module list"
"--os-cert mycert --os-key mickey module list"
) )
self.assertEqual('mycert', _shell.options.cert) self.assertEqual('mycert', _shell.options.cert)
self.assertEqual('mickey', _shell.options.key) self.assertEqual('mickey', _shell.options.key)

View File

@@ -86,17 +86,20 @@ class ParserException(Exception):
class TestCase(testtools.TestCase): class TestCase(testtools.TestCase):
def setUp(self): def setUp(self):
testtools.TestCase.setUp(self) testtools.TestCase.setUp(self)
if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or if (
os.environ.get("OS_STDOUT_CAPTURE") == "1"): os.environ.get("OS_STDOUT_CAPTURE") == "True"
or os.environ.get("OS_STDOUT_CAPTURE") == "1"
):
stdout = self.useFixture(fixtures.StringStream("stdout")).stream stdout = self.useFixture(fixtures.StringStream("stdout")).stream
self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout)) self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout))
if (os.environ.get("OS_STDERR_CAPTURE") == "True" or if (
os.environ.get("OS_STDERR_CAPTURE") == "1"): os.environ.get("OS_STDERR_CAPTURE") == "True"
or os.environ.get("OS_STDERR_CAPTURE") == "1"
):
stderr = self.useFixture(fixtures.StringStream("stderr")).stream stderr = self.useFixture(fixtures.StringStream("stderr")).stream
self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr))
@@ -162,10 +165,13 @@ class TestCommand(TestCase):
for col_expected, col_actual in zip(expected, actual): for col_expected, col_actual in zip(expected, actual):
if isinstance(col_expected, cliff_columns.FormattableColumn): if isinstance(col_expected, cliff_columns.FormattableColumn):
self.assertIsInstance(col_actual, col_expected.__class__) self.assertIsInstance(col_actual, col_expected.__class__)
self.assertEqual(col_expected.human_readable(), self.assertEqual(
col_actual.human_readable()) col_expected.human_readable(), col_actual.human_readable()
self.assertEqual(col_expected.machine_readable(), )
col_actual.machine_readable()) self.assertEqual(
col_expected.machine_readable(),
col_actual.machine_readable(),
)
else: else:
self.assertEqual(col_expected, col_actual) self.assertEqual(col_expected, col_actual)
@@ -201,18 +207,24 @@ class TestClientManager(TestCase):
# fake v2password token retrieval # fake v2password token retrieval
self.stub_auth(json=fakes.TEST_RESPONSE_DICT) self.stub_auth(json=fakes.TEST_RESPONSE_DICT)
# fake token and token_endpoint retrieval # fake token and token_endpoint retrieval
self.stub_auth(json=fakes.TEST_RESPONSE_DICT, self.stub_auth(
url='/'.join([fakes.AUTH_URL, 'v2.0/tokens'])) json=fakes.TEST_RESPONSE_DICT,
url='/'.join([fakes.AUTH_URL, 'v2.0/tokens']),
)
# fake v3password token retrieval # fake v3password token retrieval
self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3, self.stub_auth(
url='/'.join([fakes.AUTH_URL, 'v3/auth/tokens'])) json=fakes.TEST_RESPONSE_DICT_V3,
url='/'.join([fakes.AUTH_URL, 'v3/auth/tokens']),
)
# fake password token retrieval # fake password token retrieval
self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3, self.stub_auth(
url='/'.join([fakes.AUTH_URL, 'auth/tokens'])) json=fakes.TEST_RESPONSE_DICT_V3,
url='/'.join([fakes.AUTH_URL, 'auth/tokens']),
)
# fake password version endpoint discovery # fake password version endpoint discovery
self.stub_auth(json=fakes.TEST_VERSIONS, self.stub_auth(
url=fakes.AUTH_URL, json=fakes.TEST_VERSIONS, url=fakes.AUTH_URL, verb='GET'
verb='GET') )
# Mock the auth plugin # Mock the auth plugin
self.auth_mock = mock.Mock() self.auth_mock = mock.Mock()
@@ -250,7 +262,6 @@ class TestClientManager(TestCase):
auth_plugin_name=None, auth_plugin_name=None,
auth_required=None, auth_required=None,
): ):
if identity_api_version is None: if identity_api_version is None:
identity_api_version = '2.0' identity_api_version = '2.0'
if auth_plugin_name is None: if auth_plugin_name is None:
@@ -267,13 +278,15 @@ class TestClientManager(TestCase):
auth_dict = auth_args auth_dict = auth_args
cli_options = defaults.get_defaults() cli_options = defaults.get_defaults()
cli_options.update({ cli_options.update(
'auth_type': auth_plugin_name, {
'auth': auth_dict, 'auth_type': auth_plugin_name,
'interface': fakes.INTERFACE, 'auth': auth_dict,
'region_name': fakes.REGION_NAME, 'interface': fakes.INTERFACE,
# 'workflow_api_version': '2', 'region_name': fakes.REGION_NAME,
}) # 'workflow_api_version': '2',
}
)
if config_args is not None: if config_args is not None:
cli_options.update(config_args) cli_options.update(config_args)
@@ -302,7 +315,6 @@ class TestClientManager(TestCase):
class TestShell(TestCase): class TestShell(TestCase):
# Full name of the OpenStackShell class to test (cliff.app.App subclass) # Full name of the OpenStackShell class to test (cliff.app.App subclass)
shell_class_name = "osc_lib.shell.OpenStackShell" shell_class_name = "osc_lib.shell.OpenStackShell"
@@ -325,8 +337,8 @@ class TestShell(TestCase):
""" """
with mock.patch( with mock.patch(
self.shell_class_name + ".initialize_app", self.shell_class_name + ".initialize_app",
self.app, self.app,
): ):
_shell = make_shell(shell_class=self.shell_class) _shell = make_shell(shell_class=self.shell_class)
_cmd = cmd_options + " module list" _cmd = cmd_options + " module list"
@@ -352,8 +364,8 @@ class TestShell(TestCase):
cloud.config = {} cloud.config = {}
self.occ_get_one = mock.Mock(return_value=cloud) self.occ_get_one = mock.Mock(return_value=cloud)
with mock.patch( with mock.patch(
"openstack.config.loader.OpenStackConfig.get_one", "openstack.config.loader.OpenStackConfig.get_one",
self.occ_get_one, self.occ_get_one,
): ):
_shell = make_shell(shell_class=self.shell_class) _shell = make_shell(shell_class=self.shell_class)
_cmd = cmd_options + " module list" _cmd = cmd_options + " module list"

View File

@@ -16,7 +16,6 @@ from osc_lib.utils import columns as column_utils
class TestColumnUtils(test_utils.TestCase): class TestColumnUtils(test_utils.TestCase):
def test_get_column_definitions(self): def test_get_column_definitions(self):
attr_map = ( attr_map = (
('id', 'ID', column_utils.LIST_BOTH), ('id', 'ID', column_utils.LIST_BOTH),
@@ -25,7 +24,8 @@ class TestColumnUtils(test_utils.TestCase):
('summary', 'Summary', column_utils.LIST_SHORT_ONLY), ('summary', 'Summary', column_utils.LIST_SHORT_ONLY),
) )
headers, columns = column_utils.get_column_definitions( headers, columns = column_utils.get_column_definitions(
attr_map, long_listing=False) attr_map, long_listing=False
)
self.assertEqual(['id', 'name', 'summary'], columns) self.assertEqual(['id', 'name', 'summary'], columns)
self.assertEqual(['ID', 'Name', 'Summary'], headers) self.assertEqual(['ID', 'Name', 'Summary'], headers)
@@ -37,7 +37,8 @@ class TestColumnUtils(test_utils.TestCase):
('summary', 'Summary', column_utils.LIST_SHORT_ONLY), ('summary', 'Summary', column_utils.LIST_SHORT_ONLY),
) )
headers, columns = column_utils.get_column_definitions( headers, columns = column_utils.get_column_definitions(
attr_map, long_listing=True) attr_map, long_listing=True
)
self.assertEqual(['id', 'tenant_id', 'name'], columns) self.assertEqual(['id', 'tenant_id', 'name'], columns)
self.assertEqual(['ID', 'Project', 'Name'], headers) self.assertEqual(['ID', 'Project', 'Name'], headers)

View File

@@ -26,15 +26,22 @@ def help_enhancer(_h):
class TestTags(test_utils.TestCase): class TestTags(test_utils.TestCase):
def test_add_tag_filtering_option_to_parser(self): def test_add_tag_filtering_option_to_parser(self):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
tags.add_tag_filtering_option_to_parser(parser, 'test') tags.add_tag_filtering_option_to_parser(parser, 'test')
parsed_args = parser.parse_args(['--tags', 'tag1,tag2', parsed_args = parser.parse_args(
'--any-tags', 'tag4', [
'--not-tags', 'tag5', '--tags',
'--not-any-tags', 'tag6']) 'tag1,tag2',
'--any-tags',
'tag4',
'--not-tags',
'tag5',
'--not-any-tags',
'tag6',
]
)
actual = getattr(parsed_args, 'tags', []) actual = getattr(parsed_args, 'tags', [])
expected = ['tag1', 'tag2'] expected = ['tag1', 'tag2']
@@ -56,12 +63,24 @@ class TestTags(test_utils.TestCase):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
tags.add_tag_filtering_option_to_parser(parser, 'test') tags.add_tag_filtering_option_to_parser(parser, 'test')
parsed_args = parser.parse_args(['--tags', 'tag1,tag2', parsed_args = parser.parse_args(
'--any-tags', 'tag4', [
'--not-tags', 'tag5', '--tags',
'--not-any-tags', 'tag6']) 'tag1,tag2',
expected = {'tags': 'tag1,tag2', 'any_tags': 'tag4', '--any-tags',
'not_tags': 'tag5', 'not_any_tags': 'tag6'} 'tag4',
'--not-tags',
'tag5',
'--not-any-tags',
'tag6',
]
)
expected = {
'tags': 'tag1,tag2',
'any_tags': 'tag4',
'not_tags': 'tag5',
'not_any_tags': 'tag6',
}
args = {} args = {}
tags.get_tag_filtering_args(parsed_args, args) tags.get_tag_filtering_args(parsed_args, args)
self.assertEqual(expected, args) self.assertEqual(expected, args)
@@ -71,8 +90,9 @@ class TestTags(test_utils.TestCase):
tags.add_tag_option_to_parser_for_create(parser, 'test') tags.add_tag_option_to_parser_for_create(parser, 'test')
# Test that --tag and --no-tag are mutually exclusive # Test that --tag and --no-tag are mutually exclusive
self.assertRaises(SystemExit, parser.parse_args, self.assertRaises(
['--tag', 'tag1', '--no-tag']) SystemExit, parser.parse_args, ['--tag', 'tag1', '--no-tag']
)
parsed_args = parser.parse_args(['--tag', 'tag1']) parsed_args = parser.parse_args(['--tag', 'tag1'])
actual = getattr(parsed_args, 'tags', []) actual = getattr(parsed_args, 'tags', [])
@@ -103,8 +123,9 @@ class TestTags(test_utils.TestCase):
tags.add_tag_option_to_parser_for_unset(parser, 'test') tags.add_tag_option_to_parser_for_unset(parser, 'test')
# Test that --tag and --all-tag are mutually exclusive # Test that --tag and --all-tag are mutually exclusive
self.assertRaises(SystemExit, parser.parse_args, self.assertRaises(
['--tag', 'tag1', '--all-tag']) SystemExit, parser.parse_args, ['--tag', 'tag1', '--all-tag']
)
parsed_args = parser.parse_args(['--tag', 'tag1']) parsed_args = parser.parse_args(['--tag', 'tag1'])
actual = getattr(parsed_args, 'tags', []) actual = getattr(parsed_args, 'tags', [])
@@ -127,7 +148,8 @@ class TestTags(test_utils.TestCase):
mock_obj.tags = None mock_obj.tags = None
tags.update_tags_for_set(mock_client, mock_obj, mock_parsed_args) tags.update_tags_for_set(mock_client, mock_obj, mock_parsed_args)
mock_client.set_tags.assert_called_once_with( mock_client.set_tags.assert_called_once_with(
mock_obj, list(mock_parsed_args.tags)) mock_obj, list(mock_parsed_args.tags)
)
# no-tag False path # no-tag False path
mock_client.set_tags.reset_mock() mock_client.set_tags.reset_mock()
@@ -136,8 +158,7 @@ class TestTags(test_utils.TestCase):
mock_obj.tags = ['tag2'] mock_obj.tags = ['tag2']
expected_list = ['tag1', 'tag2'] expected_list = ['tag1', 'tag2']
tags.update_tags_for_set(mock_client, mock_obj, mock_parsed_args) tags.update_tags_for_set(mock_client, mock_obj, mock_parsed_args)
mock_client.set_tags.assert_called_once_with( mock_client.set_tags.assert_called_once_with(mock_obj, expected_list)
mock_obj, expected_list)
# no new tags path # no new tags path
mock_client.set_tags.reset_mock() mock_client.set_tags.reset_mock()
@@ -164,8 +185,7 @@ class TestTags(test_utils.TestCase):
mock_parsed_args.all_tag = True mock_parsed_args.all_tag = True
mock_parsed_args.tags = None mock_parsed_args.tags = None
tags.update_tags_for_unset(mock_client, mock_obj, mock_parsed_args) tags.update_tags_for_unset(mock_client, mock_obj, mock_parsed_args)
mock_client.set_tags.assert_called_once_with( mock_client.set_tags.assert_called_once_with(mock_obj, [])
mock_obj, [])
# Remove one tag # Remove one tag
mock_client.set_tags.reset_mock() mock_client.set_tags.reset_mock()
@@ -173,12 +193,10 @@ class TestTags(test_utils.TestCase):
mock_parsed_args.all_tag = False mock_parsed_args.all_tag = False
mock_parsed_args.tags = ['tag2'] mock_parsed_args.tags = ['tag2']
tags.update_tags_for_unset(mock_client, mock_obj, mock_parsed_args) tags.update_tags_for_unset(mock_client, mock_obj, mock_parsed_args)
mock_client.set_tags.assert_called_once_with( mock_client.set_tags.assert_called_once_with(mock_obj, ['tag1'])
mock_obj, ['tag1'])
class TestTagHelps(test_utils.TestCase): class TestTagHelps(test_utils.TestCase):
def _test_tag_method_help(self, meth, exp_normal, exp_enhanced): def _test_tag_method_help(self, meth, exp_normal, exp_enhanced):
"""Vet the help text of the options added by the tag filtering helpers. """Vet the help text of the options added by the tag filtering helpers.
@@ -241,7 +259,8 @@ usage: run.py [-h] [--tags <tag>[,<tag>,...]] [--any-tags <tag>[,<tag>,...]]
--not-any-tags <tag>[,<tag>,...] --not-any-tags <tag>[,<tag>,...]
)sgat fo tsil detarapes-ammoC( )s(gat nevig yna evah )sgat fo tsil detarapes-ammoC( )s(gat nevig yna evah
hcihw tset edulcxE hcihw tset edulcxE
""") """,
)
def test_add_tag_option_to_parser_for_create(self): def test_add_tag_option_to_parser_for_create(self):
self._test_tag_method_help( self._test_tag_method_help(
@@ -263,7 +282,8 @@ usage: run.py [-h] [--tag <tag> | --no-tag]
--tag <tag> )sgat elpitlum tes ot noitpo taeper( tset eht ot dedda eb ot --tag <tag> )sgat elpitlum tes ot noitpo taeper( tset eht ot dedda eb ot
gaT gaT
--no-tag tset eht htiw detaicossa sgat oN --no-tag tset eht htiw detaicossa sgat oN
""") """,
)
def test_add_tag_option_to_parser_for_set(self): def test_add_tag_option_to_parser_for_set(self):
self._test_tag_method_help( self._test_tag_method_help(
@@ -287,7 +307,8 @@ usage: run.py [-h] [--tag <tag>] [--no-tag]
gaT gaT
--no-tag sgat tnerruc etirwrevo ot gat-on-- dna gat-- htob yficepS .tset --no-tag sgat tnerruc etirwrevo ot gat-on-- dna gat-- htob yficepS .tset
eht htiw detaicossa sgat raelC eht htiw detaicossa sgat raelC
""") """,
)
def test_add_tag_option_to_parser_for_unset(self): def test_add_tag_option_to_parser_for_unset(self):
self._test_tag_method_help( self._test_tag_method_help(
@@ -309,4 +330,5 @@ usage: run.py [-h] [--tag <tag> | --all-tag]
--tag <tag> )sgat elpitlum evomer ot noitpo taeper( tset eht morf devomer --tag <tag> )sgat elpitlum evomer ot noitpo taeper( tset eht morf devomer
eb ot gaT eb ot gaT
--all-tag tset eht htiw detaicossa sgat lla raelC --all-tag tset eht htiw detaicossa sgat lla raelC
""") """,
)

View File

@@ -31,7 +31,6 @@ DROWSSAP = "dr0w$$aP"
class FakeOddballResource(fakes.FakeResource): class FakeOddballResource(fakes.FakeResource):
def get(self, attr): def get(self, attr):
"""get() is needed for utils.find_resource()""" """get() is needed for utils.find_resource()"""
if attr == 'id': if attr == 'id':
@@ -43,7 +42,6 @@ class FakeOddballResource(fakes.FakeResource):
class TestUtils(test_utils.TestCase): class TestUtils(test_utils.TestCase):
def _get_test_items(self): def _get_test_items(self):
item1 = {'a': 1, 'b': 2} item1 = {'a': 1, 'b': 2}
item2 = {'a': 1, 'b': 3} item2 = {'a': 1, 'b': 3}
@@ -130,18 +128,18 @@ class TestUtils(test_utils.TestCase):
mock_stdin = mock.Mock() mock_stdin = mock.Mock()
mock_stdin.isatty = mock.Mock() mock_stdin.isatty = mock.Mock()
mock_stdin.isatty.return_value = False mock_stdin.isatty.return_value = False
self.assertRaises(exceptions.CommandError, self.assertRaises(
utils.get_password, exceptions.CommandError, utils.get_password, mock_stdin
mock_stdin) )
def test_get_password_cntrl_d(self): def test_get_password_cntrl_d(self):
with mock.patch("getpass.getpass", side_effect=EOFError()): with mock.patch("getpass.getpass", side_effect=EOFError()):
mock_stdin = mock.Mock() mock_stdin = mock.Mock()
mock_stdin.isatty = mock.Mock() mock_stdin.isatty = mock.Mock()
mock_stdin.isatty.return_value = True mock_stdin.isatty.return_value = True
self.assertRaises(exceptions.CommandError, self.assertRaises(
utils.get_password, exceptions.CommandError, utils.get_password, mock_stdin
mock_stdin) )
def test_sort_items_with_one_key(self): def test_sort_items_with_one_key(self):
items = self._get_test_items() items = self._get_test_items()
@@ -187,16 +185,16 @@ class TestUtils(test_utils.TestCase):
def test_sort_items_with_invalid_key(self): def test_sort_items_with_invalid_key(self):
items = self._get_test_items() items = self._get_test_items()
sort_str = 'c' sort_str = 'c'
self.assertRaises(exceptions.CommandError, self.assertRaises(
utils.sort_items, exceptions.CommandError, utils.sort_items, items, sort_str
items, sort_str) )
def test_sort_items_with_invalid_direction(self): def test_sort_items_with_invalid_direction(self):
items = self._get_test_items() items = self._get_test_items()
sort_str = 'a:bad_dir' sort_str = 'a:bad_dir'
self.assertRaises(exceptions.CommandError, self.assertRaises(
utils.sort_items, exceptions.CommandError, utils.sort_items, items, sort_str
items, sort_str) )
def test_sort_items_with_different_type_exception(self): def test_sort_items_with_different_type_exception(self):
item1 = {'a': 2} item1 = {'a': 2}
@@ -216,8 +214,9 @@ class TestUtils(test_utils.TestCase):
sort_str = 'a' sort_str = 'a'
sort_type = int sort_type = int
expect_items = [item3, item4, item1, item2] expect_items = [item3, item4, item1, item2]
self.assertEqual(expect_items, utils.sort_items(items, sort_str, self.assertEqual(
sort_type)) expect_items, utils.sort_items(items, sort_str, sort_type)
)
def test_sort_items_with_different_type_str(self): def test_sort_items_with_different_type_str(self):
item1 = {'a': 'a'} item1 = {'a': 'a'}
@@ -228,21 +227,22 @@ class TestUtils(test_utils.TestCase):
sort_str = 'a' sort_str = 'a'
sort_type = str sort_type = str
expect_items = [item3, item2, item1, item4] expect_items = [item3, item2, item1, item4]
self.assertEqual(expect_items, utils.sort_items(items, sort_str, self.assertEqual(
sort_type)) expect_items, utils.sort_items(items, sort_str, sort_type)
)
@mock.patch.object(time, 'sleep') @mock.patch.object(time, 'sleep')
def test_wait_for_delete_ok(self, mock_sleep): def test_wait_for_delete_ok(self, mock_sleep):
# Tests the normal flow that the resource is deleted with a 404 coming # Tests the normal flow that the resource is deleted with a 404 coming
# back on the 2nd iteration of the wait loop. # back on the 2nd iteration of the wait loop.
resource = mock.MagicMock(status='ACTIVE', progress=None) resource = mock.MagicMock(status='ACTIVE', progress=None)
mock_get = mock.Mock(side_effect=[resource, mock_get = mock.Mock(side_effect=[resource, exceptions.NotFound(404)])
exceptions.NotFound(404)])
manager = mock.MagicMock(get=mock_get) manager = mock.MagicMock(get=mock_get)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
callback = mock.Mock() callback = mock.Mock()
self.assertTrue(utils.wait_for_delete(manager, res_id, self.assertTrue(
callback=callback)) utils.wait_for_delete(manager, res_id, callback=callback)
)
mock_sleep.assert_called_once_with(5) mock_sleep.assert_called_once_with(5)
callback.assert_called_once_with(0) callback.assert_called_once_with(0)
@@ -253,8 +253,9 @@ class TestUtils(test_utils.TestCase):
mock_get = mock.Mock(return_value=resource) mock_get = mock.Mock(return_value=resource)
manager = mock.MagicMock(get=mock_get) manager = mock.MagicMock(get=mock_get)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1, self.assertFalse(
timeout=1)) utils.wait_for_delete(manager, res_id, sleep_time=1, timeout=1)
)
mock_sleep.assert_called_once_with(1) mock_sleep.assert_called_once_with(1)
@mock.patch.object(time, 'sleep') @mock.patch.object(time, 'sleep')
@@ -274,9 +275,14 @@ class TestUtils(test_utils.TestCase):
mock_get = mock.Mock(return_value=resource) mock_get = mock.Mock(return_value=resource)
manager = mock.MagicMock(get=mock_get) manager = mock.MagicMock(get=mock_get)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
self.assertFalse(utils.wait_for_delete(manager, res_id, self.assertFalse(
status_field='my_status', utils.wait_for_delete(
error_status=['failed'])) manager,
res_id,
status_field='my_status',
error_status=['failed'],
)
)
mock_sleep.assert_not_called() mock_sleep.assert_not_called()
@mock.patch.object(time, 'sleep') @mock.patch.object(time, 'sleep')
@@ -285,8 +291,11 @@ class TestUtils(test_utils.TestCase):
mock_get = mock.Mock(side_effect=Exception) mock_get = mock.Mock(side_effect=Exception)
manager = mock.MagicMock(get=mock_get) manager = mock.MagicMock(get=mock_get)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
self.assertTrue(utils.wait_for_delete(manager, res_id, self.assertTrue(
exception_name=['Exception'])) utils.wait_for_delete(
manager, res_id, exception_name=['Exception']
)
)
mock_sleep.assert_not_called() mock_sleep.assert_not_called()
@mock.patch.object(time, 'sleep') @mock.patch.object(time, 'sleep')
@@ -295,7 +304,12 @@ class TestUtils(test_utils.TestCase):
resource = mock.MagicMock(status='ACTIVE') resource = mock.MagicMock(status='ACTIVE')
status_f = mock.Mock(return_value=resource) status_f = mock.Mock(return_value=resource)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
self.assertTrue(utils.wait_for_status(status_f, res_id,)) self.assertTrue(
utils.wait_for_status(
status_f,
res_id,
)
)
mock_sleep.assert_not_called() mock_sleep.assert_not_called()
@mock.patch.object(time, 'sleep') @mock.patch.object(time, 'sleep')
@@ -304,9 +318,14 @@ class TestUtils(test_utils.TestCase):
resource = mock.MagicMock(my_status='COMPLETE') resource = mock.MagicMock(my_status='COMPLETE')
status_f = mock.Mock(return_value=resource) status_f = mock.Mock(return_value=resource)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
self.assertTrue(utils.wait_for_status(status_f, res_id, self.assertTrue(
status_field='my_status', utils.wait_for_status(
success_status=['complete'])) status_f,
res_id,
status_field='my_status',
success_status=['complete'],
)
)
mock_sleep.assert_not_called() mock_sleep.assert_not_called()
@mock.patch.object(time, 'sleep') @mock.patch.object(time, 'sleep')
@@ -324,14 +343,20 @@ class TestUtils(test_utils.TestCase):
resource = mock.MagicMock(my_status='FAILED') resource = mock.MagicMock(my_status='FAILED')
status_f = mock.Mock(return_value=resource) status_f = mock.Mock(return_value=resource)
res_id = str(uuid.uuid4()) res_id = str(uuid.uuid4())
self.assertFalse(utils.wait_for_status(status_f, res_id, self.assertFalse(
status_field='my_status', utils.wait_for_status(
error_status=['failed'])) status_f,
res_id,
status_field='my_status',
error_status=['failed'],
)
)
mock_sleep.assert_not_called() mock_sleep.assert_not_called()
def test_build_kwargs_dict_value_set(self): def test_build_kwargs_dict_value_set(self):
self.assertEqual({'arg_bla': 'bla'}, self.assertEqual(
utils.build_kwargs_dict('arg_bla', 'bla')) {'arg_bla': 'bla'}, utils.build_kwargs_dict('arg_bla', 'bla')
)
def test_build_kwargs_dict_value_None(self): def test_build_kwargs_dict_value_None(self):
self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None)) self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None))
@@ -349,21 +374,17 @@ class TestUtils(test_utils.TestCase):
self.assertEqual("999", utils.format_size(999)) self.assertEqual("999", utils.format_size(999))
self.assertEqual("100K", utils.format_size(100000)) self.assertEqual("100K", utils.format_size(100000))
self.assertEqual("2M", utils.format_size(2000000)) self.assertEqual("2M", utils.format_size(2000000))
self.assertEqual( self.assertEqual("16.4M", utils.format_size(16361280))
"16.4M", utils.format_size(16361280) self.assertEqual("1.6G", utils.format_size(1576395005))
)
self.assertEqual(
"1.6G", utils.format_size(1576395005)
)
self.assertEqual("0", utils.format_size(None)) self.assertEqual("0", utils.format_size(None))
def test_backward_compat_col_lister(self): def test_backward_compat_col_lister(self):
fake_col_headers = ['ID', 'Name', 'Size'] fake_col_headers = ['ID', 'Name', 'Size']
columns = ['Display Name'] columns = ['Display Name']
column_map = {'Display Name': 'Name'} column_map = {'Display Name': 'Name'}
results = utils.backward_compat_col_lister(fake_col_headers, results = utils.backward_compat_col_lister(
columns, fake_col_headers, columns, column_map
column_map) )
self.assertIsInstance(results, list) self.assertIsInstance(results, list)
self.assertIn('Display Name', results) self.assertIn('Display Name', results)
self.assertNotIn('Name', results) self.assertNotIn('Name', results)
@@ -374,9 +395,9 @@ class TestUtils(test_utils.TestCase):
fake_col_headers = ['ID', 'Name', 'Size'] fake_col_headers = ['ID', 'Name', 'Size']
columns = [] columns = []
column_map = {'Display Name': 'Name'} column_map = {'Display Name': 'Name'}
results = utils.backward_compat_col_lister(fake_col_headers, results = utils.backward_compat_col_lister(
columns, fake_col_headers, columns, column_map
column_map) )
self.assertIsInstance(results, list) self.assertIsInstance(results, list)
self.assertNotIn('Display Name', results) self.assertNotIn('Display Name', results)
self.assertIn('Name', results) self.assertIn('Name', results)
@@ -387,9 +408,9 @@ class TestUtils(test_utils.TestCase):
fake_col_headers = ('ID', 'Name', 'Size') fake_col_headers = ('ID', 'Name', 'Size')
columns = ['Display Name'] columns = ['Display Name']
column_map = {'Display Name': 'Name'} column_map = {'Display Name': 'Name'}
results = utils.backward_compat_col_lister(fake_col_headers, results = utils.backward_compat_col_lister(
columns, fake_col_headers, columns, column_map
column_map) )
self.assertIsInstance(results, list) self.assertIsInstance(results, list)
self.assertIn('Display Name', results) self.assertIn('Display Name', results)
self.assertNotIn('Name', results) self.assertNotIn('Name', results)
@@ -397,14 +418,16 @@ class TestUtils(test_utils.TestCase):
self.assertIn('Size', results) self.assertIn('Size', results)
def test_backward_compat_col_showone(self): def test_backward_compat_col_showone(self):
fake_object = {'id': 'fake-id', fake_object = {
'name': 'fake-name', 'id': 'fake-id',
'size': 'fake-size'} 'name': 'fake-name',
'size': 'fake-size',
}
columns = ['display_name'] columns = ['display_name']
column_map = {'display_name': 'name'} column_map = {'display_name': 'name'}
results = utils.backward_compat_col_showone(fake_object, results = utils.backward_compat_col_showone(
columns, fake_object, columns, column_map
column_map) )
self.assertIsInstance(results, dict) self.assertIsInstance(results, dict)
self.assertIn('display_name', results) self.assertIn('display_name', results)
self.assertIn('id', results) self.assertIn('id', results)
@@ -412,14 +435,16 @@ class TestUtils(test_utils.TestCase):
self.assertIn('size', results) self.assertIn('size', results)
def test_backward_compat_col_showone_no_specify_column(self): def test_backward_compat_col_showone_no_specify_column(self):
fake_object = {'id': 'fake-id', fake_object = {
'name': 'fake-name', 'id': 'fake-id',
'size': 'fake-size'} 'name': 'fake-name',
'size': 'fake-size',
}
columns = [] columns = []
column_map = {'display_name': 'name'} column_map = {'display_name': 'name'}
results = utils.backward_compat_col_showone(fake_object, results = utils.backward_compat_col_showone(
columns, fake_object, columns, column_map
column_map) )
self.assertIsInstance(results, dict) self.assertIsInstance(results, dict)
self.assertNotIn('display_name', results) self.assertNotIn('display_name', results)
self.assertIn('id', results) self.assertIn('id', results)
@@ -429,8 +454,9 @@ class TestUtils(test_utils.TestCase):
def _test_get_item_properties_with_formatter(self, formatters): def _test_get_item_properties_with_formatter(self, formatters):
names = ('id', 'attr') names = ('id', 'attr')
item = fakes.FakeResource(info={'id': 'fake-id', 'attr': ['a', 'b']}) item = fakes.FakeResource(info={'id': 'fake-id', 'attr': ['a', 'b']})
res_id, res_attr = utils.get_item_properties(item, names, res_id, res_attr = utils.get_item_properties(
formatters=formatters) item, names, formatters=formatters
)
self.assertEqual('fake-id', res_id) self.assertEqual('fake-id', res_id)
return res_attr return res_attr
@@ -447,8 +473,9 @@ class TestUtils(test_utils.TestCase):
def _test_get_dict_properties_with_formatter(self, formatters): def _test_get_dict_properties_with_formatter(self, formatters):
names = ('id', 'attr') names = ('id', 'attr')
item = {'id': 'fake-id', 'attr': ['a', 'b']} item = {'id': 'fake-id', 'attr': ['a', 'b']}
res_id, res_attr = utils.get_dict_properties(item, names, res_id, res_attr = utils.get_dict_properties(
formatters=formatters) item, names, formatters=formatters
)
self.assertEqual('fake-id', res_id) self.assertEqual('fake-id', res_id)
return res_attr return res_attr
@@ -462,14 +489,16 @@ class TestUtils(test_utils.TestCase):
res_attr = self._test_get_dict_properties_with_formatter(formatters) res_attr = self._test_get_dict_properties_with_formatter(formatters)
self.assertIsInstance(res_attr, format_columns.ListColumn) self.assertIsInstance(res_attr, format_columns.ListColumn)
def _test_calculate_header_and_attrs(self, parsed_args_columns, def _test_calculate_header_and_attrs(
expected_headers, expected_attrs): self, parsed_args_columns, expected_headers, expected_attrs
):
column_headers = ('ID', 'Name', 'Fixed IP Addresses') column_headers = ('ID', 'Name', 'Fixed IP Addresses')
columns = ('id', 'name', 'fixed_ips') columns = ('id', 'name', 'fixed_ips')
parsed_args = mock.Mock() parsed_args = mock.Mock()
parsed_args.columns = parsed_args_columns parsed_args.columns = parsed_args_columns
ret_headers, ret_attrs = utils.calculate_header_and_attrs( ret_headers, ret_attrs = utils.calculate_header_and_attrs(
column_headers, columns, parsed_args) column_headers, columns, parsed_args
)
self.assertEqual(expected_headers, ret_headers) self.assertEqual(expected_headers, ret_headers)
self.assertEqual(expected_attrs, ret_attrs) self.assertEqual(expected_attrs, ret_attrs)
if parsed_args_columns: if parsed_args_columns:
@@ -481,25 +510,27 @@ class TestUtils(test_utils.TestCase):
self._test_calculate_header_and_attrs( self._test_calculate_header_and_attrs(
[], [],
('ID', 'Name', 'Fixed IP Addresses'), ('ID', 'Name', 'Fixed IP Addresses'),
('id', 'name', 'fixed_ips')) ('id', 'name', 'fixed_ips'),
)
def test_calculate_header_and_attrs_with_known_columns(self): def test_calculate_header_and_attrs_with_known_columns(self):
self._test_calculate_header_and_attrs( self._test_calculate_header_and_attrs(
['Name', 'ID'], ['Name', 'ID'], ['Name', 'ID'], ['name', 'id']
['Name', 'ID'], )
['name', 'id'])
def test_calculate_header_and_attrs_with_unknown_columns(self): def test_calculate_header_and_attrs_with_unknown_columns(self):
self._test_calculate_header_and_attrs( self._test_calculate_header_and_attrs(
['Name', 'ID', 'device_id'], ['Name', 'ID', 'device_id'],
['Name', 'ID', 'device_id'], ['Name', 'ID', 'device_id'],
['name', 'id', 'device_id']) ['name', 'id', 'device_id'],
)
def test_calculate_header_and_attrs_with_attrname_columns(self): def test_calculate_header_and_attrs_with_attrname_columns(self):
self._test_calculate_header_and_attrs( self._test_calculate_header_and_attrs(
['name', 'id', 'device_id'], ['name', 'id', 'device_id'],
['Name', 'ID', 'device_id'], ['Name', 'ID', 'device_id'],
['name', 'id', 'device_id']) ['name', 'id', 'device_id'],
)
def test_subtest(self): def test_subtest(self):
for i in range(3): for i in range(3):
@@ -512,7 +543,6 @@ class NoUniqueMatch(Exception):
class TestFindResource(test_utils.TestCase): class TestFindResource(test_utils.TestCase):
def setUp(self): def setUp(self):
super(TestFindResource, self).setUp() super(TestFindResource, self).setUp()
self.name = 'legos' self.name = 'legos'
@@ -572,36 +602,39 @@ class TestFindResource(test_utils.TestCase):
self.manager.find = mock.Mock( self.manager.find = mock.Mock(
side_effect=exceptions.NotFound(404, "2") side_effect=exceptions.NotFound(404, "2")
) )
result = self.assertRaises(exceptions.CommandError, result = self.assertRaises(
utils.find_resource, exceptions.CommandError,
self.manager, utils.find_resource,
self.name) self.manager,
self.assertEqual("No lego with a name or ID of 'legos' exists.", self.name,
str(result)) )
self.assertEqual(
"No lego with a name or ID of 'legos' exists.", str(result)
)
self.manager.get.assert_called_with(self.name) self.manager.get.assert_called_with(self.name)
self.manager.find.assert_called_with(name=self.name) self.manager.find.assert_called_with(name=self.name)
def test_find_resource_list_forbidden(self): def test_find_resource_list_forbidden(self):
self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
self.manager.find = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock(side_effect=Exception('Boom!'))
self.manager.list = mock.Mock( self.manager.list = mock.Mock(side_effect=exceptions.Forbidden(403))
side_effect=exceptions.Forbidden(403) self.assertRaises(
exceptions.Forbidden, utils.find_resource, self.manager, self.name
) )
self.assertRaises(exceptions.Forbidden,
utils.find_resource,
self.manager,
self.name)
self.manager.list.assert_called_with() self.manager.list.assert_called_with()
def test_find_resource_find_no_unique(self): def test_find_resource_find_no_unique(self):
self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
self.manager.find = mock.Mock(side_effect=NoUniqueMatch()) self.manager.find = mock.Mock(side_effect=NoUniqueMatch())
result = self.assertRaises(exceptions.CommandError, result = self.assertRaises(
utils.find_resource, exceptions.CommandError,
self.manager, utils.find_resource,
self.name) self.manager,
self.assertEqual("More than one lego exists with the name 'legos'.", self.name,
str(result)) )
self.assertEqual(
"More than one lego exists with the name 'legos'.", str(result)
)
self.manager.get.assert_called_with(self.name) self.manager.get.assert_called_with(self.name)
self.manager.find.assert_called_with(name=self.name) self.manager.find.assert_called_with(name=self.name)
@@ -620,7 +653,9 @@ class TestFindResource(test_utils.TestCase):
loaded=True, loaded=True,
) )
self.manager.list = mock.Mock( self.manager.list = mock.Mock(
return_value=[silly_resource, ], return_value=[
silly_resource,
],
) )
result = utils.find_resource(self.manager, self.name) result = utils.find_resource(self.manager, self.name)
self.assertEqual(silly_resource, result) self.assertEqual(silly_resource, result)
@@ -637,12 +672,13 @@ class TestFindResource(test_utils.TestCase):
) )
) )
self.manager.list = mock.Mock(return_value=[]) self.manager.list = mock.Mock(return_value=[])
result = self.assertRaises(exceptions.CommandError, result = self.assertRaises(
utils.find_resource, exceptions.CommandError,
self.manager, utils.find_resource,
self.name) self.manager,
self.assertEqual("Could not find resource legos", self.name,
str(result)) )
self.assertEqual("Could not find resource legos", str(result))
self.manager.get.assert_called_with(self.name) self.manager.get.assert_called_with(self.name)
self.manager.find.assert_called_with(name=self.name) self.manager.find.assert_called_with(name=self.name)
@@ -665,34 +701,39 @@ class TestFindResource(test_utils.TestCase):
{'id': 'abcde', 'name': self.name}, {'id': 'abcde', 'name': self.name},
loaded=True, loaded=True,
) )
self.manager.list = mock.Mock(return_value=[silly_resource, self.manager.list = mock.Mock(
silly_resource_same]) return_value=[silly_resource, silly_resource_same]
result = self.assertRaises(exceptions.CommandError, )
utils.find_resource, result = self.assertRaises(
self.manager, exceptions.CommandError,
self.name) utils.find_resource,
self.assertEqual("More than one resource exists " self.manager,
"with the name or ID 'legos'.", str(result)) self.name,
)
self.assertEqual(
"More than one resource exists " "with the name or ID 'legos'.",
str(result),
)
self.manager.get.assert_called_with(self.name) self.manager.get.assert_called_with(self.name)
self.manager.find.assert_called_with(name=self.name) self.manager.find.assert_called_with(name=self.name)
def test_format_dict(self): def test_format_dict(self):
expected = "a='b', c='d', e='f'" expected = "a='b', c='d', e='f'"
self.assertEqual(expected, self.assertEqual(
utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'})) expected, utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'})
self.assertEqual(expected, )
utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'})) self.assertEqual(
expected, utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'})
)
self.assertIsNone(utils.format_dict(None)) self.assertIsNone(utils.format_dict(None))
def test_format_dict_recursive(self): def test_format_dict_recursive(self):
expected = "a='b', c.1='d', c.2=''" expected = "a='b', c.1='d', c.2=''"
self.assertEqual( self.assertEqual(
expected, expected, utils.format_dict({'a': 'b', 'c': {'1': 'd', '2': ''}})
utils.format_dict({'a': 'b', 'c': {'1': 'd', '2': ''}})
) )
self.assertEqual( self.assertEqual(
expected, expected, utils.format_dict({'c': {'1': 'd', '2': ''}, 'a': 'b'})
utils.format_dict({'c': {'1': 'd', '2': ''}, 'a': 'b'})
) )
self.assertIsNone(utils.format_dict(None)) self.assertIsNone(utils.format_dict(None))
@@ -710,7 +751,7 @@ class TestFindResource(test_utils.TestCase):
'b2': 'D', 'b2': 'D',
}, },
} }
) ),
) )
self.assertEqual( self.assertEqual(
expected, expected,
@@ -725,45 +766,64 @@ class TestFindResource(test_utils.TestCase):
}, },
'a1': 'A', 'a1': 'A',
} }
) ),
) )
def test_format_dict_of_list(self): def test_format_dict_of_list(self):
expected = "a=a1, a2; b=b1, b2; c=c1, c2; e=" expected = "a=a1, a2; b=b1, b2; c=c1, c2; e="
self.assertEqual(expected, self.assertEqual(
utils.format_dict_of_list({'a': ['a2', 'a1'], expected,
'b': ['b2', 'b1'], utils.format_dict_of_list(
'c': ['c1', 'c2'], {
'd': None, 'a': ['a2', 'a1'],
'e': []}) 'b': ['b2', 'b1'],
) 'c': ['c1', 'c2'],
self.assertEqual(expected, 'd': None,
utils.format_dict_of_list({'c': ['c1', 'c2'], 'e': [],
'a': ['a2', 'a1'], }
'b': ['b2', 'b1'], ),
'e': []}) )
) self.assertEqual(
expected,
utils.format_dict_of_list(
{
'c': ['c1', 'c2'],
'a': ['a2', 'a1'],
'b': ['b2', 'b1'],
'e': [],
}
),
)
self.assertIsNone(utils.format_dict_of_list(None)) self.assertIsNone(utils.format_dict_of_list(None))
def test_format_dict_of_list_with_separator(self): def test_format_dict_of_list_with_separator(self):
expected = "a=a1, a2\nb=b1, b2\nc=c1, c2\ne=" expected = "a=a1, a2\nb=b1, b2\nc=c1, c2\ne="
self.assertEqual(expected, self.assertEqual(
utils.format_dict_of_list({'a': ['a2', 'a1'], expected,
'b': ['b2', 'b1'], utils.format_dict_of_list(
'c': ['c1', 'c2'], {
'd': None, 'a': ['a2', 'a1'],
'e': []}, 'b': ['b2', 'b1'],
separator='\n') 'c': ['c1', 'c2'],
) 'd': None,
self.assertEqual(expected, 'e': [],
utils.format_dict_of_list({'c': ['c1', 'c2'], },
'a': ['a2', 'a1'], separator='\n',
'b': ['b2', 'b1'], ),
'e': []}, )
separator='\n') self.assertEqual(
) expected,
self.assertIsNone(utils.format_dict_of_list(None, utils.format_dict_of_list(
separator='\n')) {
'c': ['c1', 'c2'],
'a': ['a2', 'a1'],
'b': ['b2', 'b1'],
'e': [],
},
separator='\n',
),
)
self.assertIsNone(utils.format_dict_of_list(None, separator='\n'))
def test_format_list(self): def test_format_list(self):
expected = 'a, b, c' expected = 'a, b, c'
@@ -790,35 +850,43 @@ class TestFindResource(test_utils.TestCase):
class TestAssertItemEqual(test_utils.TestCommand): class TestAssertItemEqual(test_utils.TestCommand):
def test_assert_normal_item(self): def test_assert_normal_item(self):
expected = ['a', 'b', 'c'] expected = ['a', 'b', 'c']
actual = ['a', 'b', 'c'] actual = ['a', 'b', 'c']
self.assertItemEqual(expected, actual) self.assertItemEqual(expected, actual)
def test_assert_item_with_formattable_columns(self): def test_assert_item_with_formattable_columns(self):
expected = [format_columns.DictColumn({'a': 1, 'b': 2}), expected = [
format_columns.ListColumn(['x', 'y', 'z'])] format_columns.DictColumn({'a': 1, 'b': 2}),
actual = [format_columns.DictColumn({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z']),
format_columns.ListColumn(['x', 'y', 'z'])] ]
actual = [
format_columns.DictColumn({'a': 1, 'b': 2}),
format_columns.ListColumn(['x', 'y', 'z']),
]
self.assertItemEqual(expected, actual) self.assertItemEqual(expected, actual)
def test_assert_item_different_length(self): def test_assert_item_different_length(self):
expected = ['a', 'b', 'c'] expected = ['a', 'b', 'c']
actual = ['a', 'b'] actual = ['a', 'b']
self.assertRaises(AssertionError, self.assertRaises(
self.assertItemEqual, expected, actual) AssertionError, self.assertItemEqual, expected, actual
)
def test_assert_item_formattable_columns_vs_legacy_formatter(self): def test_assert_item_formattable_columns_vs_legacy_formatter(self):
expected = [format_columns.DictColumn({'a': 1, 'b': 2}), expected = [
format_columns.ListColumn(['x', 'y', 'z'])] format_columns.DictColumn({'a': 1, 'b': 2}),
actual = [utils.format_dict({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z']),
utils.format_list(['x', 'y', 'z'])] ]
self.assertRaises(AssertionError, actual = [
self.assertItemEqual, expected, actual) utils.format_dict({'a': 1, 'b': 2}),
utils.format_list(['x', 'y', 'z']),
]
self.assertRaises(
AssertionError, self.assertItemEqual, expected, actual
)
def test_assert_item_different_formattable_columns(self): def test_assert_item_different_formattable_columns(self):
class ExceptionColumn(cliff_columns.FormattableColumn): class ExceptionColumn(cliff_columns.FormattableColumn):
def human_readable(self): def human_readable(self):
raise Exception('always fail') raise Exception('always fail')
@@ -828,63 +896,80 @@ class TestAssertItemEqual(test_utils.TestCommand):
# AssertionError is a subclass of Exception # AssertionError is a subclass of Exception
# so raising AssertionError ensures ExceptionColumn.human_readable() # so raising AssertionError ensures ExceptionColumn.human_readable()
# is not called. # is not called.
self.assertRaises(AssertionError, self.assertRaises(
self.assertItemEqual, expected, actual) AssertionError, self.assertItemEqual, expected, actual
)
def test_assert_list_item(self): def test_assert_list_item(self):
expected = [ expected = [
['a', 'b', 'c'], ['a', 'b', 'c'],
[format_columns.DictColumn({'a': 1, 'b': 2}), [
format_columns.ListColumn(['x', 'y', 'z'])] format_columns.DictColumn({'a': 1, 'b': 2}),
format_columns.ListColumn(['x', 'y', 'z']),
],
] ]
actual = [ actual = [
['a', 'b', 'c'], ['a', 'b', 'c'],
[format_columns.DictColumn({'a': 1, 'b': 2}), [
format_columns.ListColumn(['x', 'y', 'z'])] format_columns.DictColumn({'a': 1, 'b': 2}),
format_columns.ListColumn(['x', 'y', 'z']),
],
] ]
self.assertListItemEqual(expected, actual) self.assertListItemEqual(expected, actual)
class TestSDKUtils(test_utils.TestCase): class TestSDKUtils(test_utils.TestCase):
def setUp(self): def setUp(self):
super(TestSDKUtils, self).setUp() super(TestSDKUtils, self).setUp()
def _test_get_osc_show_columns_for_sdk_resource( def _test_get_osc_show_columns_for_sdk_resource(
self, sdk_resource, column_map, self,
expected_display_columns, expected_attr_columns): sdk_resource,
display_columns, attr_columns = \ column_map,
utils.get_osc_show_columns_for_sdk_resource( expected_display_columns,
sdk_resource, column_map) expected_attr_columns,
):
(
display_columns,
attr_columns,
) = utils.get_osc_show_columns_for_sdk_resource(
sdk_resource, column_map
)
self.assertEqual(expected_display_columns, display_columns) self.assertEqual(expected_display_columns, display_columns)
self.assertEqual(expected_attr_columns, attr_columns) self.assertEqual(expected_attr_columns, attr_columns)
def test_get_osc_show_columns_for_sdk_resource_empty(self): def test_get_osc_show_columns_for_sdk_resource_empty(self):
self._test_get_osc_show_columns_for_sdk_resource( self._test_get_osc_show_columns_for_sdk_resource(
{}, {}, tuple(), tuple()) {}, {}, tuple(), tuple()
)
def test_get_osc_show_columns_for_sdk_resource_empty_map(self): def test_get_osc_show_columns_for_sdk_resource_empty_map(self):
self._test_get_osc_show_columns_for_sdk_resource( self._test_get_osc_show_columns_for_sdk_resource(
{'foo': 'foo1'}, {}, {'foo': 'foo1'}, {}, ('foo',), ('foo',)
('foo',), ('foo',)) )
def test_get_osc_show_columns_for_sdk_resource_empty_data(self): def test_get_osc_show_columns_for_sdk_resource_empty_data(self):
self._test_get_osc_show_columns_for_sdk_resource( self._test_get_osc_show_columns_for_sdk_resource(
{}, {'foo': 'foo_map'}, {}, {'foo': 'foo_map'}, ('foo_map',), ('foo_map',)
('foo_map',), ('foo_map',)) )
def test_get_osc_show_columns_for_sdk_resource_map(self): def test_get_osc_show_columns_for_sdk_resource_map(self):
self._test_get_osc_show_columns_for_sdk_resource( self._test_get_osc_show_columns_for_sdk_resource(
{'foo': 'foo1'}, {'foo': 'foo_map'}, {'foo': 'foo1'}, {'foo': 'foo_map'}, ('foo_map',), ('foo',)
('foo_map',), ('foo',)) )
def test_get_osc_show_columns_for_sdk_resource_map_dup(self): def test_get_osc_show_columns_for_sdk_resource_map_dup(self):
self._test_get_osc_show_columns_for_sdk_resource( self._test_get_osc_show_columns_for_sdk_resource(
{'foo': 'foo1', 'foo_map': 'foo1'}, {'foo': 'foo_map'}, {'foo': 'foo1', 'foo_map': 'foo1'},
('foo_map',), ('foo',)) {'foo': 'foo_map'},
('foo_map',),
('foo',),
)
def test_get_osc_show_columns_for_sdk_resource_map_full(self): def test_get_osc_show_columns_for_sdk_resource_map_full(self):
self._test_get_osc_show_columns_for_sdk_resource( self._test_get_osc_show_columns_for_sdk_resource(
{'foo': 'foo1', 'bar': 'bar1'}, {'foo': 'foo1', 'bar': 'bar1'},
{'foo': 'foo_map', 'new': 'bar'}, {'foo': 'foo_map', 'new': 'bar'},
('bar', 'foo_map'), ('bar', 'foo')) ('bar', 'foo_map'),
('bar', 'foo'),
)

View File

@@ -53,11 +53,13 @@ def backward_compat_col_lister(column_headers, columns, column_map):
column_headers = list(column_headers) column_headers = list(column_headers)
for old_col, new_col in column_map.items(): for old_col, new_col in column_map.items():
if old_col in columns: if old_col in columns:
LOG.warning(_('The column "%(old_column)s" was deprecated, ' LOG.warning(
'please use "%(new_column)s" replace.') % { _(
'old_column': old_col, 'The column "%(old_column)s" was deprecated, '
'new_column': new_col} 'please use "%(new_column)s" replace.'
) )
% {'old_column': old_col, 'new_column': new_col}
)
if new_col in column_headers: if new_col in column_headers:
column_headers[column_headers.index(new_col)] = old_col column_headers[column_headers.index(new_col)] = old_col
return column_headers return column_headers
@@ -81,11 +83,13 @@ def backward_compat_col_showone(show_object, columns, column_map):
show_object = copy.deepcopy(show_object) show_object = copy.deepcopy(show_object)
for old_col, new_col in column_map.items(): for old_col, new_col in column_map.items():
if old_col in columns: if old_col in columns:
LOG.warning(_('The column "%(old_column)s" was deprecated, ' LOG.warning(
'please use "%(new_column)s" replace.') % { _(
'old_column': old_col, 'The column "%(old_column)s" was deprecated, '
'new_column': new_col} 'please use "%(new_column)s" replace.'
) )
% {'old_column': old_col, 'new_column': new_col}
)
if new_col in show_object: if new_col in show_object:
show_object.update({old_col: show_object.pop(new_col)}) show_object.update({old_col: show_object.pop(new_col)})
return show_object return show_object
@@ -118,11 +122,13 @@ def calculate_header_and_attrs(column_headers, attrs, parsed_args):
""" """
if parsed_args.columns: if parsed_args.columns:
header_attr_map = dict(zip(column_headers, attrs)) header_attr_map = dict(zip(column_headers, attrs))
expected_attrs = [header_attr_map.get(c, c) expected_attrs = [
for c in parsed_args.columns] header_attr_map.get(c, c) for c in parsed_args.columns
]
attr_header_map = dict(zip(attrs, column_headers)) attr_header_map = dict(zip(attrs, column_headers))
expected_headers = [attr_header_map.get(c, c) expected_headers = [
for c in parsed_args.columns] attr_header_map.get(c, c) for c in parsed_args.columns
]
# If attribute name is used in parsed_args.columns # If attribute name is used in parsed_args.columns
# convert it into display names because cliff expects # convert it into display names because cliff expects
# name in parsed_args.columns and name in column_headers matches. # name in parsed_args.columns and name in column_headers matches.
@@ -215,9 +221,11 @@ def find_resource(manager, name_or_id, **kwargs):
# Eventually this should be pulled from a common set # Eventually this should be pulled from a common set
# of client exceptions. # of client exceptions.
except Exception as ex: except Exception as ex:
if (type(ex).__name__ == 'NotFound' or if (
type(ex).__name__ == 'HTTPNotFound' or type(ex).__name__ == 'NotFound'
type(ex).__name__ == 'TypeError'): or type(ex).__name__ == 'HTTPNotFound'
or type(ex).__name__ == 'TypeError'
):
pass pass
else: else:
raise raise
@@ -246,21 +254,25 @@ def find_resource(manager, name_or_id, **kwargs):
# of client exceptions. # of client exceptions.
except Exception as ex: except Exception as ex:
if type(ex).__name__ == 'NotFound': if type(ex).__name__ == 'NotFound':
msg = _( msg = _("No %(resource)s with a name or ID of '%(id)s' exists.")
"No %(resource)s with a name or ID of '%(id)s' exists." raise exceptions.CommandError(
msg
% {
'resource': manager.resource_class.__name__.lower(),
'id': name_or_id,
}
) )
raise exceptions.CommandError(msg % {
'resource': manager.resource_class.__name__.lower(),
'id': name_or_id,
})
if type(ex).__name__ == 'NoUniqueMatch': if type(ex).__name__ == 'NoUniqueMatch':
msg = _( msg = _(
"More than one %(resource)s exists with the name '%(id)s'." "More than one %(resource)s exists with the name '%(id)s'."
) )
raise exceptions.CommandError(msg % { raise exceptions.CommandError(
'resource': manager.resource_class.__name__.lower(), msg
'id': name_or_id, % {
}) 'resource': manager.resource_class.__name__.lower(),
'id': name_or_id,
}
)
else: else:
pass pass
@@ -268,8 +280,10 @@ def find_resource(manager, name_or_id, **kwargs):
# to find a matching name or ID. # to find a matching name or ID.
count = 0 count = 0
for resource in manager.list(): for resource in manager.list():
if (resource.get('id') == name_or_id or if (
resource.get('name') == name_or_id): resource.get('id') == name_or_id
or resource.get('name') == name_or_id
):
count += 1 count += 1
_resource = resource _resource = resource
if count == 0: if count == 0:
@@ -403,17 +417,21 @@ def get_client_class(api_name, version, version_map):
try: try:
client_path = version_map[str(version)] client_path = version_map[str(version)]
except (KeyError, ValueError): except (KeyError, ValueError):
sorted_versions = sorted(version_map.keys(), sorted_versions = sorted(
key=lambda s: list(map(int, s.split('.')))) version_map.keys(), key=lambda s: list(map(int, s.split('.')))
)
msg = _( msg = _(
"Invalid %(api_name)s client version '%(version)s'. " "Invalid %(api_name)s client version '%(version)s'. "
"must be one of: %(version_map)s" "must be one of: %(version_map)s"
) )
raise exceptions.UnsupportedVersion(msg % { raise exceptions.UnsupportedVersion(
'api_name': api_name, msg
'version': version, % {
'version_map': ', '.join(sorted_versions), 'api_name': api_name,
}) 'version': version,
'version_map': ', '.join(sorted_versions),
}
)
return importutils.import_class(client_path) return importutils.import_class(client_path)
@@ -443,16 +461,15 @@ def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
if field in formatters: if field in formatters:
formatter = formatters[field] formatter = formatters[field]
# columns must be either a subclass of FormattableColumn # columns must be either a subclass of FormattableColumn
if ( if isinstance(formatter, type) and issubclass(
isinstance(formatter, type) and formatter, cliff_columns.FormattableColumn
issubclass(formatter, cliff_columns.FormattableColumn)
): ):
data = formatter(data) data = formatter(data)
# or a partial wrapping one (to allow us to pass extra parameters) # or a partial wrapping one (to allow us to pass extra parameters)
elif ( elif (
isinstance(formatter, functools.partial) and isinstance(formatter, functools.partial)
isinstance(formatter.func, type) and and isinstance(formatter.func, type)
issubclass(formatter.func, cliff_columns.FormattableColumn) and issubclass(formatter.func, cliff_columns.FormattableColumn)
): ):
data = formatter(data) data = formatter(data)
# otherwise it's probably a legacy-style function # otherwise it's probably a legacy-style function
@@ -461,7 +478,8 @@ def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
'The usage of formatter functions is now discouraged. ' 'The usage of formatter functions is now discouraged. '
'Consider using cliff.columns.FormattableColumn instead. ' 'Consider using cliff.columns.FormattableColumn instead. '
'See reviews linked with bug 1687955 for more detail.', 'See reviews linked with bug 1687955 for more detail.',
category=DeprecationWarning) category=DeprecationWarning,
)
if data is not None: if data is not None:
data = formatter(data) data = formatter(data)
else: else:
@@ -520,15 +538,17 @@ def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
data = getattr(item, field_name, '') data = getattr(item, field_name, '')
if field in formatters: if field in formatters:
formatter = formatters[field] formatter = formatters[field]
if (isinstance(formatter, type) and issubclass( if isinstance(formatter, type) and issubclass(
formatter, cliff_columns.FormattableColumn)): formatter, cliff_columns.FormattableColumn
):
data = formatter(data) data = formatter(data)
elif callable(formatter): elif callable(formatter):
warnings.warn( warnings.warn(
'The usage of formatter functions is now discouraged. ' 'The usage of formatter functions is now discouraged. '
'Consider using cliff.columns.FormattableColumn instead. ' 'Consider using cliff.columns.FormattableColumn instead. '
'See reviews linked with bug 1687955 for more detail.', 'See reviews linked with bug 1687955 for more detail.',
category=DeprecationWarning) category=DeprecationWarning,
)
if data is not None: if data is not None:
data = formatter(data) data = formatter(data)
else: else:
@@ -561,8 +581,11 @@ def get_password(stdin, prompt=None, confirm=True):
def is_ascii(string): def is_ascii(string):
try: try:
(string.decode('ascii') if isinstance(string, bytes) (
else string.encode('ascii')) string.decode('ascii')
if isinstance(string, bytes)
else string.encode('ascii')
)
return True return True
except (UnicodeEncodeError, UnicodeDecodeError): except (UnicodeEncodeError, UnicodeDecodeError):
return False return False
@@ -607,10 +630,13 @@ def sort_items(items, sort_str, sort_type=None):
"'%(direction)s' is not a valid sort direction for " "'%(direction)s' is not a valid sort direction for "
"sort key %(sort_key)s, use 'asc' or 'desc' instead" "sort key %(sort_key)s, use 'asc' or 'desc' instead"
) )
raise exceptions.CommandError(msg % { raise exceptions.CommandError(
'direction': direction, msg
'sort_key': sort_key, % {
}) 'direction': direction,
'sort_key': sort_key,
}
)
if direction == 'desc': if direction == 'desc':
reverse = True reverse = True
@@ -632,14 +658,16 @@ def sort_items(items, sort_str, sort_type=None):
return items return items
def wait_for_delete(manager, def wait_for_delete(
res_id, manager,
status_field='status', res_id,
error_status=['error'], status_field='status',
exception_name=['NotFound'], error_status=['error'],
sleep_time=5, exception_name=['NotFound'],
timeout=300, sleep_time=5,
callback=None): timeout=300,
callback=None,
):
"""Wait for resource deletion """Wait for resource deletion
:param manager: the manager from which we can get the resource :param manager: the manager from which we can get the resource
@@ -683,13 +711,15 @@ def wait_for_delete(manager,
return False return False
def wait_for_status(status_f, def wait_for_status(
res_id, status_f,
status_field='status', res_id,
success_status=['active'], status_field='status',
error_status=['error'], success_status=['active'],
sleep_time=5, error_status=['error'],
callback=None): sleep_time=5,
callback=None,
):
"""Wait for status change on a resource during a long-running operation """Wait for status change on a resource during a long-running operation
:param status_f: a status function that takes a single id argument :param status_f: a status function that takes a single id argument
@@ -718,9 +748,7 @@ def wait_for_status(status_f,
def get_osc_show_columns_for_sdk_resource( def get_osc_show_columns_for_sdk_resource(
sdk_resource, sdk_resource, osc_column_map, invisible_columns=None
osc_column_map,
invisible_columns=None
): ):
"""Get and filter the display and attribute columns for an SDK resource. """Get and filter the display and attribute columns for an SDK resource.
@@ -740,7 +768,8 @@ def get_osc_show_columns_for_sdk_resource(
# 100% sdk compatible. Unless we introduce SDK test/fake resources we # 100% sdk compatible. Unless we introduce SDK test/fake resources we
# should check presence of the specific method # should check presence of the specific method
resource_dict = sdk_resource.to_dict( resource_dict = sdk_resource.to_dict(
body=True, headers=False, ignore_none=False) body=True, headers=False, ignore_none=False
)
else: else:
# We might land here with not a real SDK Resource (during the # We might land here with not a real SDK Resource (during the
# transition period). # transition period).

View File

@@ -44,15 +44,29 @@ def get_column_definitions(attr_map, long_listing):
""" """
if long_listing: if long_listing:
headers = [hdr for col, hdr, listing_mode in attr_map headers = [
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)] hdr
columns = [col for col, hdr, listing_mode in attr_map for col, hdr, listing_mode in attr_map
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)] if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)
]
columns = [
col
for col, hdr, listing_mode in attr_map
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)
]
else: else:
headers = [hdr for col, hdr, listing_mode in attr_map if listing_mode headers = [
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)] hdr
columns = [col for col, hdr, listing_mode in attr_map if listing_mode for col, hdr, listing_mode in attr_map
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)] if listing_mode
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)
]
columns = [
col
for col, hdr, listing_mode in attr_map
if listing_mode
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)
]
return headers, columns return headers, columns
@@ -92,8 +106,8 @@ def get_columns(item, attr_map=None):
attr_map = attr_map or tuple([]) attr_map = attr_map or tuple([])
_attr_map_dict = dict((col, hdr) for col, hdr, listing_mode in attr_map) _attr_map_dict = dict((col, hdr) for col, hdr, listing_mode in attr_map)
columns = [(column, _attr_map_dict.get(column, column)) columns = [
for column in item.keys()] (column, _attr_map_dict.get(column, column)) for column in item.keys()
]
columns = sorted(columns, key=operator.itemgetter(1)) columns = sorted(columns, key=operator.itemgetter(1))
return (tuple(col[0] for col in columns), return (tuple(col[0] for col in columns), tuple(col[1] for col in columns))
tuple(col[1] for col in columns))

View File

@@ -17,13 +17,13 @@ from osc_lib.i18n import _
class _CommaListAction(argparse.Action): class _CommaListAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values.split(',')) setattr(namespace, self.dest, values.split(','))
def add_tag_filtering_option_to_parser( def add_tag_filtering_option_to_parser(
parser, resource_name, enhance_help=lambda _h: _h): parser, resource_name, enhance_help=lambda _h: _h
):
"""Add tag filtering options to a parser. """Add tag filtering options to a parser.
:param parser: argparse.Argument parser object. :param parser: argparse.Argument parser object.
@@ -39,32 +39,48 @@ def add_tag_filtering_option_to_parser(
metavar='<tag>[,<tag>,...]', metavar='<tag>[,<tag>,...]',
action=_CommaListAction, action=_CommaListAction,
help=enhance_help( help=enhance_help(
_('List %s which have all given tag(s) ' _(
'(Comma-separated list of tags)') % resource_name) 'List %s which have all given tag(s) '
'(Comma-separated list of tags)'
)
% resource_name
),
) )
parser.add_argument( parser.add_argument(
'--any-tags', '--any-tags',
metavar='<tag>[,<tag>,...]', metavar='<tag>[,<tag>,...]',
action=_CommaListAction, action=_CommaListAction,
help=enhance_help( help=enhance_help(
_('List %s which have any given tag(s) ' _(
'(Comma-separated list of tags)') % resource_name) 'List %s which have any given tag(s) '
'(Comma-separated list of tags)'
)
% resource_name
),
) )
parser.add_argument( parser.add_argument(
'--not-tags', '--not-tags',
metavar='<tag>[,<tag>,...]', metavar='<tag>[,<tag>,...]',
action=_CommaListAction, action=_CommaListAction,
help=enhance_help( help=enhance_help(
_('Exclude %s which have all given tag(s) ' _(
'(Comma-separated list of tags)') % resource_name) 'Exclude %s which have all given tag(s) '
'(Comma-separated list of tags)'
)
% resource_name
),
) )
parser.add_argument( parser.add_argument(
'--not-any-tags', '--not-any-tags',
metavar='<tag>[,<tag>,...]', metavar='<tag>[,<tag>,...]',
action=_CommaListAction, action=_CommaListAction,
help=enhance_help( help=enhance_help(
_('Exclude %s which have any given tag(s) ' _(
'(Comma-separated list of tags)') % resource_name) 'Exclude %s which have any given tag(s) '
'(Comma-separated list of tags)'
)
% resource_name
),
) )
@@ -88,7 +104,8 @@ def get_tag_filtering_args(parsed_args, args):
def add_tag_option_to_parser_for_create( def add_tag_option_to_parser_for_create(
parser, resource_name, enhance_help=lambda _h: _h): parser, resource_name, enhance_help=lambda _h: _h
):
"""Add tag options to a parser for create commands. """Add tag options to a parser for create commands.
:param parser: argparse.Argument parser object. :param parser: argparse.Argument parser object.
@@ -106,18 +123,23 @@ def add_tag_option_to_parser_for_create(
dest='tags', dest='tags',
metavar='<tag>', metavar='<tag>',
help=enhance_help( help=enhance_help(
_("Tag to be added to the %s " _(
"(repeat option to set multiple tags)") % resource_name) "Tag to be added to the %s "
"(repeat option to set multiple tags)"
)
% resource_name
),
) )
tag_group.add_argument( tag_group.add_argument(
'--no-tag', '--no-tag',
action='store_true', action='store_true',
help=enhance_help(_("No tags associated with the %s") % resource_name) help=enhance_help(_("No tags associated with the %s") % resource_name),
) )
def add_tag_option_to_parser_for_set( def add_tag_option_to_parser_for_set(
parser, resource_name, enhance_help=lambda _h: _h): parser, resource_name, enhance_help=lambda _h: _h
):
"""Add tag options to a parser for set commands. """Add tag options to a parser for set commands.
:param parser: argparse.Argument parser object. :param parser: argparse.Argument parser object.
@@ -134,20 +156,29 @@ def add_tag_option_to_parser_for_set(
dest='tags', dest='tags',
metavar='<tag>', metavar='<tag>',
help=enhance_help( help=enhance_help(
_("Tag to be added to the %s " _(
"(repeat option to set multiple tags)") % resource_name) "Tag to be added to the %s "
"(repeat option to set multiple tags)"
)
% resource_name
),
) )
parser.add_argument( parser.add_argument(
'--no-tag', '--no-tag',
action='store_true', action='store_true',
help=enhance_help( help=enhance_help(
_("Clear tags associated with the %s. Specify both " _(
"--tag and --no-tag to overwrite current tags") % resource_name) "Clear tags associated with the %s. Specify both "
"--tag and --no-tag to overwrite current tags"
)
% resource_name
),
) )
def add_tag_option_to_parser_for_unset( def add_tag_option_to_parser_for_unset(
parser, resource_name, enhance_help=lambda _h: _h): parser, resource_name, enhance_help=lambda _h: _h
):
"""Add tag options to a parser for set commands. """Add tag options to a parser for set commands.
:param parser: argparse.Argument parser object. :param parser: argparse.Argument parser object.
@@ -165,13 +196,20 @@ def add_tag_option_to_parser_for_unset(
dest='tags', dest='tags',
metavar='<tag>', metavar='<tag>',
help=enhance_help( help=enhance_help(
_("Tag to be removed from the %s " _(
"(repeat option to remove multiple tags)") % resource_name)) "Tag to be removed from the %s "
"(repeat option to remove multiple tags)"
)
% resource_name
),
)
tag_group.add_argument( tag_group.add_argument(
'--all-tag', '--all-tag',
action='store_true', action='store_true',
help=enhance_help( help=enhance_help(
_("Clear all tags associated with the %s") % resource_name)) _("Clear all tags associated with the %s") % resource_name
),
)
def update_tags_for_set(client, obj, parsed_args): def update_tags_for_set(client, obj, parsed_args):

View File

@@ -32,6 +32,7 @@
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
from sphinx.util import logging from sphinx.util import logging
# According to the discussion in # According to the discussion in
# https://github.com/sphinx-doc/sphinx/issues/10112 this may be applied as a # https://github.com/sphinx-doc/sphinx/issues/10112 this may be applied as a
# dirty hack until the issue with replacing extlinks is resolved # dirty hack until the issue with replacing extlinks is resolved
@@ -248,10 +249,8 @@ htmlhelp_basename = 'OSC_LIBReleaseNotesdoc'
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper', # 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt', # 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
# 'preamble': '', # 'preamble': '',
} }
@@ -259,12 +258,14 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [( latex_documents = [
'index', (
'OSC_LIBReleaseNotes.tex', 'index',
'osc-lib Release Notes Documentation', 'OSC_LIBReleaseNotes.tex',
'osc-lib Developers', 'osc-lib Release Notes Documentation',
'manual'), 'osc-lib Developers',
'manual',
),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@@ -298,13 +299,15 @@ latex_documents = [(
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [( man_pages = [
'index', (
'osc_libreleasenotes', 'index',
'osc-lib Release Notes Documentation', 'osc_libreleasenotes',
['osc-lib Developers'], 'osc-lib Release Notes Documentation',
1, ['osc-lib Developers'],
)] 1,
)
]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
# #
@@ -316,15 +319,17 @@ man_pages = [(
# Grouping the document tree into Texinfo files. List of tuples # Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [( texinfo_documents = [
'index', (
'OSC_LIBReleaseNotes', 'index',
'osc-lib Release Notes Documentation', 'OSC_LIBReleaseNotes',
'osc-lib Developers', 'osc-lib Release Notes Documentation',
'OSC_LIBReleaseNotes', 'osc-lib Developers',
'Common base library for OpenStackClient plugins.', 'OSC_LIBReleaseNotes',
'Miscellaneous', 'Common base library for OpenStackClient plugins.',
)] 'Miscellaneous',
)
]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
# #

View File

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

16
tox.ini
View File

@@ -62,9 +62,17 @@ commands =
[flake8] [flake8]
show-source = True show-source = True
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools
# If 'ignore' is not set there are default errors and warnings that are set # The following are ignored on purpose. It's not super worth it to fix them.
# Doc: http://flake8.readthedocs.org/en/latest/config.html#default # However, if you feel strongly about it, patches will be accepted to fix them
ignore = W504 # if they fix ALL of the occurances of one and only one of them.
# E203 Black will put spaces after colons in list comprehensions
# E501 Black takes care of line length for us
# H238 New Style Classes are the default in Python3
# H301 Black will put commas after imports that can't fit on one line
# H4 Are about docstrings and there's just a huge pile of pre-existing issues.
# W503 Is supposed to be off by default but in the latest pycodestyle isn't.
# Also, both openstacksdk and Donald Knuth disagree with the rule. Line
# breaks should occur before the binary operator for readability.
ignore = E203, E501, H301, H238, H4, W503
import-order-style = pep8 import-order-style = pep8
application-import-names = osc_lib application-import-names = osc_lib
filename = *.py