summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-01-06 06:43:28 +0000
committerGerrit Code Review <review@openstack.org>2012-01-06 06:43:28 +0000
commiteac98b07b30b46edcc12b7b2f0789e294cac539b (patch)
tree3ea42fe64337d003fb87f86dd891e22a0b654058
parent6111fb5caf2c24d2f4834617e19b0568a6f3687e (diff)
parent2e3ee148fd9a4f22302fe25644b727fbd31efb94 (diff)
Merge "Initial keystone-manage rewrite (bp keystone-manage2)"
-rwxr-xr-xbin/keystone-manage10
-rw-r--r--keystone/manage2/__init__.py66
-rw-r--r--keystone/manage2/commands/__init__.py0
-rw-r--r--keystone/manage2/commands/version.py39
-rw-r--r--keystone/manage2/common.py33
-rw-r--r--keystone/test/client/test_keystone_manage.py2
-rw-r--r--keystone/test/unit/test_buffout.py91
-rw-r--r--keystone/test/unit/test_commands.py64
-rw-r--r--keystone/tools/buffout.py83
9 files changed, 386 insertions, 2 deletions
diff --git a/bin/keystone-manage b/bin/keystone-manage
index 5bef46f3..4392a0c8 100755
--- a/bin/keystone-manage
+++ b/bin/keystone-manage
@@ -11,8 +11,16 @@ if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
sys.path.insert(0, possible_topdir)
import keystone.manage
+import keystone.manage2
import keystone.tools.tracer # @UnusedImport # module runs on import
if __name__ == '__main__':
- keystone.manage.main()
+ if len(sys.argv) > 1 and sys.argv[1] in keystone.manage.OBJECTS:
+ # the args look like the old 'subject verb' (e.g. 'user add')
+ # (this module is pending deprecation)
+ keystone.manage.main()
+ else:
+ # calls that don't start with a 'subject' go to the new impl
+ # which uses a 'verb_subject' convention (e.g. 'add_user')
+ keystone.manage2.main()
diff --git a/keystone/manage2/__init__.py b/keystone/manage2/__init__.py
new file mode 100644
index 00000000..4ed86243
--- /dev/null
+++ b/keystone/manage2/__init__.py
@@ -0,0 +1,66 @@
+"""OpenStack Identity (Keystone) Management"""
+
+
+import argparse
+import pkgutil
+import os
+import sys
+
+from keystone.manage2 import commands
+
+
+# builds a complete path to the commands package
+PACKAGE_PATH = os.path.dirname(commands.__file__)
+
+# builds a list of modules in the commands package
+MODULES = [tupl for tupl in pkgutil.iter_modules([PACKAGE_PATH])]
+
+
+def load_module(module_name):
+ """Imports a module given the module name"""
+ try:
+ module_loader, name, is_package = [md for md in MODULES
+ if md[1] == module_name][0]
+ except IndexError:
+ raise ValueError("No module found named '%s'" % module_name)
+
+ loader = module_loader.find_module(name)
+ module = loader.load_module(name)
+ return module
+
+
+def main():
+ # discover command modules
+ module_names = [name for _, name, _ in MODULES]
+ module_names.sort()
+
+ # we need the name of the command before hitting argparse
+ command = None
+ for pos, arg in enumerate(sys.argv):
+ if arg in module_names:
+ command = sys.argv.pop(pos)
+ break
+
+ if command and command in module_names:
+ # load, configure and run command
+ module = load_module(command)
+ parser = argparse.ArgumentParser(prog=command,
+ description=module.Command.__doc__)
+
+ # let the command append arguments to the parser
+ module.Command.append_parser(parser)
+ args = parser.parse_args()
+
+ # command
+ exit(module.Command.run(args))
+ else:
+ # show help
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('command', metavar='command', type=str,
+ help=', '.join(module_names))
+ args = parser.parse_args()
+
+ parser.print_help()
+
+ # always exit 2; something about the input args was invalid
+ exit(2)
diff --git a/keystone/manage2/commands/__init__.py b/keystone/manage2/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keystone/manage2/commands/__init__.py
diff --git a/keystone/manage2/commands/version.py b/keystone/manage2/commands/version.py
new file mode 100644
index 00000000..0610570c
--- /dev/null
+++ b/keystone/manage2/commands/version.py
@@ -0,0 +1,39 @@
+"""Prints keystone's version information"""
+
+
+from keystone import version
+from keystone.manage2 import common
+
+
+@common.arg('--api', action='store_true',
+ default=False,
+ help='only print the API version')
+@common.arg('--implementation', action='store_true',
+ default=False,
+ help='only print the implementation version')
+class Command(common.BaseCommand):
+ """Returns keystone version data.
+
+ Includes the latest API version, implementation version, or both,
+ if neither is specified.
+ """
+
+ def get_api_version(self):
+ """Returns a complete API version string"""
+ return ' '.join([version.API_VERSION, version.API_VERSION_STATUS])
+
+ def get_implementation_version(self):
+ """Returns a complete implementation version string"""
+ return version.version()
+
+ @staticmethod
+ def run(args):
+ """Process argparse args, and print results to stdout"""
+ cmd = Command()
+
+ show_all = not (args.api or args.implementation)
+
+ if args.api or show_all:
+ print 'API v%s' % cmd.get_api_version()
+ if args.implementation or show_all:
+ print 'Implementation v%s' % cmd.get_implementation_version()
diff --git a/keystone/manage2/common.py b/keystone/manage2/common.py
new file mode 100644
index 00000000..d9c8dd23
--- /dev/null
+++ b/keystone/manage2/common.py
@@ -0,0 +1,33 @@
+def arg(name, **kwargs):
+ """Decorate the command class with an argparse argument"""
+ def _decorator(cls):
+ if not hasattr(cls, '_args'):
+ setattr(cls, '_args', {})
+ args = getattr(cls, '_args')
+ args[name] = kwargs
+ return cls
+ return _decorator
+
+
+class BaseCommand(object):
+ """Provides a common pattern for keystone-manage commands"""
+ # initialize to an empty dict, in case a command is not decorated
+ _args = {}
+
+ @staticmethod
+ def append_parser(parser):
+ """Appends this command's arguments to an argparser
+
+ :param parser: argparse.ArgumentParser
+ """
+ args = BaseCommand._args
+ for name in args.keys():
+ parser.add_argument(name, **args[name])
+
+ @staticmethod
+ def run(args):
+ """Handles argparse args and prints command results to stdout
+
+ :param args: argparse Namespace
+ """
+ raise NotImplemented()
diff --git a/keystone/test/client/test_keystone_manage.py b/keystone/test/client/test_keystone_manage.py
index bb0e0a2e..dc5cc677 100644
--- a/keystone/test/client/test_keystone_manage.py
+++ b/keystone/test/client/test_keystone_manage.py
@@ -30,7 +30,7 @@ class TestKeystoneManage(unittest.TestCase):
]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
result = process.communicate()[0]
- self.assertIn('Usage', result)
+ self.assertIn('usage', result)
def test_keystone_manage_calls(self):
"""
diff --git a/keystone/test/unit/test_buffout.py b/keystone/test/unit/test_buffout.py
new file mode 100644
index 00000000..715960e0
--- /dev/null
+++ b/keystone/test/unit/test_buffout.py
@@ -0,0 +1,91 @@
+import unittest2 as unittest
+import sys
+
+from keystone.tools import buffout
+
+
+class TestStdoutIdentity(unittest.TestCase):
+ """Tests buffout's manipulation of the stdout pointer"""
+ def test_stdout(self):
+ stdout = sys.stdout
+ ob = buffout.OutputBuffer()
+ self.assertTrue(sys.stdout is stdout,
+ "sys.stdout was replaced")
+ ob.start()
+ self.assertTrue(sys.stdout is not stdout,
+ "sys.stdout not replaced")
+ ob.stop()
+ self.assertTrue(sys.stdout is stdout,
+ "sys.stdout not restored")
+
+
+class TestOutputBufferContents(unittest.TestCase):
+ """Tests the contents of the buffer"""
+ def test_read_contents(self):
+ with buffout.OutputBuffer() as ob:
+ print 'foobar'
+ print 'wompwomp'
+ output = ob.read()
+ self.assertEquals(len(output), 16, output)
+ self.assertIn('foobar', output)
+ self.assertIn('ompwom', output)
+
+ def test_read_lines(self):
+ with buffout.OutputBuffer() as ob:
+ print 'foobar'
+ print 'wompwomp'
+ lines = ob.read_lines()
+ self.assertTrue(isinstance(lines, list))
+ self.assertEqual(len(lines), 2)
+ self.assertIn('foobar', lines)
+ self.assertIn('wompwomp', lines)
+
+ def test_additional_output(self):
+ with buffout.OutputBuffer() as ob:
+ print 'foobar'
+ lines = ob.read_lines()
+ self.assertEqual(len(lines), 1)
+ print 'wompwomp'
+ lines = ob.read_lines()
+ self.assertEqual(len(lines), 2)
+
+ def test_clear(self):
+ with buffout.OutputBuffer() as ob:
+ print 'foobar'
+ ob.clear()
+ print 'wompwomp'
+ output = ob.read()
+ self.assertNotIn('foobar', output)
+ self.assertIn('ompwom', output)
+
+ def test_buffer_preservation(self):
+ ob = buffout.OutputBuffer()
+ ob.start()
+
+ print 'foobar'
+ print 'wompwomp'
+
+ ob.stop()
+
+ output = ob.read()
+ self.assertIn('foobar', output)
+ self.assertIn('ompwom', output)
+
+ def test_buffer_contents(self):
+ ob = buffout.OutputBuffer()
+ ob.start()
+
+ print 'foobar'
+ print 'wompwomp'
+
+ ob.stop()
+
+ self.assertEqual('foobar\nwompwomp\n', unicode(ob))
+ self.assertEqual('foobar\nwompwomp\n', str(ob))
+
+ def test_exception_raising(self):
+ def raise_value_error():
+ with buffout.OutputBuffer():
+ raise ValueError()
+
+ self.assertRaises(ValueError, raise_value_error)
diff --git a/keystone/test/unit/test_commands.py b/keystone/test/unit/test_commands.py
new file mode 100644
index 00000000..3125ba1f
--- /dev/null
+++ b/keystone/test/unit/test_commands.py
@@ -0,0 +1,64 @@
+import argparse
+import logging
+import unittest2 as unittest
+
+from keystone.manage2.commands import version
+from keystone.tools import buffout
+
+
+LOGGER = logging.getLogger(__name__)
+
+
+class CommandTestCase(unittest.TestCase):
+ """Buffers stdout to test keystone-manage commands"""
+ module = None
+ stdout = None
+
+ def setUp(self):
+ # initialize the command module
+ self.cmd = self.module.Command()
+
+ # create an argparser for the module
+ self.parser = argparse.ArgumentParser()
+ version.Command.append_parser(self.parser)
+
+
+class TestVersionCommand(CommandTestCase):
+ """Tests for ./bin/keystone-manage version"""
+ module = version
+
+ API_VERSION = '2.0 beta'
+ IMPLEMENTATION_VERSION = '2012.1-dev'
+
+ def test_api_version(self):
+ v = self.cmd.get_api_version()
+ self.assertEqual(v, self.API_VERSION)
+
+ def test_implementation_version(self):
+ v = self.cmd.get_implementation_version()
+ self.assertEqual(v, self.IMPLEMENTATION_VERSION)
+
+ def test_no_args(self):
+ with buffout.OutputBuffer() as ob:
+ args = self.parser.parse_args([])
+ self.cmd.run(args)
+ lines = ob.read_lines()
+ self.assertEqual(len(lines), 2, lines)
+ self.assertIn(self.API_VERSION, lines[0])
+ self.assertIn(self.IMPLEMENTATION_VERSION, lines[1])
+
+ def test_api_arg(self):
+ with buffout.OutputBuffer() as ob:
+ args = self.parser.parse_args('--api'.split())
+ self.cmd.run(args)
+ lines = ob.read_lines()
+ self.assertEqual(len(lines), 1, lines)
+ self.assertIn(self.API_VERSION, lines[0])
+
+ def test_implementation_arg(self):
+ with buffout.OutputBuffer() as ob:
+ args = self.parser.parse_args('--implementation'.split())
+ self.cmd.run(args)
+ lines = ob.read_lines()
+ self.assertEqual(len(lines), 1, lines)
+ self.assertIn(self.IMPLEMENTATION_VERSION, lines[0])
diff --git a/keystone/tools/buffout.py b/keystone/tools/buffout.py
new file mode 100644
index 00000000..e0202c0e
--- /dev/null
+++ b/keystone/tools/buffout.py
@@ -0,0 +1,83 @@
+import sys
+import StringIO
+
+
+class OutputBuffer():
+ """Replaces stdout with a StringIO buffer"""
+
+ def __init__(self):
+ """Initialize output buffering"""
+ # True if the OutputBuffer is started
+ self.buffering = False
+
+ # a reference to the current StringIO buffer
+ self._buffer = None
+
+ # stale if buffering is True; access buffer contents using .read()
+ self._contents = None
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if exc_type is None:
+ self.stop()
+ else:
+ raise
+
+ def __unicode__(self):
+ return self._contents
+
+ def __str__(self):
+ return str(self._contents)
+
+ def start(self):
+ """Replace stdout with a fresh buffer"""
+ assert not self.buffering
+
+ self.buffering = True
+ self.old_stdout = sys.stdout
+
+ self.clear()
+
+ def read(self):
+ """Read the current buffer"""
+ if self.buffering:
+ self._contents = self._buffer.getvalue()
+
+ return self._contents
+
+ def read_lines(self):
+ """Returns the current buffer as a list
+
+ Excludes the last line, which is empty.
+
+ """
+ return self.read().split("\n")[:-1]
+
+ def clear(self):
+ """Resets the current buffer"""
+ assert self.buffering
+
+ # dispose of the previous buffer, if any
+ if self._buffer is not None:
+ self._buffer.close()
+
+ self._contents = ''
+ self._buffer = StringIO.StringIO()
+ sys.stdout = self._buffer
+
+ def stop(self):
+ """Stop buffering and pass the output along"""
+ assert self.buffering
+
+ # preserve the contents prior to closing the StringIO
+ self.read()
+ self._buffer.close()
+
+ sys.stdout = self.old_stdout
+ print self
+ self.buffering = False
+
+ return unicode(self)