summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/auth.py166
-rw-r--r--openstackclient/common/clientmanager.py24
-rw-r--r--openstackclient/common/limits.py29
-rw-r--r--openstackclient/common/quota.py12
-rw-r--r--openstackclient/common/session.py50
-rw-r--r--openstackclient/common/timing.py10
-rw-r--r--openstackclient/compute/client.py20
-rw-r--r--openstackclient/compute/v2/security_group.py5
-rw-r--r--openstackclient/compute/v2/server.py11
-rw-r--r--openstackclient/identity/common.py64
-rw-r--r--openstackclient/identity/v3/group.py7
-rw-r--r--openstackclient/identity/v3/identity_provider.py68
-rw-r--r--openstackclient/identity/v3/project.py18
-rw-r--r--openstackclient/identity/v3/role.py81
-rw-r--r--openstackclient/identity/v3/role_assignment.py17
-rw-r--r--openstackclient/identity/v3/trust.py9
-rw-r--r--openstackclient/identity/v3/user.py9
-rw-r--r--openstackclient/image/client.py56
-rw-r--r--openstackclient/image/v1/image.py16
-rw-r--r--openstackclient/image/v2/image.py16
-rw-r--r--openstackclient/shell.py129
-rw-r--r--openstackclient/tests/common/test_clientmanager.py87
-rw-r--r--openstackclient/tests/common/test_timing.py17
-rw-r--r--openstackclient/tests/compute/v2/fakes.py6
-rw-r--r--openstackclient/tests/compute/v2/test_security_group.py197
-rw-r--r--openstackclient/tests/compute/v2/test_server.py8
-rw-r--r--openstackclient/tests/identity/v3/fakes.py12
-rw-r--r--openstackclient/tests/identity/v3/test_identity_provider.py236
-rw-r--r--openstackclient/tests/identity/v3/test_project.py100
-rw-r--r--openstackclient/tests/image/v1/test_image.py53
-rw-r--r--openstackclient/tests/image/v2/fakes.py93
-rw-r--r--openstackclient/tests/image/v2/test_image.py63
-rw-r--r--openstackclient/tests/test_shell.py427
-rw-r--r--openstackclient/tests/volume/test_find_resource.py7
-rw-r--r--openstackclient/volume/client.py20
-rw-r--r--openstackclient/volume/v1/backup.py2
36 files changed, 1546 insertions, 599 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py
index ba51bee1..9fb26e71 100644
--- a/openstackclient/api/auth.py
+++ b/openstackclient/api/auth.py
@@ -27,29 +27,49 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
-
# Initialize the list of Authentication plugins early in order
# to get the command-line options
-PLUGIN_LIST = stevedore.ExtensionManager(
- base.PLUGIN_NAMESPACE,
- invoke_on_load=False,
- propagate_map_exceptions=True,
-)
+PLUGIN_LIST = None
-# Get the command line options so the help action has them available
+# List of plugin command line options
OPTIONS_LIST = {}
-for plugin in PLUGIN_LIST:
- for o in plugin.plugin.get_options():
- os_name = o.dest.lower().replace('_', '-')
- os_env_name = 'OS_' + os_name.upper().replace('-', '_')
- OPTIONS_LIST.setdefault(os_name, {'env': os_env_name, 'help': ''})
- # TODO(mhu) simplistic approach, would be better to only add
- # help texts if they vary from one auth plugin to another
- # also the text rendering is ugly in the CLI ...
- OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % (
- plugin.name,
- o.help,
+
+
+def get_plugin_list():
+ """Gather plugin list and cache it"""
+
+ global PLUGIN_LIST
+
+ if PLUGIN_LIST is None:
+ PLUGIN_LIST = stevedore.ExtensionManager(
+ base.PLUGIN_NAMESPACE,
+ invoke_on_load=False,
+ propagate_map_exceptions=True,
)
+ return PLUGIN_LIST
+
+
+def get_options_list():
+ """Gather plugin options so the help action has them available"""
+
+ global OPTIONS_LIST
+
+ if not OPTIONS_LIST:
+ for plugin in get_plugin_list():
+ for o in plugin.plugin.get_options():
+ os_name = o.dest.lower().replace('_', '-')
+ os_env_name = 'OS_' + os_name.upper().replace('-', '_')
+ OPTIONS_LIST.setdefault(
+ os_name, {'env': os_env_name, 'help': ''},
+ )
+ # TODO(mhu) simplistic approach, would be better to only add
+ # help texts if they vary from one auth plugin to another
+ # also the text rendering is ugly in the CLI ...
+ OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % (
+ plugin.name,
+ o.help,
+ )
+ return OPTIONS_LIST
def select_auth_plugin(options):
@@ -57,25 +77,27 @@ def select_auth_plugin(options):
auth_plugin_name = None
- if options.os_auth_type in [plugin.name for plugin in PLUGIN_LIST]:
- # A direct plugin name was given, use it
- return options.os_auth_type
-
- if options.os_url and options.os_token:
+ # Do the token/url check first as this must override the default
+ # 'password' set by os-client-config
+ # Also, url and token are not copied into o-c-c's auth dict (yet?)
+ if options.auth.get('url', None) and options.auth.get('token', None):
# service token authentication
auth_plugin_name = 'token_endpoint'
- elif options.os_username:
- if options.os_identity_api_version == '3':
+ elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]:
+ # A direct plugin name was given, use it
+ auth_plugin_name = options.auth_type
+ elif options.auth.get('username', None):
+ if options.identity_api_version == '3':
auth_plugin_name = 'v3password'
- elif options.os_identity_api_version == '2.0':
+ elif options.identity_api_version.startswith('2'):
auth_plugin_name = 'v2password'
else:
# let keystoneclient figure it out itself
auth_plugin_name = 'osc_password'
- elif options.os_token:
- if options.os_identity_api_version == '3':
+ elif options.auth.get('token', None):
+ if options.identity_api_version == '3':
auth_plugin_name = 'v3token'
- elif options.os_identity_api_version == '2.0':
+ elif options.identity_api_version.startswith('2'):
auth_plugin_name = 'v2token'
else:
# let keystoneclient figure it out itself
@@ -89,35 +111,27 @@ def select_auth_plugin(options):
def build_auth_params(auth_plugin_name, cmd_options):
- auth_params = {}
+
+ auth_params = dict(cmd_options.auth)
if auth_plugin_name:
LOG.debug('auth_type: %s', auth_plugin_name)
auth_plugin_class = base.get_plugin_class(auth_plugin_name)
- plugin_options = auth_plugin_class.get_options()
- for option in plugin_options:
- option_name = 'os_' + option.dest
- LOG.debug('fetching option %s' % option_name)
- auth_params[option.dest] = getattr(cmd_options, option_name, None)
# grab tenant from project for v2.0 API compatibility
if auth_plugin_name.startswith("v2"):
- auth_params['tenant_id'] = getattr(
- cmd_options,
- 'os_project_id',
- None,
- )
- auth_params['tenant_name'] = getattr(
- cmd_options,
- 'os_project_name',
- None,
- )
+ if 'project_id' in auth_params:
+ auth_params['tenant_id'] = auth_params['project_id']
+ del auth_params['project_id']
+ if 'project_name' in auth_params:
+ auth_params['tenant_name'] = auth_params['project_name']
+ del auth_params['project_name']
else:
LOG.debug('no auth_type')
# delay the plugin choice, grab every option
- plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST])
+ auth_plugin_class = None
+ plugin_options = set([o.replace('-', '_') for o in get_options_list()])
for option in plugin_options:
- option_name = 'os_' + option
- LOG.debug('fetching option %s' % option_name)
- auth_params[option] = getattr(cmd_options, option_name, None)
+ LOG.debug('fetching option %s' % option)
+ auth_params[option] = getattr(cmd_options.auth, option, None)
return (auth_plugin_class, auth_params)
@@ -126,15 +140,29 @@ def check_valid_auth_options(options, auth_plugin_name):
msg = ''
if auth_plugin_name.endswith('password'):
- if not options.os_username:
- msg += _('Set a username with --os-username or OS_USERNAME\n')
- if not options.os_auth_url:
- msg += _('Set an authentication URL, with --os-auth-url or'
- ' OS_AUTH_URL\n')
- if (not options.os_project_id and not options.os_domain_id and not
- options.os_domain_name and not options.os_project_name):
+ if not options.auth.get('username', None):
+ msg += _('Set a username with --os-username, OS_USERNAME,'
+ ' or auth.username\n')
+ if not options.auth.get('auth_url', None):
+ msg += _('Set an authentication URL, with --os-auth-url,'
+ ' OS_AUTH_URL or auth.auth_url\n')
+ if (not options.auth.get('project_id', None) and not
+ options.auth.get('domain_id', None) and not
+ options.auth.get('domain_name', None) and not
+ options.auth.get('project_name', None)):
msg += _('Set a scope, such as a project or domain, with '
- '--os-project-name or OS_PROJECT_NAME')
+ '--os-project-name, OS_PROJECT_NAME or auth.project_name')
+ elif auth_plugin_name.endswith('token'):
+ if not options.auth.get('token', None):
+ msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n')
+ if not options.auth.get('auth_url', None):
+ msg += _('Set a service AUTH_URL, with --os-auth-url, '
+ 'OS_AUTH_URL or auth.auth_url\n')
+ elif auth_plugin_name == 'token_endpoint':
+ if not options.auth.get('token', None):
+ msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n')
+ if not options.auth.get('url', None):
+ msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n')
if msg:
raise exc.CommandError('Missing parameter(s): \n%s' % msg)
@@ -147,10 +175,11 @@ def build_auth_plugins_option_parser(parser):
authentication plugin.
"""
- available_plugins = [plugin.name for plugin in PLUGIN_LIST]
+ available_plugins = [plugin.name for plugin in get_plugin_list()]
parser.add_argument(
'--os-auth-type',
metavar='<auth-type>',
+ dest='auth_type',
default=utils.env('OS_AUTH_TYPE'),
help='Select an auhentication type. Available types: ' +
', '.join(available_plugins) +
@@ -158,7 +187,7 @@ def build_auth_plugins_option_parser(parser):
' (Env: OS_AUTH_TYPE)',
choices=available_plugins
)
- # make sure we catch old v2.0 env values
+ # Maintain compatibility with old tenant env vars
envs = {
'OS_PROJECT_NAME': utils.env(
'OS_PROJECT_NAME',
@@ -169,16 +198,21 @@ def build_auth_plugins_option_parser(parser):
default=utils.env('OS_TENANT_ID')
),
}
- for o in OPTIONS_LIST:
- # remove allusion to tenants from v2.0 API
+ for o in get_options_list():
+ # Remove tenant options from KSC plugins and replace them below
if 'tenant' not in o:
parser.add_argument(
'--os-' + o,
metavar='<auth-%s>' % o,
- default=envs.get(OPTIONS_LIST[o]['env'],
- utils.env(OPTIONS_LIST[o]['env'])),
- help='%s\n(Env: %s)' % (OPTIONS_LIST[o]['help'],
- OPTIONS_LIST[o]['env']),
+ dest=o.replace('-', '_'),
+ default=envs.get(
+ OPTIONS_LIST[o]['env'],
+ utils.env(OPTIONS_LIST[o]['env']),
+ ),
+ help='%s\n(Env: %s)' % (
+ OPTIONS_LIST[o]['help'],
+ OPTIONS_LIST[o]['env'],
+ ),
)
# add tenant-related options for compatibility
# this is deprecated but still used in some tempest tests...
@@ -186,14 +220,12 @@ def build_auth_plugins_option_parser(parser):
'--os-tenant-name',
metavar='<auth-tenant-name>',
dest='os_project_name',
- default=utils.env('OS_TENANT_NAME'),
help=argparse.SUPPRESS,
)
parser.add_argument(
'--os-tenant-id',
metavar='<auth-tenant-id>',
dest='os_project_id',
- default=utils.env('OS_TENANT_ID'),
help=argparse.SUPPRESS,
)
return parser
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 10f38c25..ca5ece0d 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -19,10 +19,10 @@ import logging
import pkg_resources
import sys
-from keystoneclient import session
import requests
from openstackclient.api import auth
+from openstackclient.common import session as osc_session
from openstackclient.identity import client as identity_client
@@ -58,7 +58,7 @@ class ClientManager(object):
def __init__(
self,
- cli_options,
+ cli_options=None,
api_version=None,
verify=True,
pw_func=None,
@@ -82,8 +82,8 @@ class ClientManager(object):
self._cli_options = cli_options
self._api_version = api_version
self._pw_callback = pw_func
- self._url = self._cli_options.os_url
- self._region_name = self._cli_options.os_region_name
+ self._url = self._cli_options.auth.get('url', None)
+ self._region_name = self._cli_options.region_name
self.timing = self._cli_options.timing
@@ -121,7 +121,7 @@ class ClientManager(object):
# Horrible hack alert...must handle prompt for null password if
# password auth is requested.
if (self.auth_plugin_name.endswith('password') and
- not self._cli_options.os_password):
+ not self._cli_options.auth.get('password', None)):
self._cli_options.os_password = self._pw_callback()
(auth_plugin, self._auth_params) = auth.build_auth_params(
@@ -129,13 +129,15 @@ class ClientManager(object):
self._cli_options,
)
- default_domain = self._cli_options.os_default_domain
+ # TODO(mordred): This is a usability improvement that's broadly useful
+ # We should port it back up into os-client-config.
+ default_domain = self._cli_options.default_domain
# NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
# present, then do not change the behaviour. Otherwise, set the
# PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
if (self._api_version.get('identity') == '3' and
- not self._auth_params.get('project_domain_id') and
- not self._auth_params.get('project_domain_name')):
+ not self._auth_params.get('project_domain_id', None) and
+ not self._auth_params.get('project_domain_name', None)):
self._auth_params['project_domain_id'] = default_domain
# NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
@@ -143,8 +145,8 @@ class ClientManager(object):
# to 'OS_DEFAULT_DOMAIN' for better usability.
if (self._api_version.get('identity') == '3' and
self.auth_plugin_name.endswith('password') and
- not self._auth_params.get('user_domain_id') and
- not self._auth_params.get('user_domain_name')):
+ not self._auth_params.get('user_domain_id', None) and
+ not self._auth_params.get('user_domain_name', None)):
self._auth_params['user_domain_id'] = default_domain
# For compatibility until all clients can be updated
@@ -157,7 +159,7 @@ class ClientManager(object):
self.auth = auth_plugin.load_from_options(**self._auth_params)
# needed by SAML authentication
request_session = requests.session()
- self.session = session.Session(
+ self.session = osc_session.TimingSession(
auth=self.auth,
session=request_session,
verify=self._verify,
diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py
index 9c9458ab..4abcf169 100644
--- a/openstackclient/common/limits.py
+++ b/openstackclient/common/limits.py
@@ -21,6 +21,7 @@ import logging
from cliff import lister
from openstackclient.common import utils
+from openstackclient.identity import common as identity_common
class ShowLimits(lister.Lister):
@@ -49,6 +50,18 @@ class ShowLimits(lister.Lister):
action="store_true",
default=False,
help="Include reservations count [only valid with --absolute]")
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help='Show limits for a specific project (name or ID)'
+ ' [only valid with --absolute]',
+ )
+ parser.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help='Domain that owns --project (name or ID)'
+ ' [only valid with --absolute]',
+ )
return parser
def take_action(self, parsed_args):
@@ -57,7 +70,21 @@ class ShowLimits(lister.Lister):
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
- compute_limits = compute_client.limits.get(parsed_args.is_reserved)
+ project_id = None
+ if parsed_args.project is not None:
+ identity_client = self.app.client_manager.identity
+ if parsed_args.domain is not None:
+ domain = identity_common.find_domain(identity_client,
+ parsed_args.domain)
+ project_id = utils.find_resource(identity_client.projects,
+ parsed_args.project,
+ domain_id=domain.id).id
+ else:
+ project_id = utils.find_resource(identity_client.projects,
+ parsed_args.project).id
+
+ compute_limits = compute_client.limits.get(parsed_args.is_reserved,
+ tenant_id=project_id)
volume_limits = volume_client.limits.get()
if parsed_args.is_absolute:
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index dde4a9ac..ea1dc38f 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -82,6 +82,11 @@ class SetQuota(command.Command):
type=int,
help='New value for the %s quota' % v,
)
+ parser.add_argument(
+ '--volume-type',
+ metavar='<volume-type>',
+ help='Set quotas for a specific <volume-type>',
+ )
return parser
def take_action(self, parsed_args):
@@ -97,8 +102,11 @@ class SetQuota(command.Command):
volume_kwargs = {}
for k, v in VOLUME_QUOTAS.items():
- if v in parsed_args:
- volume_kwargs[k] = getattr(parsed_args, v, None)
+ value = getattr(parsed_args, v, None)
+ if value is not None:
+ if parsed_args.volume_type:
+ k = k + '_%s' % parsed_args.volume_type
+ volume_kwargs[k] = value
if compute_kwargs == {} and volume_kwargs == {}:
sys.stderr.write("No quotas updated")
diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py
new file mode 100644
index 00000000..dda1c417
--- /dev/null
+++ b/openstackclient/common/session.py
@@ -0,0 +1,50 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Subclass of keystoneclient.session"""
+
+from keystoneclient import session
+
+
+class TimingSession(session.Session):
+ """A Session that supports collection of timing data per Method URL"""
+
+ def __init__(
+ self,
+ **kwargs
+ ):
+ """Pass through all arguments except timing"""
+ super(TimingSession, self).__init__(**kwargs)
+
+ # times is a list of tuples: ("method url", elapsed_time)
+ self.times = []
+
+ def get_timings(self):
+ return self.times
+
+ def reset_timings(self):
+ self.times = []
+
+ def request(self, url, method, **kwargs):
+ """Wrap the usual request() method with the timers"""
+ resp = super(TimingSession, self).request(url, method, **kwargs)
+ for h in resp.history:
+ self.times.append((
+ "%s %s" % (h.request.method, h.request.url),
+ h.elapsed,
+ ))
+ self.times.append((
+ "%s %s" % (resp.request.method, resp.request.url),
+ resp.elapsed,
+ ))
+ return resp
diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py
index 1c94682c..d13c86e7 100644
--- a/openstackclient/common/timing.py
+++ b/openstackclient/common/timing.py
@@ -33,10 +33,12 @@ class Timing(lister.Lister):
results = []
total = 0.0
- for url, start, end in self.app.timing_data:
- seconds = end - start
- total += seconds
- results.append((url, seconds))
+ for url, td in self.app.timing_data:
+ # NOTE(dtroyer): Take the long way here because total_seconds()
+ # was added in py27.
+ sec = (td.microseconds + (td.seconds + td.days*86400) * 1e6) / 1e6
+ total += sec
+ results.append((url, sec))
results.append(('Total', total))
return (
column_headers,
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 7ca08a4f..93a7b715 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -15,14 +15,6 @@
import logging
-from novaclient import client as nova_client
-from novaclient import extension
-
-try:
- from novaclient.v2.contrib import list_extensions
-except ImportError:
- from novaclient.v1_1.contrib import list_extensions
-
from openstackclient.common import utils
LOG = logging.getLogger(__name__)
@@ -30,10 +22,22 @@ LOG = logging.getLogger(__name__)
DEFAULT_COMPUTE_API_VERSION = '2'
API_VERSION_OPTION = 'os_compute_api_version'
API_NAME = 'compute'
+API_VERSIONS = {
+ "2": "novaclient.client",
+}
def make_client(instance):
"""Returns a compute service client."""
+
+ # Defer client imports until we actually need them
+ from novaclient import client as nova_client
+ from novaclient import extension
+ try:
+ from novaclient.v2.contrib import list_extensions
+ except ImportError:
+ from novaclient.v1_1.contrib import list_extensions
+
compute_client = nova_client.get_client_class(
instance._api_version[API_NAME],
)
diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py
index d4643438..55405810 100644
--- a/openstackclient/compute/v2/security_group.py
+++ b/openstackclient/compute/v2/security_group.py
@@ -81,9 +81,11 @@ class CreateSecurityGroup(show.ShowOne):
compute_client = self.app.client_manager.compute
+ description = parsed_args.description or parsed_args.name
+
data = compute_client.security_groups.create(
parsed_args.name,
- parsed_args.description,
+ description,
)
info = {}
@@ -290,6 +292,7 @@ class CreateSecurityGroupRule(show.ShowOne):
parser.add_argument(
"--dst-port",
metavar="<port-range>",
+ default=(0, 0),
action=parseractions.RangeAction,
help="Destination port, may be a range: 137:139 (default: 0; "
"only required for proto tcp and udp)",
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 49ef18b2..e4e96ee7 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -275,10 +275,17 @@ class CreateServer(show.ShowOne):
)
parser.add_argument(
'--nic',
- metavar='<nic-config-string>',
+ metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
+ "port-id=port-uuid>",
action='append',
default=[],
- help=_('Specify NIC configuration (optional extension)'),
+ help=_("Create a NIC on the server. "
+ "Specify option multiple times to create multiple NICs. "
+ "Either net-id or port-id must be provided, but not both. "
+ "net-id: attach NIC to network with this UUID, "
+ "port-id: attach NIC to port with this UUID, "
+ "v4-fixed-ip: IPv4 fixed address for NIC (optional), "
+ "v6-fixed-ip: IPv6 fixed address for NIC (optional)."),
)
parser.add_argument(
'--hint',
diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py
index 253729bd..2cc68c8d 100644
--- a/openstackclient/identity/common.py
+++ b/openstackclient/identity/common.py
@@ -17,6 +17,10 @@
from keystoneclient import exceptions as identity_exc
from keystoneclient.v3 import domains
+from keystoneclient.v3 import groups
+from keystoneclient.v3 import projects
+from keystoneclient.v3 import users
+
from openstackclient.common import exceptions
from openstackclient.common import utils
@@ -40,20 +44,58 @@ def find_service(identity_client, name_type_or_id):
def find_domain(identity_client, name_or_id):
- """Find a domain.
+ return _find_identity_resource(identity_client.domains, name_or_id,
+ domains.Domain)
+
+
+def find_group(identity_client, name_or_id):
+ return _find_identity_resource(identity_client.groups, name_or_id,
+ groups.Group)
+
+
+def find_project(identity_client, name_or_id):
+ return _find_identity_resource(identity_client.projects, name_or_id,
+ projects.Project)
+
+
+def find_user(identity_client, name_or_id):
+ return _find_identity_resource(identity_client.users, name_or_id,
+ users.User)
+
- If the user does not have permissions to access the v3 domain API, e.g.,
- if the user is a project admin, assume that the domain given is the id
- rather than the name. This method is used by the project list command,
- so errors accessing the domain will be ignored and if the user has
- access to the project API, everything will work fine.
+def _find_identity_resource(identity_client_manager, name_or_id,
+ resource_type):
+ """Find a specific identity resource.
+
+ Using keystoneclient's manager, attempt to find a specific resource by its
+ name or ID. If Forbidden to find the resource (a common case if the user
+ does not have permission), then return the resource by creating a local
+ instance of keystoneclient's Resource.
+
+ The parameter identity_client_manager is a keystoneclient manager,
+ for example: keystoneclient.v3.users or keystoneclient.v3.projects.
+
+ The parameter resource_type is a keystoneclient resource, for example:
+ keystoneclient.v3.users.User or keystoneclient.v3.projects.Project.
+
+ :param identity_client_manager: the manager that contains the resource
+ :type identity_client_manager: `keystoneclient.base.CrudManager`
+ :param name_or_id: the resources's name or ID
+ :type name_or_id: string
+ :param resource_type: class that represents the resource type
+ :type resource_type: `keystoneclient.base.Resource`
+
+ :returns: the resource in question
+ :rtype: `keystoneclient.base.Resource`
- Closes bugs #1317478 and #1317485.
"""
+
try:
- dom = utils.find_resource(identity_client.domains, name_or_id)
- if dom is not None:
- return dom
+ identity_resource = utils.find_resource(identity_client_manager,
+ name_or_id)
+ if identity_resource is not None:
+ return identity_resource
except identity_exc.Forbidden:
pass
- return domains.Domain(None, {'id': name_or_id})
+
+ return resource_type(None, {'id': name_or_id, 'name': name_or_id})
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index a2afecb9..91acf3e5 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -137,11 +137,11 @@ class CreateGroup(show.ShowOne):
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
+
+ domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client,
parsed_args.domain).id
- else:
- domain = None
try:
group = identity_client.groups.create(
@@ -228,11 +228,10 @@ class ListGroup(lister.Lister):
self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
+ domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client,
parsed_args.domain).id
- else:
- domain = None
if parsed_args.user:
user = utils.find_resource(
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
index 691446da..80965800 100644
--- a/openstackclient/identity/v3/identity_provider.py
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -35,6 +35,20 @@ class CreateIdentityProvider(show.ShowOne):
metavar='<name>',
help='New identity provider name (must be unique)'
)
+ identity_remote_id_provider = parser.add_mutually_exclusive_group()
+ identity_remote_id_provider.add_argument(
+ '--remote-id',
+ metavar='<remote-id>',
+ action='append',
+ help='Remote IDs to associate with the Identity Provider '
+ '(repeat to provide multiple values)'
+ )
+ identity_remote_id_provider.add_argument(
+ '--remote-id-file',
+ metavar='<file-name>',
+ help='Name of a file that contains many remote IDs to associate '
+ 'with the identity provider, one per line'
+ )
parser.add_argument(
'--description',
metavar='<description>',
@@ -59,8 +73,17 @@ class CreateIdentityProvider(show.ShowOne):
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
+ if parsed_args.remote_id_file:
+ file_content = utils.read_blob_file_contents(
+ parsed_args.remote_id_file)
+ remote_ids = file_content.splitlines()
+ remote_ids = list(map(str.strip, remote_ids))
+ else:
+ remote_ids = (parsed_args.remote_id
+ if parsed_args.remote_id else None)
idp = identity_client.federation.identity_providers.create(
id=parsed_args.identity_provider_id,
+ remote_ids=remote_ids,
description=parsed_args.description,
enabled=parsed_args.enabled)
@@ -119,6 +142,20 @@ class SetIdentityProvider(command.Command):
metavar='<identity-provider>',
help='Identity provider to modify',
)
+ identity_remote_id_provider = parser.add_mutually_exclusive_group()
+ identity_remote_id_provider.add_argument(
+ '--remote-id',
+ metavar='<remote-id>',
+ action='append',
+ help='Remote IDs to associate with the Identity Provider '
+ '(repeat to provide multiple values)'
+ )
+ identity_remote_id_provider.add_argument(
+ '--remote-id-file',
+ metavar='<file-name>',
+ help='Name of a file that contains many remote IDs to associate '
+ 'with the identity provider, one per line'
+ )
enable_identity_provider = parser.add_mutually_exclusive_group()
enable_identity_provider.add_argument(
'--enable',
@@ -136,16 +173,33 @@ class SetIdentityProvider(command.Command):
self.log.debug('take_action(%s)', parsed_args)
federation_client = self.app.client_manager.identity.federation
- if parsed_args.enable is True:
- enabled = True
- elif parsed_args.disable is True:
- enabled = False
- else:
- self.log.error("No changes requested")
+ # Basic argument checking
+ if (not parsed_args.enable and not parsed_args.disable and not
+ parsed_args.remote_id and not parsed_args.remote_id_file):
+ self.log.error('No changes requested')
return (None, None)
+ # Always set remote_ids if either is passed in
+ if parsed_args.remote_id_file:
+ file_content = utils.read_blob_file_contents(
+ parsed_args.remote_id_file)
+ remote_ids = file_content.splitlines()
+ remote_ids = list(map(str.strip, remote_ids))
+ elif parsed_args.remote_id:
+ remote_ids = parsed_args.remote_id
+
+ # Setup keyword args for the client
+ kwargs = {}
+ if parsed_args.enable:
+ kwargs['enabled'] = True
+ if parsed_args.disable:
+ kwargs['enabled'] = False
+ if parsed_args.remote_id_file or parsed_args.remote_id:
+ kwargs['remote_ids'] = remote_ids
+
identity_provider = federation_client.identity_providers.update(
- parsed_args.identity_provider, enabled=enabled)
+ parsed_args.identity_provider, **kwargs)
+
identity_provider._info.pop('links', None)
return zip(*sorted(six.iteritems(identity_provider._info)))
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index 1c93ad5d..0cb3c453 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -47,6 +47,11 @@ class CreateProject(show.ShowOne):
help='Domain owning the project (name or ID)',
)
parser.add_argument(
+ '--parent',
+ metavar='<project>',
+ help='Parent of the project (name or ID)',
+ )
+ parser.add_argument(
'--description',
metavar='<description>',
help='Project description',
@@ -80,11 +85,17 @@ class CreateProject(show.ShowOne):
self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
+ domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client,
parsed_args.domain).id
- else:
- domain = None
+
+ parent = None
+ if parsed_args.parent:
+ parent = utils.find_resource(
+ identity_client.projects,
+ parsed_args.parent,
+ ).id
enabled = True
if parsed_args.disable:
@@ -97,6 +108,7 @@ class CreateProject(show.ShowOne):
project = identity_client.projects.create(
name=parsed_args.name,
domain=domain,
+ parent=parent,
description=parsed_args.description,
enabled=enabled,
**kwargs
@@ -111,8 +123,6 @@ class CreateProject(show.ShowOne):
raise e
project._info.pop('links')
- # TODO(stevemar): Remove the line below when we support multitenancy
- project._info.pop('parent_id', None)
return zip(*sorted(six.iteritems(project._info)))
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index 03760709..3dd998ba 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -26,6 +26,7 @@ from keystoneclient import exceptions as ksc_exc
from openstackclient.common import utils
from openstackclient.i18n import _ # noqa
+from openstackclient.identity import common
class AddRole(command.Command):
@@ -78,12 +79,12 @@ class AddRole(command.Command):
)
if parsed_args.user and parsed_args.domain:
- user = utils.find_resource(
- identity_client.users,
+ user = common.find_user(
+ identity_client,
parsed_args.user,
)
- domain = utils.find_resource(
- identity_client.domains,
+ domain = common.find_domain(
+ identity_client,
parsed_args.domain,
)
identity_client.roles.grant(
@@ -92,12 +93,12 @@ class AddRole(command.Command):
domain=domain.id,
)
elif parsed_args.user and parsed_args.project:
- user = utils.find_resource(
- identity_client.users,
+ user = common.find_user(
+ identity_client,
parsed_args.user,
)
- project = utils.find_resource(
- identity_client.projects,
+ project = common.find_project(
+ identity_client,
parsed_args.project,
)
identity_client.roles.grant(
@@ -106,12 +107,12 @@ class AddRole(command.Command):
project=project.id,
)
elif parsed_args.group and parsed_args.domain:
- group = utils.find_resource(
- identity_client.groups,
+ group = common.find_group(
+ identity_client,
parsed_args.group,
)
- domain = utils.find_resource(
- identity_client.domains,
+ domain = common.find_domain(
+ identity_client,
parsed_args.domain,
)
identity_client.roles.grant(
@@ -120,12 +121,12 @@ class AddRole(command.Command):
domain=domain.id,
)
elif parsed_args.group and parsed_args.project:
- group = utils.find_resource(
- identity_client.groups,
+ group = common.find_group(
+ identity_client,
parsed_args.group,
)
- project = utils.find_resource(
- identity_client.projects,
+ project = common.find_project(
+ identity_client,
parsed_args.project,
)
identity_client.roles.grant(
@@ -240,24 +241,24 @@ class ListRole(lister.Lister):
identity_client = self.app.client_manager.identity
if parsed_args.user:
- user = utils.find_resource(
- identity_client.users,
+ user = common.find_user(
+ identity_client,
parsed_args.user,
)
elif parsed_args.group:
- group = utils.find_resource(
- identity_client.groups,
+ group = common.find_group(
+ identity_client,
parsed_args.group,
)
if parsed_args.domain:
- domain = utils.find_resource(
- identity_client.domains,
+ domain = common.find_domain(
+ identity_client,
parsed_args.domain,
)
elif parsed_args.project:
- project = utils.find_resource(
- identity_client.projects,
+ project = common.find_project(
+ identity_client,
parsed_args.project,
)
@@ -370,12 +371,12 @@ class RemoveRole(command.Command):
)
if parsed_args.user and parsed_args.domain:
- user = utils.find_resource(
- identity_client.users,
+ user = common.find_user(
+ identity_client,
parsed_args.user,
)
- domain = utils.find_resource(
- identity_client.domains,
+ domain = common.find_domain(
+ identity_client,
parsed_args.domain,
)
identity_client.roles.revoke(
@@ -384,12 +385,12 @@ class RemoveRole(command.Command):
domain=domain.id,
)
elif parsed_args.user and parsed_args.project:
- user = utils.find_resource(
- identity_client.users,
+ user = common.find_user(
+ identity_client,
parsed_args.user,
)
- project = utils.find_resource(
- identity_client.projects,
+ project = common.find_project(
+ identity_client,
parsed_args.project,
)
identity_client.roles.revoke(
@@ -398,12 +399,12 @@ class RemoveRole(command.Command):
project=project.id,
)
elif parsed_args.group and parsed_args.domain:
- group = utils.find_resource(
- identity_client.groups,
+ group = common.find_group(
+ identity_client,
parsed_args.group,
)
- domain = utils.find_resource(
- identity_client.domains,
+ domain = common.find_domain(
+ identity_client,
parsed_args.domain,
)
identity_client.roles.revoke(
@@ -412,12 +413,12 @@ class RemoveRole(command.Command):
domain=domain.id,
)
elif parsed_args.group and parsed_args.project:
- group = utils.find_resource(
- identity_client.groups,
+ group = common.find_group(
+ identity_client,
parsed_args.group,
)
- project = utils.find_resource(
- identity_client.projects,
+ project = common.find_project(
+ identity_client,
parsed_args.project,
)
identity_client.roles.revoke(
diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py
index f053b608..24e3a7f7 100644
--- a/openstackclient/identity/v3/role_assignment.py
+++ b/openstackclient/identity/v3/role_assignment.py
@@ -18,6 +18,7 @@ import logging
from cliff import lister
from openstackclient.common import utils
+from openstackclient.identity import common
class ListRoleAssignment(lister.Lister):
@@ -80,29 +81,29 @@ class ListRoleAssignment(lister.Lister):
user = None
if parsed_args.user:
- user = utils.find_resource(
- identity_client.users,
+ user = common.find_user(
+ identity_client,
parsed_args.user,
)
domain = None
if parsed_args.domain:
- domain = utils.find_resource(
- identity_client.domains,
+ domain = common.find_domain(
+ identity_client,
parsed_args.domain,
)
project = None
if parsed_args.project:
- project = utils.find_resource(
- identity_client.projects,
+ project = common.find_project(
+ identity_client,
parsed_args.project,
)
group = None
if parsed_args.group:
- group = utils.find_resource(
- identity_client.groups,
+ group = common.find_group(
+ identity_client,
parsed_args.group,
)
diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py
index e67b02e7..ab6673d2 100644
--- a/openstackclient/identity/v3/trust.py
+++ b/openstackclient/identity/v3/trust.py
@@ -92,23 +92,20 @@ class CreateTrust(show.ShowOne):
self.log.debug('take_action(%s)' % parsed_args)
identity_client = self.app.client_manager.identity
+ project_domain = None
if parsed_args.project_domain:
project_domain = common.find_domain(identity_client,
parsed_args.project_domain).id
- else:
- project_domain = None
+ trustor_domain = None
if parsed_args.trustor_domain:
trustor_domain = common.find_domain(identity_client,
parsed_args.trustor_domain).id
- else:
- trustor_domain = None
+ trustee_domain = None
if parsed_args.trustee_domain:
trustee_domain = common.find_domain(identity_client,
parsed_args.trustee_domain).id
- else:
- trustee_domain = None
# NOTE(stevemar): Find the two users, project and roles that
# are necessary for making a trust usable, the API dictates that
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index 0a154f64..c1a0a43c 100644
--- a/openstackclient/identity/v3/user.py
+++ b/openstackclient/identity/v3/user.py
@@ -94,19 +94,17 @@ class CreateUser(show.ShowOne):
self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
+ project_id = None
if parsed_args.project:
project_id = utils.find_resource(
identity_client.projects,
parsed_args.project,
).id
- else:
- project_id = None
+ domain_id = None
if parsed_args.domain:
domain_id = common.find_domain(identity_client,
parsed_args.domain).id
- else:
- domain_id = None
enabled = True
if parsed_args.disable:
@@ -211,11 +209,10 @@ class ListUser(lister.Lister):
self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
+ domain = None
if parsed_args.domain:
domain = common.find_domain(identity_client,
parsed_args.domain).id
- else:
- domain = None
if parsed_args.group:
group = utils.find_resource(
diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py
index 35779664..c78f4425 100644
--- a/openstackclient/image/client.py
+++ b/openstackclient/image/client.py
@@ -15,9 +15,6 @@
import logging
-from glanceclient import exc as gc_exceptions
-from glanceclient.v1 import client as gc_v1_client
-from glanceclient.v1 import images as gc_v1_images
from openstackclient.common import utils
@@ -27,7 +24,7 @@ DEFAULT_IMAGE_API_VERSION = '1'
API_VERSION_OPTION = 'os_image_api_version'
API_NAME = "image"
API_VERSIONS = {
- "1": "openstackclient.image.client.Client_v1",
+ "1": "glanceclient.v1.client.Client",
"2": "glanceclient.v2.client.Client",
}
@@ -89,54 +86,3 @@ def build_option_parser(parser):
DEFAULT_IMAGE_API_VERSION +
' (Env: OS_IMAGE_API_VERSION)')
return parser
-
-
-# NOTE(dtroyer): glanceclient.v1.image.ImageManager() doesn't have a find()
-# method so add one here until the common client libs arrive
-# A similar subclass will be required for v2
-
-class Client_v1(gc_v1_client.Client):
- """An image v1 client that uses ImageManager_v1"""
-
- def __init__(self, *args, **kwargs):
- super(Client_v1, self).__init__(*args, **kwargs)
- self.images = ImageManager_v1(getattr(self, 'http_client', self))
-
-
-class ImageManager_v1(gc_v1_images.ImageManager):
- """Add find() and findall() to the ImageManager class"""
-
- def find(self, **kwargs):
- """Find a single item with attributes matching ``**kwargs``.
-
- This isn't very efficient: it loads the entire list then filters on
- the Python side.
- """
- rl = self.findall(**kwargs)
- num = len(rl)
-
- if num == 0:
- raise gc_exceptions.NotFound
- elif num > 1:
- raise gc_exceptions.NoUniqueMatch
- else:
- return rl[0]
-
- def findall(self, **kwargs):
- """Find all items with attributes matching ``**kwargs``.
-
- This isn't very efficient: it loads the entire list then filters on
- the Python side.
- """
- found = []
- searches = kwargs.items()
-
- for obj in self.list():
- try:
- if all(getattr(obj, attr) == value
- for (attr, value) in searches):
- found.append(obj)
- except AttributeError:
- continue
-
- return found
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 127a7735..830b99ba 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -190,7 +190,7 @@ class CreateImage(show.ShowOne):
kwargs = {}
copy_attrs = ('name', 'id', 'store', 'container_format',
'disk_format', 'owner', 'size', 'min_disk', 'min_ram',
- 'localtion', 'copy_from', 'volume', 'force',
+ 'location', 'copy_from', 'volume', 'force',
'checksum', 'properties')
for attr in copy_attrs:
if attr in parsed_args:
@@ -348,7 +348,7 @@ class ListImage(lister.Lister):
help='List additional fields in output',
)
- # --page-size has never worked, leave here for silent compatability
+ # --page-size has never worked, leave here for silent compatibility
# We'll implement limit/marker differently later
parser.add_argument(
"--page-size",
@@ -405,7 +405,17 @@ class ListImage(lister.Lister):
columns = ("ID", "Name")
column_headers = columns
- data = image_client.api.image_list(**kwargs)
+ # List of image data received
+ data = []
+ # No pages received yet, so start the page marker at None.
+ marker = None
+ while True:
+ page = image_client.api.image_list(marker=marker, **kwargs)
+ if not page:
+ break
+ data.extend(page)
+ # Set the marker to the id of the last item we received
+ marker = page[-1]['id']
if parsed_args.property:
# NOTE(dtroyer): coerce to a list to subscript it in py3
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index afc99e85..0b2becb8 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -98,7 +98,7 @@ class ListImage(lister.Lister):
help='List additional fields in output',
)
- # --page-size has never worked, leave here for silent compatability
+ # --page-size has never worked, leave here for silent compatibility
# We'll implement limit/marker differently later
parser.add_argument(
"--page-size",
@@ -156,7 +156,17 @@ class ListImage(lister.Lister):
columns = ("ID", "Name")
column_headers = columns
- data = image_client.api.image_list(**kwargs)
+ # List of image data received
+ data = []
+ # No pages received yet, so start the page marker at None.
+ marker = None
+ while True:
+ page = image_client.api.image_list(marker=marker, **kwargs)
+ if not page:
+ break
+ data.extend(page)
+ # Set the marker to the id of the last item we received
+ marker = page[-1]['id']
if parsed_args.property:
# NOTE(dtroyer): coerce to a list to subscript it in py3
@@ -238,5 +248,5 @@ class ShowImage(show.ShowOne):
)
info = {}
- info.update(image._info)
+ info.update(image)
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 3cfd7312..5e291021 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -1,4 +1,5 @@
# Copyright 2012-2013 OpenStack Foundation
+# Copyright 2015 Dean Troyer
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -19,9 +20,11 @@ import getpass
import logging
import sys
import traceback
+import warnings
from cliff import app
from cliff import command
+from cliff import complete
from cliff import help
import openstackclient
@@ -31,6 +34,8 @@ from openstackclient.common import exceptions as exc
from openstackclient.common import timing
from openstackclient.common import utils
+from os_client_config import config as cloud_config
+
DEFAULT_DOMAIN = 'default'
@@ -72,55 +77,24 @@ class OpenStackShell(app.App):
# Some commands do not need authentication
help.HelpCommand.auth_required = False
+ complete.CompleteCommand.auth_required = False
super(OpenStackShell, self).__init__(
description=__doc__.strip(),
version=openstackclient.__version__,
- command_manager=commandmanager.CommandManager('openstack.cli'))
+ command_manager=commandmanager.CommandManager('openstack.cli'),
+ deferred_help=True)
self.api_version = {}
# Until we have command line arguments parsed, dump any stack traces
self.dump_stack_trace = True
- # This is instantiated in initialize_app() only when using
- # password flow auth
- self.auth_client = None
-
# Assume TLS host certificate verification is enabled
self.verify = True
self.client_manager = None
- # NOTE(dtroyer): This hack changes the help action that Cliff
- # automatically adds to the parser so we can defer
- # its execution until after the api-versioned commands
- # have been loaded. There doesn't seem to be a
- # way to edit/remove anything from an existing parser.
-
- # Replace the cliff-added help.HelpAction to defer its execution
- self.DeferredHelpAction = None
- for a in self.parser._actions:
- if type(a) == help.HelpAction:
- # Found it, save and replace it
- self.DeferredHelpAction = a
-
- # These steps are argparse-implementation-dependent
- self.parser._actions.remove(a)
- if self.parser._option_string_actions['-h']:
- del self.parser._option_string_actions['-h']
- if self.parser._option_string_actions['--help']:
- del self.parser._option_string_actions['--help']
-
- # Make a new help option to just set a flag
- self.parser.add_argument(
- '-h', '--help',
- action='store_true',
- dest='deferred_help',
- default=False,
- help="Show this help message and exit",
- )
-
def configure_logging(self):
"""Configure logging for the app
@@ -139,12 +113,15 @@ class OpenStackShell(app.App):
if self.options.verbose_level == 0:
# --quiet
root_logger.setLevel(logging.ERROR)
+ warnings.simplefilter("ignore")
elif self.options.verbose_level == 1:
# This is the default case, no --debug, --verbose or --quiet
root_logger.setLevel(logging.WARNING)
+ warnings.simplefilter("ignore")
elif self.options.verbose_level == 2:
# One --verbose
root_logger.setLevel(logging.INFO)
+ warnings.simplefilter("once")
elif self.options.verbose_level >= 3:
# Two or more --verbose
root_logger.setLevel(logging.DEBUG)
@@ -162,12 +139,11 @@ class OpenStackShell(app.App):
# --debug forces traceback
self.dump_stack_trace = True
requests_log.setLevel(logging.DEBUG)
- cliff_log.setLevel(logging.DEBUG)
else:
self.dump_stack_trace = False
requests_log.setLevel(logging.ERROR)
- cliff_log.setLevel(logging.ERROR)
+ cliff_log.setLevel(logging.ERROR)
stevedore_log.setLevel(logging.ERROR)
iso8601_log.setLevel(logging.ERROR)
@@ -188,10 +164,19 @@ class OpenStackShell(app.App):
description,
version)
+ # service token auth argument
+ parser.add_argument(
+ '--os-cloud',
+ metavar='<cloud-config-name>',
+ dest='cloud',
+ default=utils.env('OS_CLOUD'),
+ help='Cloud name in clouds.yaml (Env: OS_CLOUD)',
+ )
# Global arguments
parser.add_argument(
'--os-region-name',
metavar='<auth-region-name>',
+ dest='region_name',
default=utils.env('OS_REGION_NAME'),
help='Authentication region name (Env: OS_REGION_NAME)')
parser.add_argument(
@@ -236,8 +221,43 @@ class OpenStackShell(app.App):
* authenticate against Identity if requested
"""
+ # Parent __init__ parses argv into self.options
super(OpenStackShell, self).initialize_app(argv)
+ # Resolve the verify/insecure exclusive pair here as cloud_config
+ # doesn't know about verify
+ self.options.insecure = (
+ self.options.insecure and not self.options.verify
+ )
+
+ # Set the default plugin to token_endpoint if rl and token are given
+ if (self.options.url and self.options.token):
+ # Use service token authentication
+ cloud_config.set_default('auth_type', 'token_endpoint')
+ else:
+ cloud_config.set_default('auth_type', 'osc_password')
+ self.log.debug("options: %s", self.options)
+
+ # Do configuration file handling
+ cc = cloud_config.OpenStackConfig()
+ self.log.debug("defaults: %s", cc.defaults)
+
+ self.cloud = cc.get_one_cloud(
+ cloud=self.options.cloud,
+ argparse=self.options,
+ )
+ self.log.debug("cloud cfg: %s", self.cloud.config)
+
+ # Set up client TLS
+ cacert = self.cloud.cacert
+ if cacert:
+ self.verify = cacert
+ else:
+ self.verify = not getattr(self.cloud.config, 'insecure', False)
+
+ # Neutralize verify option
+ self.options.verify = None
+
# Save default domain
self.default_domain = self.options.os_default_domain
@@ -247,6 +267,11 @@ class OpenStackShell(app.App):
if version_opt:
api = mod.API_NAME
self.api_version[api] = version_opt
+ if version_opt not in mod.API_VERSIONS:
+ self.log.warning(
+ "The %s version <%s> is not in supported versions <%s>"
+ % (api, version_opt,
+ ', '.join(mod.API_VERSIONS.keys())))
# Command groups deal only with major versions
version = '.v' + version_opt.replace('.', '_').split('_')[0]
cmd_group = 'openstack.' + api.replace('-', '_') + version
@@ -276,17 +301,10 @@ class OpenStackShell(app.App):
# set up additional clients to stuff in to client_manager??
# Handle deferred help and exit
- if self.options.deferred_help:
- self.DeferredHelpAction(self.parser, self.parser, None, None)
-
- # Set up common client session
- if self.options.os_cacert:
- self.verify = self.options.os_cacert
- else:
- self.verify = not self.options.insecure
+ self.print_help_if_requested()
self.client_manager = clientmanager.ClientManager(
- cli_options=self.options,
+ cli_options=self.cloud,
verify=self.verify,
api_version=self.api_version,
pw_func=prompt_for_password,
@@ -301,26 +319,19 @@ class OpenStackShell(app.App):
cmd.__class__.__name__,
)
if cmd.auth_required:
- try:
- # Trigger the Identity client to initialize
- self.client_manager.auth_ref
- except Exception:
- pass
+ # Trigger the Identity client to initialize
+ self.client_manager.auth_ref
return
def clean_up(self, cmd, result, err):
- self.log.debug('clean_up %s', cmd.__class__.__name__)
-
- if err:
- self.log.debug('got an error: %s', err)
+ self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '')
# Process collected timing data
if self.options.timing:
- # Loop through extensions
- for mod in self.ext_modules:
- client = getattr(self.client_manager, mod.API_NAME)
- if hasattr(client, 'get_timings'):
- self.timing_data.extend(client.get_timings())
+ # Get session data
+ self.timing_data.extend(
+ self.client_manager.session.get_timings(),
+ )
# Use the Timing pseudo-command to generate the output
tcmd = timing.Timing(self, self.options)
diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py
index 3648bf57..26cf4967 100644
--- a/openstackclient/tests/common/test_clientmanager.py
+++ b/openstackclient/tests/common/test_clientmanager.py
@@ -34,6 +34,10 @@ AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access'])
SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF)
+# This is deferred in api.auth but we need it here...
+auth.get_options_list()
+
+
class Container(object):
attr = clientmanager.ClientCache(lambda x: object())
@@ -44,13 +48,14 @@ class Container(object):
class FakeOptions(object):
def __init__(self, **kwargs):
for option in auth.OPTIONS_LIST:
- setattr(self, 'os_' + option.replace('-', '_'), None)
- self.os_auth_type = None
- self.os_identity_api_version = '2.0'
+ setattr(self, option.replace('-', '_'), None)
+ self.auth_type = None
+ self.identity_api_version = '2.0'
self.timing = None
- self.os_region_name = None
- self.os_url = None
- self.os_default_domain = 'default'
+ self.region_name = None
+ self.url = None
+ self.auth = {}
+ self.default_domain = 'default'
self.__dict__.update(kwargs)
@@ -82,9 +87,11 @@ class TestClientManager(utils.TestCase):
client_manager = clientmanager.ClientManager(
cli_options=FakeOptions(
- os_token=fakes.AUTH_TOKEN,
- os_url=fakes.AUTH_URL,
- os_auth_type='token_endpoint',
+ auth_type='token_endpoint',
+ auth=dict(
+ token=fakes.AUTH_TOKEN,
+ url=fakes.AUTH_URL,
+ ),
),
api_version=API_VERSION,
verify=True
@@ -110,9 +117,11 @@ class TestClientManager(utils.TestCase):
client_manager = clientmanager.ClientManager(
cli_options=FakeOptions(
- os_token=fakes.AUTH_TOKEN,
- os_auth_url=fakes.AUTH_URL,
- os_auth_type='v2token',
+ auth=dict(
+ token=fakes.AUTH_TOKEN,
+ auth_url=fakes.AUTH_URL,
+ ),
+ auth_type='v2token',
),
api_version=API_VERSION,
verify=True
@@ -134,10 +143,12 @@ class TestClientManager(utils.TestCase):
client_manager = clientmanager.ClientManager(
cli_options=FakeOptions(
- os_auth_url=fakes.AUTH_URL,
- os_username=fakes.USERNAME,
- os_password=fakes.PASSWORD,
- os_project_name=fakes.PROJECT_NAME,
+ auth=dict(
+ auth_url=fakes.AUTH_URL,
+ username=fakes.USERNAME,
+ password=fakes.PASSWORD,
+ project_name=fakes.PROJECT_NAME,
+ ),
),
api_version=API_VERSION,
verify=False,
@@ -194,11 +205,13 @@ class TestClientManager(utils.TestCase):
client_manager = clientmanager.ClientManager(
cli_options=FakeOptions(
- os_auth_url=fakes.AUTH_URL,
- os_username=fakes.USERNAME,
- os_password=fakes.PASSWORD,
- os_project_name=fakes.PROJECT_NAME,
- os_auth_type='v2password',
+ auth=dict(
+ auth_url=fakes.AUTH_URL,
+ username=fakes.USERNAME,
+ password=fakes.PASSWORD,
+ project_name=fakes.PROJECT_NAME,
+ ),
+ auth_type='v2password',
),
api_version=API_VERSION,
verify='cafile',
@@ -210,8 +223,8 @@ class TestClientManager(utils.TestCase):
self.assertEqual('cafile', client_manager._cacert)
def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name):
- auth_params['os_auth_type'] = auth_plugin_name
- auth_params['os_identity_api_version'] = api_version
+ auth_params['auth_type'] = auth_plugin_name
+ auth_params['identity_api_version'] = api_version
client_manager = clientmanager.ClientManager(
cli_options=FakeOptions(**auth_params),
api_version=API_VERSION,
@@ -226,19 +239,33 @@ class TestClientManager(utils.TestCase):
def test_client_manager_select_auth_plugin(self):
# test token auth
- params = dict(os_token=fakes.AUTH_TOKEN,
- os_auth_url=fakes.AUTH_URL)
+ params = dict(
+ auth=dict(
+ auth_url=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ ),
+ )
self._select_auth_plugin(params, '2.0', 'v2token')
self._select_auth_plugin(params, '3', 'v3token')
self._select_auth_plugin(params, 'XXX', 'token')
# test token/endpoint auth
- params = dict(os_token=fakes.AUTH_TOKEN, os_url='test')
+ params = dict(
+ auth_plugin='token_endpoint',
+ auth=dict(
+ url='test',
+ token=fakes.AUTH_TOKEN,
+ ),
+ )
self._select_auth_plugin(params, 'XXX', 'token_endpoint')
# test password auth
- params = dict(os_auth_url=fakes.AUTH_URL,
- os_username=fakes.USERNAME,
- os_password=fakes.PASSWORD,
- os_project_name=fakes.PROJECT_NAME)
+ params = dict(
+ auth=dict(
+ auth_url=fakes.AUTH_URL,
+ username=fakes.USERNAME,
+ password=fakes.PASSWORD,
+ project_name=fakes.PROJECT_NAME,
+ ),
+ )
self._select_auth_plugin(params, '2.0', 'v2password')
self._select_auth_plugin(params, '3', 'v3password')
self._select_auth_plugin(params, 'XXX', 'password')
diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py
index aa910b91..a7f93b55 100644
--- a/openstackclient/tests/common/test_timing.py
+++ b/openstackclient/tests/common/test_timing.py
@@ -13,14 +13,15 @@
"""Test Timing pseudo-command"""
+import datetime
+
from openstackclient.common import timing
from openstackclient.tests import fakes
from openstackclient.tests import utils
timing_url = 'GET http://localhost:5000'
-timing_start = 1404802774.872809
-timing_end = 1404802775.724802
+timing_elapsed = 0.872809
class FakeGenericClient(object):
@@ -66,9 +67,10 @@ class TestTiming(utils.TestCommand):
self.assertEqual(datalist, data)
def test_timing_list(self):
- self.app.timing_data = [
- (timing_url, timing_start, timing_end),
- ]
+ self.app.timing_data = [(
+ timing_url,
+ datetime.timedelta(microseconds=timing_elapsed*1000000),
+ )]
arglist = []
verifylist = []
@@ -79,9 +81,8 @@ class TestTiming(utils.TestCommand):
collist = ('URL', 'Seconds')
self.assertEqual(collist, columns)
- timing_sec = timing_end - timing_start
datalist = [
- (timing_url, timing_sec),
- ('Total', timing_sec)
+ (timing_url, timing_elapsed),
+ ('Total', timing_elapsed),
]
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index a22c1ce0..c18dea7e 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -16,6 +16,7 @@
import mock
from openstackclient.tests import fakes
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils
@@ -85,6 +86,11 @@ class TestComputev2(utils.TestCommand):
token=fakes.AUTH_TOKEN,
)
+ self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
+
self.app.client_manager.image = image_fakes.FakeImagev2Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py
new file mode 100644
index 00000000..fdb659a8
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_security_group.py
@@ -0,0 +1,197 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import copy
+import mock
+
+from openstackclient.compute.v2 import security_group
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+
+
+security_group_id = '11'
+security_group_name = 'wide-open'
+security_group_description = 'nothing but net'
+
+SECURITY_GROUP = {
+ 'id': security_group_id,
+ 'name': security_group_name,
+ 'description': security_group_description,
+ 'tenant_id': identity_fakes.project_id,
+}
+
+
+class FakeSecurityGroupResource(fakes.FakeResource):
+
+ def get_keys(self):
+ return {'property': 'value'}
+
+
+class TestSecurityGroup(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestSecurityGroup, self).setUp()
+
+ self.secgroups_mock = mock.Mock()
+ self.secgroups_mock.resource_class = fakes.FakeResource(None, {})
+ self.app.client_manager.compute.security_groups = self.secgroups_mock
+ self.secgroups_mock.reset_mock()
+
+ self.projects_mock = mock.Mock()
+ self.projects_mock.resource_class = fakes.FakeResource(None, {})
+ self.app.client_manager.identity.projects = self.projects_mock
+ self.projects_mock.reset_mock()
+
+
+class TestSecurityGroupCreate(TestSecurityGroup):
+
+ def setUp(self):
+ super(TestSecurityGroupCreate, self).setUp()
+
+ self.secgroups_mock.create.return_value = FakeSecurityGroupResource(
+ None,
+ copy.deepcopy(SECURITY_GROUP),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = security_group.CreateSecurityGroup(self.app, None)
+
+ def test_security_group_create_no_options(self):
+ arglist = [
+ security_group_name,
+ ]
+ verifylist = [
+ ('name', security_group_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # SecurityGroupManager.create(name, description)
+ self.secgroups_mock.create.assert_called_with(
+ security_group_name,
+ security_group_name,
+ )
+
+ collist = (
+ 'description',
+ 'id',
+ 'name',
+ 'tenant_id',
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ security_group_description,
+ security_group_id,
+ security_group_name,
+ identity_fakes.project_id,
+ )
+ self.assertEqual(datalist, data)
+
+ def test_security_group_create_description(self):
+ arglist = [
+ security_group_name,
+ '--description', security_group_description,
+ ]
+ verifylist = [
+ ('name', security_group_name),
+ ('description', security_group_description),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # SecurityGroupManager.create(name, description)
+ self.secgroups_mock.create.assert_called_with(
+ security_group_name,
+ security_group_description,
+ )
+
+ collist = (
+ 'description',
+ 'id',
+ 'name',
+ 'tenant_id',
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ security_group_description,
+ security_group_id,
+ security_group_name,
+ identity_fakes.project_id,
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestSecurityGroupList(TestSecurityGroup):
+
+ def setUp(self):
+ super(TestSecurityGroupList, self).setUp()
+
+ self.secgroups_mock.list.return_value = [
+ FakeSecurityGroupResource(
+ None,
+ copy.deepcopy(SECURITY_GROUP),
+ loaded=True,
+ ),
+ ]
+
+ # Get the command object to test
+ self.cmd = security_group.ListSecurityGroup(self.app, None)
+
+ def test_security_group_list_no_options(self):
+ self.projects_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ ),
+ ]
+
+ arglist = []
+ verifylist = [
+ ('all_projects', False),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'search_opts': {
+ 'all_tenants': False,
+ },
+ }
+
+ self.secgroups_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = (
+ 'ID',
+ 'Name',
+ 'Description',
+ )
+ self.assertEqual(collist, columns)
+ datalist = ((
+ security_group_id,
+ security_group_name,
+ security_group_description,
+ ), )
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py
index 079f301e..baf53742 100644
--- a/openstackclient/tests/compute/v2/test_server.py
+++ b/openstackclient/tests/compute/v2/test_server.py
@@ -361,14 +361,14 @@ class TestServerImageCreate(TestServer):
compute_fakes.server_name,
)
- collist = ('id', 'is_public', 'name', 'owner', 'protected')
+ collist = ('id', 'name', 'owner', 'protected', 'visibility')
self.assertEqual(collist, columns)
datalist = (
image_fakes.image_id,
- image_fakes.image_public,
image_fakes.image_name,
image_fakes.image_owner,
image_fakes.image_protected,
+ image_fakes.image_visibility,
)
self.assertEqual(datalist, data)
@@ -392,14 +392,14 @@ class TestServerImageCreate(TestServer):
'img-nam',
)
- collist = ('id', 'is_public', 'name', 'owner', 'protected')
+ collist = ('id', 'name', 'owner', 'protected', 'visibility')
self.assertEqual(collist, columns)
datalist = (
image_fakes.image_id,
- image_fakes.image_public,
image_fakes.image_name,
image_fakes.image_owner,
image_fakes.image_protected,
+ image_fakes.image_visibility,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py
index eb8673ef..dfbcf44f 100644
--- a/openstackclient/tests/identity/v3/fakes.py
+++ b/openstackclient/tests/identity/v3/fakes.py
@@ -135,6 +135,16 @@ REGION = {
'links': base_url + 'regions/' + region_id,
}
+PROJECT_WITH_PARENT = {
+ 'id': project_id + '-with-parent',
+ 'name': project_name + ' and their parents',
+ 'description': project_description + ' plus another four',
+ 'enabled': True,
+ 'domain_id': domain_id,
+ 'parent_id': project_id,
+ 'links': base_url + 'projects/' + (project_id + '-with-parent'),
+}
+
role_id = 'r1'
role_name = 'roller'
@@ -231,9 +241,11 @@ TOKEN_WITH_DOMAIN_ID = {
idp_id = 'test_idp'
idp_description = 'super exciting IdP description'
+idp_remote_ids = ['entity1', 'entity2']
IDENTITY_PROVIDER = {
'id': idp_id,
+ 'remote_ids': idp_remote_ids,
'enabled': True,
'description': idp_description
}
diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py
index 527f01b5..cd328c1d 100644
--- a/openstackclient/tests/identity/v3/test_identity_provider.py
+++ b/openstackclient/tests/identity/v3/test_identity_provider.py
@@ -14,6 +14,8 @@
import copy
+import mock
+
from openstackclient.identity.v3 import identity_provider
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -51,6 +53,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
# Set expected values
kwargs = {
+ 'remote_ids': None,
'enabled': True,
'description': None,
}
@@ -60,12 +63,13 @@ class TestIdentityProviderCreate(TestIdentityProvider):
**kwargs
)
- collist = ('description', 'enabled', 'id')
+ collist = ('description', 'enabled', 'id', 'remote_ids')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -83,6 +87,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
# Set expected values
kwargs = {
+ 'remote_ids': None,
'description': identity_fakes.idp_description,
'enabled': True,
}
@@ -92,12 +97,121 @@ class TestIdentityProviderCreate(TestIdentityProvider):
**kwargs
)
- collist = ('description', 'enabled', 'id')
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
+ )
+ self.assertEqual(datalist, data)
+
+ def test_create_identity_provider_remote_id(self):
+ arglist = [
+ identity_fakes.idp_id,
+ '--remote-id', identity_fakes.idp_remote_ids[0]
+ ]
+ verifylist = [
+ ('identity_provider_id', identity_fakes.idp_id),
+ ('remote_id', identity_fakes.idp_remote_ids[:1]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'remote_ids': identity_fakes.idp_remote_ids[:1],
+ 'description': None,
+ 'enabled': True,
+ }
+
+ self.identity_providers_mock.create.assert_called_with(
+ id=identity_fakes.idp_id,
+ **kwargs
+ )
+
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
+ )
+ self.assertEqual(datalist, data)
+
+ def test_create_identity_provider_remote_ids_multiple(self):
+ arglist = [
+ '--remote-id', identity_fakes.idp_remote_ids[0],
+ '--remote-id', identity_fakes.idp_remote_ids[1],
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider_id', identity_fakes.idp_id),
+ ('remote_id', identity_fakes.idp_remote_ids),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'remote_ids': identity_fakes.idp_remote_ids,
+ 'description': None,
+ 'enabled': True,
+ }
+
+ self.identity_providers_mock.create.assert_called_with(
+ id=identity_fakes.idp_id,
+ **kwargs
+ )
+
+ collist = ('description', 'enabled', 'id', 'remote_ids')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
+ )
+ self.assertEqual(datalist, data)
+
+ def test_create_identity_provider_remote_ids_file(self):
+ arglist = [
+ '--remote-id-file', '/tmp/file_name',
+ identity_fakes.idp_id,
+ ]
+ verifylist = [
+ ('identity_provider_id', identity_fakes.idp_id),
+ ('remote_id_file', '/tmp/file_name'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ mocker = mock.Mock()
+ mocker.return_value = "\n".join(identity_fakes.idp_remote_ids)
+ with mock.patch("openstackclient.identity.v3.identity_provider."
+ "utils.read_blob_file_contents", mocker):
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'remote_ids': identity_fakes.idp_remote_ids,
+ 'description': None,
+ 'enabled': True,
+ }
+
+ self.identity_providers_mock.create.assert_called_with(
+ id=identity_fakes.idp_id,
+ **kwargs
+ )
+
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -123,6 +237,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
# Set expected values
kwargs = {
+ 'remote_ids': None,
'enabled': False,
'description': None,
}
@@ -132,12 +247,13 @@ class TestIdentityProviderCreate(TestIdentityProvider):
**kwargs
)
- collist = ('description', 'enabled', 'id')
+ collist = ('description', 'enabled', 'id', 'remote_ids')
self.assertEqual(collist, columns)
datalist = (
None,
False,
identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -241,12 +357,13 @@ class TestIdentityProviderShow(TestIdentityProvider):
identity_fakes.idp_id,
)
- collist = ('description', 'enabled', 'id')
+ collist = ('description', 'enabled', 'id', 'remote_ids')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -276,11 +393,14 @@ class TestIdentityProviderSet(TestIdentityProvider):
prepare(self)
arglist = [
'--disable', identity_fakes.idp_id,
+ '--remote-id', identity_fakes.idp_remote_ids[0],
+ '--remote-id', identity_fakes.idp_remote_ids[1]
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
('enable', False),
('disable', True),
+ ('remote_id', identity_fakes.idp_remote_ids)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -288,14 +408,16 @@ class TestIdentityProviderSet(TestIdentityProvider):
self.identity_providers_mock.update.assert_called_with(
identity_fakes.idp_id,
enabled=False,
+ remote_ids=identity_fakes.idp_remote_ids
)
- collist = ('description', 'enabled', 'id')
+ collist = ('description', 'enabled', 'id', 'remote_ids')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.idp_description,
False,
identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -316,23 +438,122 @@ class TestIdentityProviderSet(TestIdentityProvider):
prepare(self)
arglist = [
'--enable', identity_fakes.idp_id,
+ '--remote-id', identity_fakes.idp_remote_ids[0],
+ '--remote-id', identity_fakes.idp_remote_ids[1]
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
('enable', True),
('disable', False),
+ ('remote_id', identity_fakes.idp_remote_ids)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.identity_providers_mock.update.assert_called_with(
- identity_fakes.idp_id, enabled=True)
- collist = ('description', 'enabled', 'id')
+ identity_fakes.idp_id, enabled=True,
+ remote_ids=identity_fakes.idp_remote_ids)
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
+ )
+ self.assertEqual(datalist, data)
+
+ def test_identity_provider_replace_remote_ids(self):
+ """Enable Identity Provider.
+
+ Set Identity Provider's ``enabled`` attribute to True.
+ """
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ self.new_remote_id = 'new_entity'
+
+ updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER)
+ updated_idp['remote_ids'] = [self.new_remote_id]
+ resources = fakes.FakeResource(
+ None,
+ updated_idp,
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ arglist = [
+ '--enable', identity_fakes.idp_id,
+ '--remote-id', self.new_remote_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('enable', True),
+ ('disable', False),
+ ('remote_id', [self.new_remote_id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.update.assert_called_with(
+ identity_fakes.idp_id, enabled=True,
+ remote_ids=[self.new_remote_id])
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id,
+ [self.new_remote_id]
+ )
+ self.assertEqual(datalist, data)
+
+ def test_identity_provider_replace_remote_ids_file(self):
+ """Enable Identity Provider.
+
+ Set Identity Provider's ``enabled`` attribute to True.
+ """
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ self.new_remote_id = 'new_entity'
+
+ updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER)
+ updated_idp['remote_ids'] = [self.new_remote_id]
+ resources = fakes.FakeResource(
+ None,
+ updated_idp,
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ arglist = [
+ '--enable', identity_fakes.idp_id,
+ '--remote-id-file', self.new_remote_id,
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('enable', True),
+ ('disable', False),
+ ('remote_id_file', self.new_remote_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ mocker = mock.Mock()
+ mocker.return_value = self.new_remote_id
+ with mock.patch("openstackclient.identity.v3.identity_provider."
+ "utils.read_blob_file_contents", mocker):
+ columns, data = self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.update.assert_called_with(
+ identity_fakes.idp_id, enabled=True,
+ remote_ids=[self.new_remote_id])
+ collist = ('description', 'enabled', 'id', 'remote_ids')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
+ [self.new_remote_id]
)
self.assertEqual(datalist, data)
@@ -361,6 +582,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
('identity_provider', identity_fakes.idp_id),
('enable', False),
('disable', False),
+ ('remote_id', None)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py
index 874908da..ec50da0c 100644
--- a/openstackclient/tests/identity/v3/test_project.py
+++ b/openstackclient/tests/identity/v3/test_project.py
@@ -16,6 +16,7 @@
import copy
import mock
+from openstackclient.common import exceptions
from openstackclient.identity.v3 import project
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -60,6 +61,7 @@ class TestProjectCreate(TestProject):
identity_fakes.project_name,
]
verifylist = [
+ ('parent', None),
('enable', False),
('disable', False),
('name', identity_fakes.project_name),
@@ -75,6 +77,7 @@ class TestProjectCreate(TestProject):
'domain': None,
'description': None,
'enabled': True,
+ 'parent': None,
}
# ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs)
@@ -103,6 +106,7 @@ class TestProjectCreate(TestProject):
('enable', False),
('disable', False),
('name', identity_fakes.project_name),
+ ('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -115,6 +119,7 @@ class TestProjectCreate(TestProject):
'domain': None,
'description': 'new desc',
'enabled': True,
+ 'parent': None,
}
# ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs)
@@ -143,6 +148,7 @@ class TestProjectCreate(TestProject):
('enable', False),
('disable', False),
('name', identity_fakes.project_name),
+ ('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -155,6 +161,7 @@ class TestProjectCreate(TestProject):
'domain': identity_fakes.domain_id,
'description': None,
'enabled': True,
+ 'parent': None,
}
# ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs)
@@ -183,6 +190,7 @@ class TestProjectCreate(TestProject):
('enable', False),
('disable', False),
('name', identity_fakes.project_name),
+ ('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
@@ -197,6 +205,7 @@ class TestProjectCreate(TestProject):
'domain': identity_fakes.domain_id,
'description': None,
'enabled': True,
+ 'parent': None,
}
self.projects_mock.create.assert_called_with(
**kwargs
@@ -221,6 +230,7 @@ class TestProjectCreate(TestProject):
('enable', True),
('disable', False),
('name', identity_fakes.project_name),
+ ('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -233,6 +243,7 @@ class TestProjectCreate(TestProject):
'domain': None,
'description': None,
'enabled': True,
+ 'parent': None,
}
# ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs)
@@ -260,6 +271,7 @@ class TestProjectCreate(TestProject):
('enable', False),
('disable', True),
('name', identity_fakes.project_name),
+ ('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -272,6 +284,7 @@ class TestProjectCreate(TestProject):
'domain': None,
'description': None,
'enabled': False,
+ 'parent': None,
}
# ProjectManager.create(name=, domain=,
# description=, enabled=, **kwargs)
@@ -311,6 +324,7 @@ class TestProjectCreate(TestProject):
'domain': None,
'description': None,
'enabled': True,
+ 'parent': None,
'fee': 'fi',
'fo': 'fum',
}
@@ -331,6 +345,92 @@ class TestProjectCreate(TestProject):
)
self.assertEqual(datalist, data)
+ def test_project_create_parent(self):
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+ self.projects_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT),
+ loaded=True,
+ )
+
+ arglist = [
+ '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'],
+ '--parent', identity_fakes.PROJECT['name'],
+ identity_fakes.PROJECT_WITH_PARENT['name'],
+ ]
+ verifylist = [
+ ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']),
+ ('parent', identity_fakes.PROJECT['name']),
+ ('enable', False),
+ ('disable', False),
+ ('name', identity_fakes.PROJECT_WITH_PARENT['name']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'name': identity_fakes.PROJECT_WITH_PARENT['name'],
+ 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'],
+ 'parent': identity_fakes.PROJECT['id'],
+ 'description': None,
+ 'enabled': True,
+ }
+
+ self.projects_mock.create.assert_called_with(
+ **kwargs
+ )
+
+ collist = (
+ 'description',
+ 'domain_id',
+ 'enabled',
+ 'id',
+ 'name',
+ 'parent_id',
+ )
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.PROJECT_WITH_PARENT['description'],
+ identity_fakes.PROJECT_WITH_PARENT['domain_id'],
+ identity_fakes.PROJECT_WITH_PARENT['enabled'],
+ identity_fakes.PROJECT_WITH_PARENT['id'],
+ identity_fakes.PROJECT_WITH_PARENT['name'],
+ identity_fakes.PROJECT['id'],
+ )
+ self.assertEqual(data, datalist)
+
+ def test_project_create_invalid_parent(self):
+ self.projects_mock.resource_class.__name__ = 'Project'
+ self.projects_mock.get.side_effect = exceptions.NotFound(
+ 'Invalid parent')
+ self.projects_mock.find.side_effect = exceptions.NotFound(
+ 'Invalid parent')
+
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ '--parent', 'invalid',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ('parent', 'invalid'),
+ ('enable', False),
+ ('disable', False),
+ ('name', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+
class TestProjectDelete(TestProject):
diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py
index 2776e744..ef7ca9ea 100644
--- a/openstackclient/tests/image/v1/test_image.py
+++ b/openstackclient/tests/image/v1/test_image.py
@@ -306,8 +306,8 @@ class TestImageList(TestImage):
super(TestImageList, self).setUp()
self.api_mock = mock.Mock()
- self.api_mock.image_list.return_value = [
- copy.deepcopy(image_fakes.IMAGE),
+ self.api_mock.image_list.side_effect = [
+ [copy.deepcopy(image_fakes.IMAGE)], [],
]
self.app.client_manager.image.api = self.api_mock
@@ -327,6 +327,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=False,
+ marker=image_fakes.image_id,
)
collist = ('ID', 'Name')
@@ -354,6 +355,7 @@ class TestImageList(TestImage):
self.api_mock.image_list.assert_called_with(
detailed=False,
public=True,
+ marker=image_fakes.image_id,
)
collist = ('ID', 'Name')
@@ -381,6 +383,7 @@ class TestImageList(TestImage):
self.api_mock.image_list.assert_called_with(
detailed=False,
private=True,
+ marker=image_fakes.image_id,
)
collist = ('ID', 'Name')
@@ -405,6 +408,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=True,
+ marker=image_fakes.image_id,
)
collist = (
@@ -437,8 +441,8 @@ class TestImageList(TestImage):
@mock.patch('openstackclient.api.utils.simple_filter')
def test_image_list_property_option(self, sf_mock):
- sf_mock.return_value = [
- copy.deepcopy(image_fakes.IMAGE),
+ sf_mock.side_effect = [
+ [copy.deepcopy(image_fakes.IMAGE)], [],
]
arglist = [
@@ -453,6 +457,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
detailed=True,
+ marker=image_fakes.image_id,
)
sf_mock.assert_called_with(
[image_fakes.IMAGE],
@@ -472,8 +477,8 @@ class TestImageList(TestImage):
@mock.patch('openstackclient.common.utils.sort_items')
def test_image_list_sort_option(self, si_mock):
- si_mock.return_value = [
- copy.deepcopy(image_fakes.IMAGE)
+ si_mock.side_effect = [
+ [copy.deepcopy(image_fakes.IMAGE)], [],
]
arglist = ['--sort', 'name:asc']
@@ -483,7 +488,8 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
- detailed=False
+ detailed=False,
+ marker=image_fakes.image_id,
)
si_mock.assert_called_with(
[image_fakes.IMAGE],
@@ -653,3 +659,36 @@ class TestImageSet(TestImage):
image_fakes.image_id,
**kwargs
)
+
+
+class TestImageShow(TestImage):
+
+ def setUp(self):
+ super(TestImageShow, self).setUp()
+
+ self.images_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(image_fakes.IMAGE),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = image.ShowImage(self.app, None)
+
+ def test_image_show(self):
+ arglist = [
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('image', image_fakes.image_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+ self.images_mock.get.assert_called_with(
+ image_fakes.image_id,
+ )
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py
index 1b7edf08..678291bb 100644
--- a/openstackclient/tests/image/v2/fakes.py
+++ b/openstackclient/tests/image/v2/fakes.py
@@ -19,18 +19,105 @@ from openstackclient.tests import fakes
from openstackclient.tests import utils
-image_id = 'im1'
+image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c'
image_name = 'graven'
image_owner = 'baal'
-image_public = False
image_protected = False
+image_visibility = 'public'
IMAGE = {
'id': image_id,
'name': image_name,
- 'is_public': image_public,
'owner': image_owner,
'protected': image_protected,
+ 'visibility': image_visibility,
+}
+
+IMAGE_columns = tuple(sorted(IMAGE))
+IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE)))
+
+# Just enough v2 schema to do some testing
+IMAGE_schema = {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "name": "image",
+ "links": [
+ {
+ "href": "{self}",
+ "rel": "self"
+ },
+ {
+ "href": "{file}",
+ "rel": "enclosure"
+ },
+ {
+ "href": "{schema}",
+ "rel": "describedby"
+ }
+ ],
+ "properties": {
+ "id": {
+ "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", # noqa
+ "type": "string",
+ "description": "An identifier for the image"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "description": "Descriptive name for the image",
+ "maxLength": 255
+ },
+ "owner": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "description": "Owner of the image",
+ "maxLength": 255
+ },
+ "protected": {
+ "type": "boolean",
+ "description": "If true, image will not be deletable."
+ },
+ "self": {
+ "type": "string",
+ "description": "(READ-ONLY)"
+ },
+ "schema": {
+ "type": "string",
+ "description": "(READ-ONLY)"
+ },
+ "size": {
+ "type": [
+ "null",
+ "integer"
+ ],
+ "description": "Size of image file in bytes (READ-ONLY)"
+ },
+ "status": {
+ "enum": [
+ "queued",
+ "saving",
+ "active",
+ "killed",
+ "deleted",
+ "pending_delete"
+ ],
+ "type": "string",
+ "description": "Status of the image (READ-ONLY)"
+ },
+ "visibility": {
+ "enum": [
+ "public",
+ "private"
+ ],
+ "type": "string",
+ "description": "Scope of image accessibility"
+ },
+ }
}
diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py
index 6a28b1ec..73b5d39a 100644
--- a/openstackclient/tests/image/v2/test_image.py
+++ b/openstackclient/tests/image/v2/test_image.py
@@ -16,6 +16,9 @@
import copy
import mock
+import warlock
+
+from glanceclient.v2 import schemas
from openstackclient.image.v2 import image
from openstackclient.tests import fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
@@ -70,8 +73,8 @@ class TestImageList(TestImage):
super(TestImageList, self).setUp()
self.api_mock = mock.Mock()
- self.api_mock.image_list.return_value = [
- copy.deepcopy(image_fakes.IMAGE),
+ self.api_mock.image_list.side_effect = [
+ [copy.deepcopy(image_fakes.IMAGE)], [],
]
self.app.client_manager.image.api = self.api_mock
@@ -90,7 +93,9 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=image_fakes.image_id,
+ )
collist = ('ID', 'Name')
@@ -117,6 +122,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
public=True,
+ marker=image_fakes.image_id,
)
collist = ('ID', 'Name')
@@ -144,6 +150,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
private=True,
+ marker=image_fakes.image_id,
)
collist = ('ID', 'Name')
@@ -171,6 +178,7 @@ class TestImageList(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
shared=True,
+ marker=image_fakes.image_id,
)
collist = ('ID', 'Name')
@@ -193,7 +201,9 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=image_fakes.image_id,
+ )
collist = (
'ID',
@@ -216,7 +226,7 @@ class TestImageList(TestImage):
'',
'',
'',
- '',
+ 'public',
False,
image_fakes.image_owner,
'',
@@ -239,7 +249,9 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=image_fakes.image_id,
+ )
sf_mock.assert_called_with(
[image_fakes.IMAGE],
attr='a',
@@ -268,7 +280,9 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.api_mock.image_list.assert_called_with()
+ self.api_mock.image_list.assert_called_with(
+ marker=image_fakes.image_id,
+ )
si_mock.assert_called_with(
[image_fakes.IMAGE],
'name:asc'
@@ -282,3 +296,38 @@ class TestImageList(TestImage):
image_fakes.image_name
), )
self.assertEqual(datalist, tuple(data))
+
+
+class TestImageShow(TestImage):
+
+ def setUp(self):
+ super(TestImageShow, self).setUp()
+
+ # Set up the schema
+ self.model = warlock.model_factory(
+ image_fakes.IMAGE_schema,
+ schemas.SchemaBasedModel,
+ )
+
+ self.images_mock.get.return_value = self.model(**image_fakes.IMAGE)
+
+ # Get the command object to test
+ self.cmd = image.ShowImage(self.app, None)
+
+ def test_image_show(self):
+ arglist = [
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('image', image_fakes.image_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+ self.images_mock.get.assert_called_with(
+ image_fakes.image_id,
+ )
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py
index f1043072..a43be954 100644
--- a/openstackclient/tests/test_shell.py
+++ b/openstackclient/tests/test_shell.py
@@ -82,34 +82,62 @@ class TestShell(utils.TestCase):
fake_execute(_shell, _cmd)
self.app.assert_called_with(["list", "project"])
- self.assertEqual(default_args["auth_url"],
- _shell.options.os_auth_url)
- self.assertEqual(default_args["project_id"],
- _shell.options.os_project_id)
- self.assertEqual(default_args["project_name"],
- _shell.options.os_project_name)
- self.assertEqual(default_args["domain_id"],
- _shell.options.os_domain_id)
- self.assertEqual(default_args["domain_name"],
- _shell.options.os_domain_name)
- self.assertEqual(default_args["user_domain_id"],
- _shell.options.os_user_domain_id)
- self.assertEqual(default_args["user_domain_name"],
- _shell.options.os_user_domain_name)
- self.assertEqual(default_args["project_domain_id"],
- _shell.options.os_project_domain_id)
- self.assertEqual(default_args["project_domain_name"],
- _shell.options.os_project_domain_name)
- self.assertEqual(default_args["username"],
- _shell.options.os_username)
- self.assertEqual(default_args["password"],
- _shell.options.os_password)
- self.assertEqual(default_args["region_name"],
- _shell.options.os_region_name)
- self.assertEqual(default_args["trust_id"],
- _shell.options.os_trust_id)
- self.assertEqual(default_args['auth_type'],
- _shell.options.os_auth_type)
+ self.assertEqual(
+ default_args.get("auth_url", ''),
+ _shell.options.auth_url,
+ )
+ self.assertEqual(
+ default_args.get("project_id", ''),
+ _shell.options.project_id,
+ )
+ self.assertEqual(
+ default_args.get("project_name", ''),
+ _shell.options.project_name,
+ )
+ self.assertEqual(
+ default_args.get("domain_id", ''),
+ _shell.options.domain_id,
+ )
+ self.assertEqual(
+ default_args.get("domain_name", ''),
+ _shell.options.domain_name,
+ )
+ self.assertEqual(
+ default_args.get("user_domain_id", ''),
+ _shell.options.user_domain_id,
+ )
+ self.assertEqual(
+ default_args.get("user_domain_name", ''),
+ _shell.options.user_domain_name,
+ )
+ self.assertEqual(
+ default_args.get("project_domain_id", ''),
+ _shell.options.project_domain_id,
+ )
+ self.assertEqual(
+ default_args.get("project_domain_name", ''),
+ _shell.options.project_domain_name,
+ )
+ self.assertEqual(
+ default_args.get("username", ''),
+ _shell.options.username,
+ )
+ self.assertEqual(
+ default_args.get("password", ''),
+ _shell.options.password,
+ )
+ self.assertEqual(
+ default_args.get("region_name", ''),
+ _shell.options.region_name,
+ )
+ self.assertEqual(
+ default_args.get("trust_id", ''),
+ _shell.options.trust_id,
+ )
+ self.assertEqual(
+ default_args.get('auth_type', ''),
+ _shell.options.auth_type,
+ )
def _assert_token_auth(self, cmd_options, default_args):
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
@@ -118,9 +146,34 @@ class TestShell(utils.TestCase):
fake_execute(_shell, _cmd)
self.app.assert_called_with(["list", "role"])
- self.assertEqual(default_args["os_token"], _shell.options.os_token)
- self.assertEqual(default_args["os_auth_url"],
- _shell.options.os_auth_url)
+ self.assertEqual(
+ default_args.get("token", ''),
+ _shell.options.token,
+ "token"
+ )
+ self.assertEqual(
+ default_args.get("auth_url", ''),
+ _shell.options.auth_url,
+ "auth_url"
+ )
+
+ def _assert_token_endpoint_auth(self, cmd_options, default_args):
+ with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
+ self.app):
+ _shell, _cmd = make_shell(), cmd_options + " list role"
+ fake_execute(_shell, _cmd)
+
+ self.app.assert_called_with(["list", "role"])
+ self.assertEqual(
+ default_args.get("token", ''),
+ _shell.options.token,
+ "token",
+ )
+ self.assertEqual(
+ default_args.get("url", ''),
+ _shell.options.url,
+ "url",
+ )
def _assert_cli(self, cmd_options, default_args):
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
@@ -178,301 +231,233 @@ class TestShellPasswordAuth(TestShell):
flag = "--os-auth-url " + DEFAULT_AUTH_URL
kwargs = {
"auth_url": DEFAULT_AUTH_URL,
- "project_id": "",
- "project_name": "",
- "user_domain_id": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_project_id_flow(self):
flag = "--os-project-id " + DEFAULT_PROJECT_ID
kwargs = {
- "auth_url": "",
"project_id": DEFAULT_PROJECT_ID,
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_project_name_flow(self):
flag = "--os-project-name " + DEFAULT_PROJECT_NAME
kwargs = {
- "auth_url": "",
- "project_id": "",
"project_name": DEFAULT_PROJECT_NAME,
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_domain_id_flow(self):
flag = "--os-domain-id " + DEFAULT_DOMAIN_ID
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
"domain_id": DEFAULT_DOMAIN_ID,
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_domain_name_flow(self):
flag = "--os-domain-name " + DEFAULT_DOMAIN_NAME
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
"domain_name": DEFAULT_DOMAIN_NAME,
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_user_domain_id_flow(self):
flag = "--os-user-domain-id " + DEFAULT_USER_DOMAIN_ID
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
"user_domain_id": DEFAULT_USER_DOMAIN_ID,
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_user_domain_name_flow(self):
flag = "--os-user-domain-name " + DEFAULT_USER_DOMAIN_NAME
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
"user_domain_name": DEFAULT_USER_DOMAIN_NAME,
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_project_domain_id_flow(self):
flag = "--os-project-domain-id " + DEFAULT_PROJECT_DOMAIN_ID
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
"project_domain_id": DEFAULT_PROJECT_DOMAIN_ID,
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_project_domain_name_flow(self):
flag = "--os-project-domain-name " + DEFAULT_PROJECT_DOMAIN_NAME
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
"project_domain_name": DEFAULT_PROJECT_DOMAIN_NAME,
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_username_flow(self):
flag = "--os-username " + DEFAULT_USERNAME
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
"username": DEFAULT_USERNAME,
- "password": "",
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_password_flow(self):
flag = "--os-password " + DEFAULT_PASSWORD
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
"password": DEFAULT_PASSWORD,
- "region_name": "",
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_region_name_flow(self):
flag = "--os-region-name " + DEFAULT_REGION_NAME
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
"region_name": DEFAULT_REGION_NAME,
- "trust_id": "",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_trust_id_flow(self):
flag = "--os-trust-id " + "1234"
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
"trust_id": "1234",
- "auth_type": "",
}
self._assert_password_auth(flag, kwargs)
def test_only_auth_type_flow(self):
flag = "--os-auth-type " + "v2password"
kwargs = {
- "auth_url": "",
- "project_id": "",
- "project_name": "",
- "domain_id": "",
- "domain_name": "",
- "user_domain_id": "",
- "user_domain_name": "",
- "project_domain_id": "",
- "project_domain_name": "",
- "username": "",
- "password": "",
- "region_name": "",
- "trust_id": "",
"auth_type": DEFAULT_AUTH_PLUGIN
}
self._assert_password_auth(flag, kwargs)
class TestShellTokenAuth(TestShell):
+ def test_only_token(self):
+ flag = "--os-token " + DEFAULT_TOKEN
+ kwargs = {
+ "token": DEFAULT_TOKEN,
+ "auth_url": '',
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_only_auth_url(self):
+ flag = "--os-auth-url " + DEFAULT_AUTH_URL
+ kwargs = {
+ "token": '',
+ "auth_url": DEFAULT_AUTH_URL,
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_empty_auth(self):
+ os.environ = {}
+ flag = ""
+ kwargs = {}
+ self._assert_token_auth(flag, kwargs)
+
+
+class TestShellTokenAuthEnv(TestShell):
def setUp(self):
- super(TestShellTokenAuth, self).setUp()
+ super(TestShellTokenAuthEnv, self).setUp()
env = {
"OS_TOKEN": DEFAULT_TOKEN,
- "OS_AUTH_URL": DEFAULT_SERVICE_URL,
+ "OS_AUTH_URL": DEFAULT_AUTH_URL,
}
self.orig_env, os.environ = os.environ, env.copy()
def tearDown(self):
- super(TestShellTokenAuth, self).tearDown()
+ super(TestShellTokenAuthEnv, self).tearDown()
os.environ = self.orig_env
- def test_default_auth(self):
+ def test_env(self):
flag = ""
kwargs = {
- "os_token": DEFAULT_TOKEN,
- "os_auth_url": DEFAULT_SERVICE_URL
+ "token": DEFAULT_TOKEN,
+ "auth_url": DEFAULT_AUTH_URL,
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_only_token(self):
+ flag = "--os-token xyzpdq"
+ kwargs = {
+ "token": "xyzpdq",
+ "auth_url": DEFAULT_AUTH_URL,
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_only_auth_url(self):
+ flag = "--os-auth-url http://cloud.local:555"
+ kwargs = {
+ "token": DEFAULT_TOKEN,
+ "auth_url": "http://cloud.local:555",
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_empty_auth(self):
+ os.environ = {}
+ flag = ""
+ kwargs = {
+ "token": '',
+ "auth_url": '',
+ }
+ self._assert_token_auth(flag, kwargs)
+
+
+class TestShellTokenEndpointAuth(TestShell):
+ def test_only_token(self):
+ flag = "--os-token " + DEFAULT_TOKEN
+ kwargs = {
+ "token": DEFAULT_TOKEN,
+ "url": '',
+ }
+ self._assert_token_endpoint_auth(flag, kwargs)
+
+ def test_only_url(self):
+ flag = "--os-url " + DEFAULT_SERVICE_URL
+ kwargs = {
+ "token": '',
+ "url": DEFAULT_SERVICE_URL,
+ }
+ self._assert_token_endpoint_auth(flag, kwargs)
+
+ def test_empty_auth(self):
+ os.environ = {}
+ flag = ""
+ kwargs = {
+ "token": '',
+ "auth_url": '',
+ }
+ self._assert_token_endpoint_auth(flag, kwargs)
+
+
+class TestShellTokenEndpointAuthEnv(TestShell):
+ def setUp(self):
+ super(TestShellTokenEndpointAuthEnv, self).setUp()
+ env = {
+ "OS_TOKEN": DEFAULT_TOKEN,
+ "OS_URL": DEFAULT_SERVICE_URL,
+ }
+ self.orig_env, os.environ = os.environ, env.copy()
+
+ def tearDown(self):
+ super(TestShellTokenEndpointAuthEnv, self).tearDown()
+ os.environ = self.orig_env
+
+ def test_env(self):
+ flag = ""
+ kwargs = {
+ "token": DEFAULT_TOKEN,
+ "url": DEFAULT_SERVICE_URL,
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_only_token(self):
+ flag = "--os-token xyzpdq"
+ kwargs = {
+ "token": "xyzpdq",
+ "url": DEFAULT_SERVICE_URL,
+ }
+ self._assert_token_auth(flag, kwargs)
+
+ def test_only_url(self):
+ flag = "--os-url http://cloud.local:555"
+ kwargs = {
+ "token": DEFAULT_TOKEN,
+ "url": "http://cloud.local:555",
}
self._assert_token_auth(flag, kwargs)
@@ -480,8 +465,8 @@ class TestShellTokenAuth(TestShell):
os.environ = {}
flag = ""
kwargs = {
- "os_token": "",
- "os_auth_url": ""
+ "token": '',
+ "url": '',
}
self._assert_token_auth(flag, kwargs)
diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py
index 56081966..00cc46a6 100644
--- a/openstackclient/tests/volume/test_find_resource.py
+++ b/openstackclient/tests/volume/test_find_resource.py
@@ -24,6 +24,13 @@ from openstackclient.tests import utils as test_utils
from openstackclient.volume import client # noqa
+# Monkey patch for v1 cinderclient
+# NOTE(dtroyer): Do here because openstackclient.volume.client
+# doesn't do it until the client object is created now.
+volumes.Volume.NAME_ATTR = 'display_name'
+volume_snapshots.Snapshot.NAME_ATTR = 'display_name'
+
+
ID = '1after909'
NAME = 'PhilSpector'
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index 21072aeb..a7b64def 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -15,17 +15,8 @@
import logging
-from cinderclient import extension
-from cinderclient.v1.contrib import list_extensions
-from cinderclient.v1 import volume_snapshots
-from cinderclient.v1 import volumes
-
from openstackclient.common import utils
-# Monkey patch for v1 cinderclient
-volumes.Volume.NAME_ATTR = 'display_name'
-volume_snapshots.Snapshot.NAME_ATTR = 'display_name'
-
LOG = logging.getLogger(__name__)
DEFAULT_VOLUME_API_VERSION = '1'
@@ -38,6 +29,17 @@ API_VERSIONS = {
def make_client(instance):
"""Returns a volume service client."""
+
+ # Defer client imports until we actually need them
+ from cinderclient import extension
+ from cinderclient.v1.contrib import list_extensions
+ from cinderclient.v1 import volume_snapshots
+ from cinderclient.v1 import volumes
+
+ # Monkey patch for v1 cinderclient
+ volumes.Volume.NAME_ATTR = 'display_name'
+ volume_snapshots.Snapshot.NAME_ATTR = 'display_name'
+
volume_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py
index 71c8ed38..03c63a05 100644
--- a/openstackclient/volume/v1/backup.py
+++ b/openstackclient/volume/v1/backup.py
@@ -64,7 +64,7 @@ class CreateBackup(show.ShowOne):
parsed_args.volume).id
backup = volume_client.backups.create(
volume_id,
- parsed_args.volume,
+ parsed_args.container,
parsed_args.name,
parsed_args.description
)