Add resource type list CLI
New cli is based on openstackclient, this patch adds cli support for plugin list: openstack search resource-type list Partial blueprint: openstack-client-search-initial-plugin Change-Id: I3ce8b9de7f13ed4974d9e0b1060c8e53facf4ae5
This commit is contained in:
parent
d0b4ee94be
commit
04a4a844a4
56
README.rst
56
README.rst
|
@ -25,6 +25,62 @@ OpenStack.
|
|||
Command-line API
|
||||
----------------
|
||||
|
||||
To execute CLI commands to standalone searchlight set with keystone.
|
||||
|
||||
* Clone repository for python-searchlightclient::
|
||||
|
||||
$ git clone https://github.com/openstack/python-searchlightclient.git
|
||||
$ cd python-searchlightclient
|
||||
|
||||
* Setup a virtualenv
|
||||
|
||||
.. note::
|
||||
This is an optional step, but will allow Searchlightclient's dependencies
|
||||
to be installed in a contained environment that can be easily deleted
|
||||
if you choose to start over or uninstall Searchlightclient.
|
||||
|
||||
::
|
||||
|
||||
$ tox -evenv --notest
|
||||
|
||||
Activate the virtual environment whenever you want to work in it.
|
||||
All further commands in this section should be run with the venv active:
|
||||
|
||||
::
|
||||
|
||||
$ source .tox/venv/bin/activate
|
||||
|
||||
.. note::
|
||||
When ALL steps are complete, deactivate the virtualenv: $ deactivate
|
||||
|
||||
* Install Searchlightclient and its dependencies::
|
||||
|
||||
(venv) $ python setup.py develop
|
||||
|
||||
* To execute CLI commands::
|
||||
|
||||
$ export OS_USERNAME=<user>
|
||||
$ export OS_PASSWORD=<password>
|
||||
$ export OS_TENANT_NAME=<project>
|
||||
$ export OS_AUTH_URL='http://localhost:5000/v2.0/'
|
||||
|
||||
.. note::
|
||||
With devstack you just need to $ source openrc <user> <project>
|
||||
|
||||
::
|
||||
|
||||
$ openstack
|
||||
(openstack) search resource-type list
|
||||
+--------------------------+--------------------------+
|
||||
| Name | Type |
|
||||
+--------------------------+--------------------------+
|
||||
| OS::Designate::RecordSet | OS::Designate::RecordSet |
|
||||
| OS::Designate::Zone | OS::Designate::Zone |
|
||||
| OS::Glance::Image | OS::Glance::Image |
|
||||
| OS::Glance::Metadef | OS::Glance::Metadef |
|
||||
| OS::Nova::Server | OS::Nova::Server |
|
||||
+--------------------------+--------------------------+
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
Babel>=1.3 # BSD
|
||||
pbr>=1.6 # Apache-2.0
|
||||
cliff>=1.15.0 # Apache-2.0
|
||||
argparse # PSF
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.4.0 # Apache-2.0
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
||||
python-openstackclient>=2.0.0 # Apache-2.0
|
||||
PyYAML>=3.1.0 # MIT
|
||||
requests!=2.9.0,>=2.8.1 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# 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 logging
|
||||
|
||||
from openstackclient.common import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SEARCH_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_search_api_version'
|
||||
API_NAME = 'search'
|
||||
API_VERSIONS = {
|
||||
'1': 'searchlightclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns a search service client"""
|
||||
search_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
|
||||
client = search_client(
|
||||
endpoint=instance.get_endpoint_for_service_type('search'),
|
||||
session=instance.session,
|
||||
auth_url=instance._auth_url,
|
||||
username=instance._username,
|
||||
password=instance._password,
|
||||
region_name=instance._region_name,
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options"""
|
||||
parser.add_argument(
|
||||
'--os-search-api-version',
|
||||
metavar='<search-api-version>',
|
||||
default=utils.env(
|
||||
'OS_SEARCH_API_VERSION',
|
||||
default=DEFAULT_SEARCH_API_VERSION),
|
||||
help='Search API version, default=' +
|
||||
DEFAULT_SEARCH_API_VERSION +
|
||||
' (Env: OS_SEARCH_API_VERSION)')
|
||||
return parser
|
|
@ -0,0 +1,40 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
"""Searchlight v1 Resource Type action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from cliff import lister
|
||||
from openstackclient.common import utils
|
||||
|
||||
|
||||
class ListResourceType(lister.Lister):
|
||||
"""List Searchlight Resource Type (Plugin)."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListResourceType")
|
||||
|
||||
@utils.log_method(log)
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
search_client = self.app.client_manager.search
|
||||
columns = (
|
||||
"Name",
|
||||
"Type"
|
||||
)
|
||||
data = search_client.resource_types.list()
|
||||
return (columns,
|
||||
(utils.get_item_properties(
|
||||
s, columns,
|
||||
) for s in data))
|
|
@ -0,0 +1,68 @@
|
|||
# Copyright 2013 Nebula Inc.
|
||||
#
|
||||
# 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 six
|
||||
import sys
|
||||
|
||||
|
||||
AUTH_TOKEN = "foobar"
|
||||
AUTH_URL = "http://0.0.0.0"
|
||||
|
||||
|
||||
class FakeStdout:
|
||||
def __init__(self):
|
||||
self.content = []
|
||||
|
||||
def write(self, text):
|
||||
self.content.append(text)
|
||||
|
||||
def make_string(self):
|
||||
result = ''
|
||||
for line in self.content:
|
||||
result = result + line
|
||||
return result
|
||||
|
||||
|
||||
class FakeApp(object):
|
||||
def __init__(self, _stdout):
|
||||
self.stdout = _stdout
|
||||
self.client_manager = None
|
||||
self.stdin = sys.stdin
|
||||
self.stdout = _stdout or sys.stdout
|
||||
self.stderr = sys.stderr
|
||||
|
||||
|
||||
class FakeClientManager(object):
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
self.auth_ref = None
|
||||
|
||||
|
||||
class FakeResource(object):
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in six.iteritems(info):
|
||||
setattr(self, k, v)
|
||||
|
||||
def __repr__(self):
|
||||
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
|
||||
k != 'manager')
|
||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||
return "<%s %s>" % (self.__class__.__name__, info)
|
|
@ -0,0 +1,93 @@
|
|||
# Copyright 2012-2013 OpenStack Foundation
|
||||
# Copyright 2013 Nebula Inc.
|
||||
#
|
||||
# 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 os
|
||||
|
||||
import fixtures
|
||||
import sys
|
||||
import testtools
|
||||
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
def setUp(self):
|
||||
testtools.TestCase.setUp(self)
|
||||
|
||||
if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or
|
||||
os.environ.get("OS_STDOUT_CAPTURE") == "1"):
|
||||
stdout = self.useFixture(fixtures.StringStream("stdout")).stream
|
||||
self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout))
|
||||
|
||||
if (os.environ.get("OS_STDERR_CAPTURE") == "True" or
|
||||
os.environ.get("OS_STDERR_CAPTURE") == "1"):
|
||||
stderr = self.useFixture(fixtures.StringStream("stderr")).stream
|
||||
self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr))
|
||||
|
||||
def assertNotCalled(self, m, msg=None):
|
||||
"""Assert a function was not called"""
|
||||
|
||||
if m.called:
|
||||
if not msg:
|
||||
msg = 'method %s should not have been called' % m
|
||||
self.fail(msg)
|
||||
|
||||
# 2.6 doesn't have the assert dict equals so make sure that it exists
|
||||
if tuple(sys.version_info)[0:2] < (2, 7):
|
||||
|
||||
def assertIsInstance(self, obj, cls, msg=None):
|
||||
"""self.assertTrue(isinstance(obj, cls)), with a nicer message"""
|
||||
|
||||
if not isinstance(obj, cls):
|
||||
standardMsg = '%s is not an instance of %r' % (obj, cls)
|
||||
self.fail(self._formatMessage(msg, standardMsg))
|
||||
|
||||
def assertDictEqual(self, d1, d2, msg=None):
|
||||
# Simple version taken from 2.7
|
||||
self.assertIsInstance(d1, dict,
|
||||
'First argument is not a dictionary')
|
||||
self.assertIsInstance(d2, dict,
|
||||
'Second argument is not a dictionary')
|
||||
if d1 != d2:
|
||||
if msg:
|
||||
self.fail(msg)
|
||||
else:
|
||||
standardMsg = '%r != %r' % (d1, d2)
|
||||
self.fail(standardMsg)
|
||||
|
||||
|
||||
class TestCommand(TestCase):
|
||||
"""Test cliff command classes"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCommand, self).setUp()
|
||||
# Build up a fake app
|
||||
self.fake_stdout = fakes.FakeStdout()
|
||||
self.app = fakes.FakeApp(self.fake_stdout)
|
||||
self.app.client_manager = fakes.FakeClientManager()
|
||||
|
||||
def check_parser(self, cmd, args, verify_args):
|
||||
cmd_parser = cmd.get_parser('check_parser')
|
||||
try:
|
||||
parsed_args = cmd_parser.parse_args(args)
|
||||
except SystemExit:
|
||||
raise Exception("Argument parse failed")
|
||||
for av in verify_args:
|
||||
attr, value = av
|
||||
if attr:
|
||||
self.assertIn(attr, parsed_args)
|
||||
self.assertEqual(getattr(parsed_args, attr), value)
|
||||
return parsed_args
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
from searchlightclient.tests.unit.osc import utils
|
||||
|
||||
|
||||
ResourceType = {
|
||||
"index": "searchlight",
|
||||
"type": "OS::Nova::Server",
|
||||
"name": "OS::Nova::Server"
|
||||
}
|
||||
|
||||
|
||||
class FakeSearchv1Client(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.http_client = mock.Mock()
|
||||
self.http_client.auth_token = kwargs['token']
|
||||
self.http_client.management_url = kwargs['endpoint']
|
||||
self.resource_types = mock.Mock()
|
||||
self.resource_types.list = mock.Mock(return_value=[])
|
||||
|
||||
|
||||
class TestSearchv1(utils.TestCommand):
|
||||
def setUp(self):
|
||||
super(TestSearchv1, self).setUp()
|
||||
|
||||
self.app.client_manager.search = FakeSearchv1Client(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
# 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
|
||||
|
||||
from searchlightclient.osc.v1 import resource_type
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
from searchlightclient.tests.unit.osc.v1 import fakes as searchlight_fakes
|
||||
|
||||
|
||||
class TestResourceType(searchlight_fakes.TestSearchv1):
|
||||
def setUp(self):
|
||||
super(TestResourceType, self).setUp()
|
||||
self.rtype_client = self.app.client_manager.search.resource_types
|
||||
|
||||
|
||||
class TestResourceTypeList(TestResourceType):
|
||||
|
||||
def setUp(self):
|
||||
super(TestResourceTypeList, self).setUp()
|
||||
self.cmd = resource_type.ListResourceType(self.app, None)
|
||||
self.rtype_client.list.return_value = [
|
||||
fakes.FakeResource(
|
||||
None,
|
||||
copy.deepcopy(searchlight_fakes.ResourceType),
|
||||
loaded=True,
|
||||
),
|
||||
]
|
||||
|
||||
def test_list(self):
|
||||
parsed_args = self.check_parser(self.cmd, [], [])
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.rtype_client.list.assert_called_with()
|
||||
|
||||
collist = ('Name', 'Type')
|
||||
self.assertEqual(collist, columns)
|
||||
|
||||
datalist = (('OS::Nova::Server', 'OS::Nova::Server'),)
|
||||
self.assertEqual(datalist, tuple(data))
|
|
@ -22,6 +22,13 @@ classifier =
|
|||
packages =
|
||||
searchlightclient
|
||||
|
||||
[entry_points]
|
||||
openstack.cli.extension =
|
||||
search = searchlightclient.osc.plugin
|
||||
|
||||
openstack.search.v1 =
|
||||
search_resource-type_list = searchlightclient.osc.v1.resource_type:ListResourceType
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
||||
|
|
Loading…
Reference in New Issue