diff --git a/novaclient/base.py b/novaclient/base.py
index 9b35ffed5..1f73ffda1 100644
--- a/novaclient/base.py
+++ b/novaclient/base.py
@@ -74,7 +74,10 @@ class Manager(object):
         # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
         #           unlike other services which just return the list...
         if type(data) is dict:
-            data = data['values']
+            try:
+                data = data['values']
+            except KeyError:
+                pass
 
         with self.uuid_cache(obj_class, mode="w"):
             return [obj_class(self, res, loaded=True) for res in data if res]
diff --git a/novaclient/shell.py b/novaclient/shell.py
index cee4e1d78..5c91b890b 100644
--- a/novaclient/shell.py
+++ b/novaclient/shell.py
@@ -20,10 +20,13 @@ Command-line interface to the OpenStack Nova API.
 """
 
 import argparse
+import glob
 import httplib2
+import imp
 import os
 import sys
 
+from novaclient import base
 from novaclient import exceptions as exc
 from novaclient import utils
 from novaclient.v1_0 import shell as shell_v1_0
@@ -96,7 +99,7 @@ class OpenStackComputeShell(object):
 
         return parser
 
-    def get_subcommand_parser(self, version):
+    def get_subcommand_parser(self, version, extensions):
         parser = self.get_base_parser()
 
         self.subcommands = {}
@@ -115,8 +118,49 @@ class OpenStackComputeShell(object):
         self._find_actions(subparsers, actions_module)
         self._find_actions(subparsers, self)
 
+        for _, _, ext_module in extensions:
+            self._find_actions(subparsers, ext_module)
+
+        self._add_bash_completion_subparser(subparsers)
+
         return parser
 
+    def _discover_extensions(self, version):
+        module_path = os.path.dirname(os.path.abspath(__file__))
+        version_str = "v%s" % version.replace('.', '_')
+        ext_path = os.path.join(module_path, version_str, 'contrib')
+        ext_glob = os.path.join(ext_path, "*.py")
+
+        extensions = []
+        for ext_path in glob.iglob(ext_glob):
+            name = os.path.basename(ext_path)[:-3]
+            ext_module = imp.load_source(name, ext_path)
+
+            # Extract Manager class
+            ext_manager_class = None
+            for attr_value in ext_module.__dict__.values():
+                try:
+                    if issubclass(attr_value, base.Manager):
+                        ext_manager_class = attr_value
+                        break
+                except TypeError:
+                    continue  # in case attr_value isn't a class...
+
+            if not ext_manager_class:
+                raise Exception("Could not find Manager class in extension.")
+
+            extensions.append((name, ext_manager_class, ext_module))
+
+        return extensions
+
+    def _add_bash_completion_subparser(self, subparsers):
+        subparser = subparsers.add_parser('bash_completion',
+            add_help=False,
+            formatter_class=OpenStackHelpFormatter
+        )
+        self.subcommands['bash_completion'] = subparser
+        subparser.set_defaults(func=self.do_bash_completion)
+
     def _find_actions(self, subparsers, actions_module):
         for attr in (a for a in dir(actions_module) if a.startswith('do_')):
             # I prefer to be hypen-separated instead of underscores.
@@ -147,7 +191,9 @@ class OpenStackComputeShell(object):
         (options, args) = parser.parse_known_args(argv)
 
         # build available subcommands based on version
-        subcommand_parser = self.get_subcommand_parser(options.version)
+        extensions = self._discover_extensions(options.version)
+        subcommand_parser = self.get_subcommand_parser(
+                options.version, extensions)
         self.parser = subcommand_parser
 
         # Parse args again and call whatever callback was selected
@@ -161,6 +207,9 @@ class OpenStackComputeShell(object):
         if args.func == self.do_help:
             self.do_help(args)
             return 0
+        elif args.func == self.do_bash_completion:
+            self.do_bash_completion(args)
+            return 0
 
         (user, apikey, password, projectid, url, region_name,
                 endpoint_name, insecure) = (args.username, args.apikey,
@@ -199,7 +248,8 @@ class OpenStackComputeShell(object):
         self.cs = self.get_api_class(options.version)(user, password,
                                      projectid, url, insecure,
                                      region_name=region_name,
-                                     endpoint_name=endpoint_name)
+                                     endpoint_name=endpoint_name,
+                                     extensions=extensions)
 
         try:
             self.cs.authenticate()
@@ -221,6 +271,22 @@ class OpenStackComputeShell(object):
         except KeyError:
             return shell_v1_0.CLIENT_CLASS
 
+    def do_bash_completion(self, args):
+        """
+        Prints all of the commands and options to stdout so that the
+        nova.bash_completion script doesn't have to hard code them.
+        """
+        commands = set()
+        options = set()
+        for sc_str, sc in self.subcommands.items():
+            commands.add(sc_str)
+            for option in sc._optionals._option_string_actions.keys():
+                options.add(option)
+
+        commands.remove('bash-completion')
+        commands.remove('bash_completion')
+        print ' '.join(commands | options)
+
     @utils.arg('command', metavar='<subcommand>', nargs='?',
                     help='Display help for <subcommand>')
     def do_help(self, args):
diff --git a/novaclient/utils.py b/novaclient/utils.py
index 582829d11..083620add 100644
--- a/novaclient/utils.py
+++ b/novaclient/utils.py
@@ -71,3 +71,15 @@ def find_resource(manager, name_or_id):
         msg = "No %s with a name or ID of '%s' exists." % \
               (manager.resource_class.__name__.lower(), name_or_id)
         raise exceptions.CommandError(msg)
+
+
+def _format_servers_list_networks(server):
+    output = []
+    for (network, addresses) in server.networks.items():
+        if len(addresses) == 0:
+            continue
+        addresses_csv = ', '.join(addresses)
+        group = "%s=%s" % (network, addresses_csv)
+        output.append(group)
+
+    return '; '.join(output)
diff --git a/novaclient/v1_0/client.py b/novaclient/v1_0/client.py
index 7e326cd3f..16594bae1 100644
--- a/novaclient/v1_0/client.py
+++ b/novaclient/v1_0/client.py
@@ -27,7 +27,7 @@ class Client(object):
 
     def __init__(self, username, api_key, project_id, auth_url=None,
                  insecure=False, timeout=None, token=None, region_name=None,
-                 endpoint_name='publicURL'):
+                 endpoint_name='publicURL', extensions=None):
 
         # FIXME(comstud): Rename the api_key argument above when we
         # know it's not being used as keyword argument
@@ -40,6 +40,11 @@ class Client(object):
         self.servers = servers.ServerManager(self)
         self.zones = zones.ZoneManager(self)
 
+        # Add in any extensions...
+        if extensions:
+            for (ext_name, ext_manager_class, ext_module) in extensions:
+                setattr(self, ext_name, ext_manager_class(self))
+
         _auth_url = auth_url or 'https://auth.api.rackspacecloud.com/v1.0'
 
         self.client = client.HTTPClient(username,
diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py
index cf82b1cfc..8f363f05d 100644
--- a/novaclient/v1_1/client.py
+++ b/novaclient/v1_1/client.py
@@ -33,7 +33,7 @@ class Client(object):
     # FIXME(jesse): project_id isn't required to authenticate
     def __init__(self, username, api_key, project_id, auth_url,
                   insecure=False, timeout=None, token=None, region_name=None,
-                  endpoint_name='publicURL'):
+                  endpoint_name='publicURL', extensions=None):
         # FIXME(comstud): Rename the api_key argument above when we
         # know it's not being used as keyword argument
         password = api_key
@@ -53,6 +53,11 @@ class Client(object):
         self.security_group_rules = \
             security_group_rules.SecurityGroupRuleManager(self)
 
+        # Add in any extensions...
+        if extensions:
+            for (ext_name, ext_manager_class, ext_module) in extensions:
+                setattr(self, ext_name, ext_manager_class(self))
+
         self.client = client.HTTPClient(username,
                                         password,
                                         project_id,
diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py
index 45398b3c0..cc8f7c90b 100644
--- a/novaclient/v1_1/shell.py
+++ b/novaclient/v1_1/shell.py
@@ -481,23 +481,11 @@ def do_list(cs, args):
         id_col = 'ID'
 
     columns = [id_col, 'Name', 'Status', 'Networks']
-    formatters = {'Networks': _format_servers_list_networks}
+    formatters = {'Networks': utils._format_servers_list_networks}
     utils.print_list(cs.servers.list(search_opts=search_opts), columns,
                      formatters)
 
 
-def _format_servers_list_networks(server):
-    output = []
-    for (network, addresses) in server.networks.items():
-        if len(addresses) == 0:
-            continue
-        addresses_csv = ', '.join(addresses)
-        group = "%s=%s" % (network, addresses_csv)
-        output.append(group)
-
-    return '; '.join(output)
-
-
 @utils.arg('--hard',
     dest='reboot_type',
     action='store_const',
diff --git a/run_tests.sh b/run_tests.sh
index 94ff3a14a..0a4bbf872 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -92,9 +92,8 @@ function run_pep8 {
   #     other than what the PEP8 tool claims. It is deprecated in Python 3, so,
   #     perhaps the mistake was thinking that the deprecation applied to Python 2
   #     as well.
-  ${wrapper} pep8 --repeat --show-pep8 --show-source \
-    --ignore=E202,W602 \
-    ${srcfiles}
+  pep8_opts="--ignore=E202,W602 --repeat"
+  ${wrapper} pep8 ${pep8_opts} ${srcfiles}
 }
 
 NOSETESTS="nosetests $noseopts $noseargs"
diff --git a/tests/v1_1/fakes.py b/tests/v1_1/fakes.py
index ccfb64358..8ec2929f6 100644
--- a/tests/v1_1/fakes.py
+++ b/tests/v1_1/fakes.py
@@ -1,3 +1,17 @@
+# Copyright 2011 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 httplib2
 
 from novaclient import client as base_client
diff --git a/tools/nova.bash_completion b/tools/nova.bash_completion
index 0bcd0afdf..6bbbc268d 100644
--- a/tools/nova.bash_completion
+++ b/tools/nova.bash_completion
@@ -4,15 +4,8 @@ _nova()
     COMPREPLY=()
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
-    opts="add-fixed-ip backup backup-schedule backup-schedule-delete boot
-          boot-for-account delete delete diagnostics flavor-list image-create
-          image-delete image-list ip-share ip-unshare ipgroup-create
-          ipgroup-delete ipgroup-list ipgroup-show list migrate pause reboot
-          rebuild remove-fixed-ip rename rescue resize resize-confirm
-          resize-revert resume root-password show suspend unpause unrescue
-          zone zone-add zone-boot zone-delete zone-info zone-list help
-          --debug --endpoint_name --password --projectid --region_name --url
-          --username --version"
+
+    opts="$(nova bash_completion)"
 
     UUID_CACHE=~/.novaclient_cached_*_uuids
     opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')