diff --git a/congressclient/common/__init__.py b/congressclient/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/congressclient/common/utils.py b/congressclient/common/utils.py new file mode 100644 index 0000000..85a6efc --- /dev/null +++ b/congressclient/common/utils.py @@ -0,0 +1,94 @@ +# Copyright 2012-2013 OpenStack, LLC. +# +# 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 sys + +from congressclient import exceptions + + +def env(*vars, **kwargs): + """Search for the first defined of possibly many env vars + + Returns the first environment variable defined in vars, or + returns the default defined in kwargs. + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return value + return kwargs.get('default', '') + + +def get_client_class(api_name, version, version_map): + """Returns the client class for the requested API version + + :param api_name: the name of the API, e.g. 'compute', 'image', etc + :param version: the requested API version + :param version_map: a dict of client classes keyed by version + :rtype: a client class for the requested API version + """ + try: + client_path = version_map[str(version)] + except (KeyError, ValueError): + msg = "Invalid %s client version '%s'. must be one of: %s" % ( + (api_name, version, ', '.join(version_map.keys()))) + raise exceptions.UnsupportedVersion(msg) + + return import_class(client_path) + + +def import_class(import_str): + """Returns a class from a string including module and class + + :param import_str: a string representation of the class name + :rtype: the requested class + """ + mod_str, _sep, class_str = import_str.rpartition('.') + __import__(mod_str) + return getattr(sys.modules[mod_str], class_str) + + +def format_list(data): + """Return a formatted strings + + :param data: a list of strings + :rtype: a string formatted to a,b,c + """ + + return ', '.join(data) + + +def get_dict_properties(item, fields, mixed_case_fields=[], formatters={}): + """Return a tuple containing the item properties. + + :param item: a single dict resource + :param fields: tuple of strings with the desired field names + :param mixed_case_fields: tuple of field names to preserve case + :param formatters: dictionary mapping field names to callables + to format the values + """ + row = [] + + for field in fields: + if field in mixed_case_fields: + field_name = field.replace(' ', '_') + else: + field_name = field.lower().replace(' ', '_') + data = item[field_name] if field_name in item else '' + if field in formatters: + row.append(formatters[field](data)) + else: + row.append(data) + return tuple(row) diff --git a/congressclient/exceptions.py b/congressclient/exceptions.py new file mode 100644 index 0000000..a2143a6 --- /dev/null +++ b/congressclient/exceptions.py @@ -0,0 +1,16 @@ +# Copyright (c) 2012 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +from congressclient.openstack.common.apiclient.exceptions import * # noqa diff --git a/congressclient/osc/__init__.py b/congressclient/osc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/congressclient/osc/osc_plugin.py b/congressclient/osc/osc_plugin.py new file mode 100644 index 0000000..c01b773 --- /dev/null +++ b/congressclient/osc/osc_plugin.py @@ -0,0 +1,61 @@ +# Copyright 2014 VMWare. +# +# 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 + +import keystoneclient + +from congressclient.common import utils + +LOG = logging.getLogger(__name__) + +DEFAULT_COMPUTE_API_VERSION = '1' +API_VERSION_OPTION = 'os_policy_api_version' +API_NAME = 'congressclient' +API_VERSIONS = { + '1': 'congressclient.v1.client.Client', +} + + +def make_client(instance): + """Returns a congress service client.""" + congress_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + auth = keystoneclient.auth.identity.v2.Password( + auth_url=instance._auth_url, + username=instance._username, + password=instance._password, tenant_name=instance._project_name) + session = keystoneclient.session.Session(auth=auth) + LOG.debug('instantiating congress client: %s', congress_client) + return congress_client(session=session, + auth=None, + interface='publicURL', + service_type='policy', + region_name=instance._region_name) + + +def build_option_parser(parser): + """Hook to add global options.""" + parser.add_argument( + '--os-policy-api-version', + metavar='', + default=utils.env( + 'OS_POLICY_API_VERSION', + default=DEFAULT_COMPUTE_API_VERSION), + help='Policy API version, default=' + + DEFAULT_COMPUTE_API_VERSION + + ' (Env: OS_POLICY_API_VERSION)') + return parser diff --git a/congressclient/osc/v1/__init__.py b/congressclient/osc/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/congressclient/osc/v1/policy.py b/congressclient/osc/v1/policy.py new file mode 100644 index 0000000..01faef2 --- /dev/null +++ b/congressclient/osc/v1/policy.py @@ -0,0 +1,162 @@ +# Copyright 2012-2013 OpenStack, LLC. +# +# 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. + +"""Policy action implemenations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show +import six + +from congressclient.common import utils +from congressclient.openstack.common import jsonutils + + +def _format_rule(rule): + """Break up rule string so it fits on screen.""" + + rule_split = jsonutils.dumps(rule).split(":-") + formatted_string = rule_split[0] + ":-\n" + for rule in rule_split[1].split("), "): + formatted_string += rule + '\n' + return formatted_string + + +class CreatePolicyRule(show.ShowOne): + """Create a policy rule.""" + + log = logging.getLogger(__name__ + '.CreatePolicyRule') + + def get_parser(self, prog_name): + parser = super(CreatePolicyRule, self).get_parser(prog_name) + parser.add_argument( + 'policy_name', + metavar="", + help="Name or identifier of the policy") + parser.add_argument( + 'rule', + metavar="", + help="Policy rule") + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.congressclient + body = {'rule': parsed_args.rule} + data = client.create_policy_rule(parsed_args.policy_name, body) + data['rule'] = _format_rule(data['rule']) + return zip(*sorted(six.iteritems(data))) + + +class DeletePolicyRule(command.Command): + """Delete a policy rule.""" + + log = logging.getLogger(__name__ + '.DeletePolicyRule') + + def get_parser(self, prog_name): + parser = super(DeletePolicyRule, self).get_parser(prog_name) + parser.add_argument( + 'policy_name', + metavar="", + help="Name of the policy to delete") + parser.add_argument( + 'rule_id', + metavar="", + help="ID of the policy to delete") + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.congressclient + client.delete_policy_rule(parsed_args.policy_name, + parsed_args.rule_id) + + +class ListPolicyRules(lister.Lister): + """List policy rules.""" + + log = logging.getLogger(__name__ + '.ListPolicyRules') + + def get_parser(self, prog_name): + parser = super(ListPolicyRules, self).get_parser(prog_name) + parser.add_argument( + 'policy_name', + metavar="", + help="Name of the policy") + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.congressclient + data = client.list_policy_rules(parsed_args.policy_name)['results'] + columns = ['id', 'comment', 'rule'] + formatters = {'PolicyRules': utils.format_list} + return (columns, + (utils.get_dict_properties(s, columns, + formatters=formatters) + for s in data)) + + +class ListPolicy(lister.Lister): + """List Policy.""" + + log = logging.getLogger(__name__ + '.ListPolicy') + + def get_parser(self, prog_name): + parser = super(ListPolicy, self).get_parser(prog_name) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.congressclient + data = client.list_policy()['results'] + columns = ['id', 'owner_id'] + formatters = {'Policies': utils.format_list} + return (columns, + (utils.get_dict_properties(s, columns, + formatters=formatters) + for s in data)) + + +class GetPolicyRow(lister.Lister): + """Get policy row.""" + + log = logging.getLogger(__name__ + '.GetPolicyRow') + + def get_parser(self, prog_name): + parser = super(GetPolicyRow, self).get_parser(prog_name) + parser.add_argument( + 'policy_name', + metavar="", + help="Name of the policy to show") + parser.add_argument( + 'table', + metavar="", + help="Table to get the policy row from") + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.congressclient + data = client.get_policy_rows(parsed_args.policy_name, + parsed_args.table)['results'] + + columns = ['data'] + formatters = {'Policies': utils.format_list} + return (columns, + (utils.get_dict_properties(s, columns, + formatters=formatters) + for s in data)) diff --git a/congressclient/tests/__init__.py b/congressclient/tests/__init__.py index 19f5e72..e69de29 100644 --- a/congressclient/tests/__init__.py +++ b/congressclient/tests/__init__.py @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# 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. diff --git a/congressclient/tests/common.py b/congressclient/tests/common.py new file mode 100644 index 0000000..d8ecaff --- /dev/null +++ b/congressclient/tests/common.py @@ -0,0 +1,53 @@ +# 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 argparse + +import mock + +from congressclient.tests import utils + + +class TestCongressBase(utils.TestCommand): + def setUp(self): + super(TestCongressBase, self).setUp() + self.app = mock.Mock(name='app') + self.app.client_manager = mock.Mock(name='client_manager') + self.namespace = argparse.Namespace() + + given_show_options = [ + '-f', + 'shell', + '-c', + 'id', + '--prefix', + 'TST', + ] + then_show_options = [ + ('formatter', 'shell'), + ('columns', ['id']), + ('prefix', 'TST'), + ] + given_list_options = [ + '-f', + 'csv', + '-c', + 'id', + '--quote', + 'all', + ] + then_list_options = [ + ('formatter', 'csv'), + ('columns', ['id']), + ('quote_mode', 'all'), + ] diff --git a/congressclient/tests/fakes.py b/congressclient/tests/fakes.py new file mode 100644 index 0000000..00addc9 --- /dev/null +++ b/congressclient/tests/fakes.py @@ -0,0 +1,81 @@ +# 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 sys + +import six + + +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 + self.restapi = None + + +class FakeClientManager(object): + def __init__(self): + self.compute = None + self.identity = None + self.image = None + self.object = None + self.volume = None + self.network = None + self.auth_ref = None + + +class FakeModule(object): + def __init__(self, name, version): + self.name = name + self.__version__ = version + + +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) diff --git a/congressclient/tests/test_congressclient.py b/congressclient/tests/test_congressclient.py deleted file mode 100644 index ddc8dd2..0000000 --- a/congressclient/tests/test_congressclient.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# 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. - -""" -test_congressclient ----------------------------------- - -Tests for `congressclient` module. -""" - -from congressclient.tests import base - - -class TestCongressclient(base.TestCase): - - def test_something(self): - pass diff --git a/congressclient/tests/utils.py b/congressclient/tests/utils.py new file mode 100644 index 0000000..31d1e6c --- /dev/null +++ b/congressclient/tests/utils.py @@ -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 sys + +import fixtures +import testtools + +from congressclient.tests 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 diff --git a/congressclient/tests/v1/__init__.py b/congressclient/tests/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/congressclient/tests/v1/test_policy.py b/congressclient/tests/v1/test_policy.py new file mode 100644 index 0000000..abdf8c4 --- /dev/null +++ b/congressclient/tests/v1/test_policy.py @@ -0,0 +1,147 @@ +# 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 congressclient.osc.v1 import policy +from congressclient.tests import common + + +class TestCreatePolicyRule(common.TestCongressBase): + + def test_create_policy_rule(self): + policy_name = 'classification' + rule = ("port_security_group(port, security_group_name) :-" + "neutron:ports(addr_pairs, security_groups, extra_dhcp_opts," + "binding_cap, status, name, admin_state_up, network_id, " + "tenant_id, binding_vif, device_owner, mac_address, " + "fixed_ips, port, device_id, binding_host_id1), " + "neutron:ports.security_groups(security_groups, " + "security_group_id), neutron:security_groups(tenant_id2, " + "security_group_name, desc2, security_group_id)") + + response = {"comment": "None", + "id": "e531f2b3-3d97-42c0-b3b5-b7b6ab532018", + "rule": rule} + + arglist = [policy_name, rule] + verifylist = [ + ('policy_name', policy_name), + ('rule', rule), + ] + + mocker = mock.Mock(return_value=response) + self.app.client_manager.congressclient.create_policy_rule = mocker + cmd = policy.CreatePolicyRule(self.app, self.namespace) + parsed_args = self.check_parser(cmd, arglist, verifylist) + result = list(cmd.take_action(parsed_args)) + filtered = [('comment', 'id', 'rule'), + ('None', 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018', + policy._format_rule(rule))] + self.assertEqual(filtered, result) + + +class TestDeletePolicyRule(common.TestCongressBase): + def test_delete_policy_rule(self): + policy_name = 'classification' + rule_id = 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018' + arglist = [ + policy_name, rule_id + ] + verifylist = [ + ('policy_name', policy_name), + ('rule_id', rule_id) + ] + mocker = mock.Mock(return_value=None) + self.app.client_manager.congressclient.delete_policy_rule = mocker + cmd = policy.DeletePolicyRule(self.app, self.namespace) + + parsed_args = self.check_parser(cmd, arglist, verifylist) + result = cmd.take_action(parsed_args) + + mocker.assert_called_with(policy_name, rule_id) + self.assertEqual(None, result) + + +class TestListPolicyRules(common.TestCongressBase): + def test_list_policy_rules(self): + policy_name = 'classification' + rule_id = 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018' + arglist = [ + policy_name + ] + verifylist = [ + ('policy_name', policy_name) + ] + response = { + "results": [{"comment": "None", + "id": rule_id, + "rule": "security_group(port, security_group_name)" + }] + } + lister = mock.Mock(return_value=response) + self.app.client_manager.congressclient.list_policy_rules = lister + cmd = policy.ListPolicyRules(self.app, self.namespace) + + parsed_args = self.check_parser(cmd, arglist, verifylist) + result = cmd.take_action(parsed_args) + + lister.assert_called_with(policy_name) + self.assertEqual(['id', 'comment', 'rule'], result[0]) + + +class ListPolicy(common.TestCongressBase): + def test_list_policy_rules(self): + policy_name = 'classification' + arglist = [ + ] + verifylist = [ + ] + response = { + "results": [{"id": policy_name, + "owner": "system" + }]} + lister = mock.Mock(return_value=response) + self.app.client_manager.congressclient.list_policy = lister + cmd = policy.ListPolicy(self.app, self.namespace) + + parsed_args = self.check_parser(cmd, arglist, verifylist) + result = cmd.take_action(parsed_args) + + lister.assert_called_with() + self.assertEqual(['id', 'owner_id'], result[0]) + + +class GetPolicyRow(common.TestCongressBase): + + def test_list_policy_rules(self): + policy_name = 'classification' + table_name = 'port_security_group' + arglist = [ + policy_name, table_name + ] + verifylist = [ + ] + response = {"results": + [{"data": ["69abc88b-c950-4625-801b-542e84381509", + "default"]}]} + + lister = mock.Mock(return_value=response) + self.app.client_manager.congressclient.get_policy_rows = lister + cmd = policy.GetPolicyRow(self.app, self.namespace) + + parsed_args = self.check_parser(cmd, arglist, verifylist) + result = cmd.take_action(parsed_args) + + lister.assert_called_with(policy_name, table_name) + self.assertEqual(['data'], result[0]) diff --git a/congressclient/v1/__init__.py b/congressclient/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/congressclient/v1/client.py b/congressclient/v1/client.py new file mode 100644 index 0000000..7f07477 --- /dev/null +++ b/congressclient/v1/client.py @@ -0,0 +1,52 @@ +# Copyright 2014 VMWare. +# +# 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. + +from keystoneclient import adapter + + +class Client(object): + policy_rules_path = '/policies/%s/rules' + policy_rules_paths = '/policies/%s/rules/%s' + policy_rows = '/policies/%s/tables/%s/rows' + policy_rules = '/policies/%s/rules' + policies = '/policies' + + def __init__(self, **kwargs): + super(Client, self).__init__() + + kwargs.setdefault('user_agent', 'python-congressclient') + self.httpclient = adapter.LegacyJsonAdapter(**kwargs) + + def create_policy_rule(self, policy_name, body=None): + resp, body = self.httpclient.post( + self.policy_rules_path % policy_name, body=body) + return body + + def delete_policy_rule(self, policy_name, rule_id): + resp, body = self.httpclient.delete( + self.policy_rules_paths % (policy_name, rule_id)) + return body + + def get_policy_rows(self, policy_name, table): + resp, body = self.httpclient.get(self.policy_rows % (policy_name, + table)) + return body + + def list_policy_rules(self, policy_name): + resp, body = self.httpclient.get(self.policy_rules % (policy_name)) + return body + + def list_policy(self): + resp, body = self.httpclient.get(self.policies) + return body diff --git a/requirements.txt b/requirements.txt index 645c922..d179454 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,7 @@ pbr>=0.6,!=0.7,<1.0 Babel>=1.3 +cliff>=1.6.0 +oslo.i18n>=0.3.0 +python-keystoneclient>=0.10.0 +requests>=1.2.1 +six>=1.7.0 diff --git a/setup.cfg b/setup.cfg index b74695a..f0ecf4d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,17 @@ classifier = packages = congressclient +[entry_points] +openstack.cli.extension = + congressclient = congressclient.osc.osc_plugin + +openstack.congressclient.v1 = + congress_policy_rule_create = congressclient.osc.v1.policy:CreatePolicyRule + congress_policy_rule_delete = congressclient.osc.v1.policy:DeletePolicyRule + congress_policy_rules_list = congressclient.osc.v1.policy:ListPolicyRules + congress_policy_list = congressclient.osc.v1.policy:ListPolicy + congress_policy_row_get = congressclient.osc.v1.policy:GetPolicyRow + [build_sphinx] source-dir = doc/source build-dir = doc/build @@ -43,4 +54,4 @@ input_file = congressclient/locale/python-congressclient.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg -output_file = congressclient/locale/python-congressclient.pot \ No newline at end of file +output_file = congressclient/locale/python-congressclient.pot diff --git a/tenant-list.log b/tenant-list.log new file mode 100644 index 0000000..16100c8 --- /dev/null +++ b/tenant-list.log @@ -0,0 +1,8 @@ ++----------------------------------+--------------------+---------+ +| id | name | enabled | ++----------------------------------+--------------------+---------+ +| 8918ef508b3a48a1ad963cec1d7bec18 | admin | True | +| e41ef6f35dab448699a5ca69eff60692 | demo | True | +| c01bea38e55d44d3a87bb018b050338c | invisible_to_admin | True | +| 3a1208bc143a4ed589288057ea8e0735 | service | True | ++----------------------------------+--------------------+---------+ diff --git a/test-requirements.txt b/test-requirements.txt index d43524b..49dcd1d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ -hacking>=0.5.6,<0.8 +hacking>=0.9.2,<0.10 coverage>=3.6 discover @@ -9,3 +9,5 @@ oslosphinx>=2.2.0.0a2 testrepository>=0.0.18 testscenarios>=0.4 testtools>=0.9.34 +mock>=1.0 +WebOb>=1.2.3