diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py
new file mode 100644
index 0000000000..4a7f062698
--- /dev/null
+++ b/openstackclient/common/module.py
@@ -0,0 +1,60 @@
+#   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.
+#
+
+"""Module action implementation"""
+
+import logging
+import six
+import sys
+
+from cliff import show
+
+
+class ListModule(show.ShowOne):
+    """List module versions"""
+
+    auth_required = False
+    log = logging.getLogger(__name__ + '.ListModule')
+
+    def get_parser(self, prog_name):
+        parser = super(ListModule, self).get_parser(prog_name)
+        parser.add_argument(
+            '--all',
+            action='store_true',
+            default=False,
+            help='Show all modules that have version information',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug('take_action(%s)' % parsed_args)
+
+        data = {}
+        # Get module versions
+        mods = sys.modules
+        for k in mods.keys():
+            k = k.split('.')[0]
+            # TODO(dtroyer): Need a better way to decide which modules to
+            #                show for the default (not --all) invocation.
+            #                It should be just the things we actually care
+            #                about like client and plugin modules...
+            if (parsed_args.all or 'client' in k):
+                try:
+                    data[k] = mods[k].__version__
+                except AttributeError:
+                    # aw, just skip it
+                    pass
+
+        return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py
new file mode 100644
index 0000000000..ce1592e41c
--- /dev/null
+++ b/openstackclient/tests/common/test_module.py
@@ -0,0 +1,88 @@
+#   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.
+#
+
+"""Test module module"""
+
+import mock
+
+from openstackclient.common import module as osc_module
+from openstackclient.tests import fakes
+from openstackclient.tests import utils
+
+
+# NOTE(dtroyer): module_1 must match the version list filter (not --all)
+#                currently == '*client*'
+module_name_1 = 'fakeclient'
+module_version_1 = '0.1.2'
+MODULE_1 = {
+    '__version__': module_version_1,
+}
+
+module_name_2 = 'zlib'
+module_version_2 = '1.1'
+MODULE_2 = {
+    '__version__': module_version_2,
+}
+
+MODULES = {
+    module_name_1: fakes.FakeModule(module_name_1, module_version_1),
+    module_name_2: fakes.FakeModule(module_name_2, module_version_2),
+}
+
+
+@mock.patch.dict(
+    'openstackclient.common.module.sys.modules',
+    values=MODULES,
+    clear=True,
+)
+class TestModuleList(utils.TestCommand):
+
+    def setUp(self):
+        super(TestModuleList, self).setUp()
+
+        # Get the command object to test
+        self.cmd = osc_module.ListModule(self.app, None)
+
+    def test_module_list_no_options(self):
+        arglist = []
+        verifylist = [
+            ('all', False),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # DisplayCommandBase.take_action() returns two tuples
+        columns, data = self.cmd.take_action(parsed_args)
+
+        # Additional modules may be present, just check our additions
+        self.assertTrue(module_name_1 in columns)
+        self.assertTrue(module_version_1 in data)
+
+    def test_module_list_all(self):
+        arglist = [
+            '--all',
+        ]
+        verifylist = [
+            ('all', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # DisplayCommandBase.take_action() returns two tuples
+        columns, data = self.cmd.take_action(parsed_args)
+
+        # Additional modules may be present, just check our additions
+        self.assertTrue(module_name_1 in columns)
+        self.assertTrue(module_name_2 in columns)
+        self.assertTrue(module_version_1 in data)
+        self.assertTrue(module_version_2 in data)
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index bb89f7628f..01214243b8 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -54,6 +54,12 @@ class FakeClientManager(object):
         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
diff --git a/setup.cfg b/setup.cfg
index 38205f53c6..116c6927ab 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,6 +26,7 @@ console_scripts =
     openstack = openstackclient.shell:main
 
 openstack.cli =
+    module_list = openstackclient.common.module:ListModule
 
 openstack.cli.extension =
     compute = openstackclient.compute.client