592 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			592 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python
 | 
						|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
						|
 | 
						|
# Copyright 2010 United States Government as represented by the
 | 
						|
# Administrator of the National Aeronautics and Space Administration.
 | 
						|
# 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.
 | 
						|
 | 
						|
# Interactive shell based on Django:
 | 
						|
#
 | 
						|
# Copyright (c) 2005, the Lawrence Journal-World
 | 
						|
# All rights reserved.
 | 
						|
#
 | 
						|
# Redistribution and use in source and binary forms, with or without
 | 
						|
# modification, are permitted provided that the following conditions are met:
 | 
						|
#
 | 
						|
#     1. Redistributions of source code must retain the above copyright notice,
 | 
						|
#        this list of conditions and the following disclaimer.
 | 
						|
#
 | 
						|
#     2. Redistributions in binary form must reproduce the above copyright
 | 
						|
#        notice, this list of conditions and the following disclaimer in the
 | 
						|
#        documentation and/or other materials provided with the distribution.
 | 
						|
#
 | 
						|
#     3. Neither the name of Django nor the names of its contributors may be
 | 
						|
#        used to endorse or promote products derived from this software without
 | 
						|
#        specific prior written permission.
 | 
						|
#
 | 
						|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
						|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
						|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
						|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
						|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
						|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
						|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
						|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
						|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
						|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
						|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 | 
						|
 | 
						|
"""
 | 
						|
  CLI interface for nova management.
 | 
						|
"""
 | 
						|
 | 
						|
import datetime
 | 
						|
import gettext
 | 
						|
import os
 | 
						|
import re
 | 
						|
import sys
 | 
						|
import time
 | 
						|
 | 
						|
import IPy
 | 
						|
 | 
						|
# If ../nova/__init__.py exists, add ../ to Python search path, so that
 | 
						|
# it will override what happens to be installed in /usr/(local/)lib/python...
 | 
						|
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
 | 
						|
                                   os.pardir,
 | 
						|
                                   os.pardir))
 | 
						|
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
 | 
						|
    sys.path.insert(0, possible_topdir)
 | 
						|
 | 
						|
gettext.install('nova', unicode=1)
 | 
						|
 | 
						|
from nova import context
 | 
						|
from nova import crypto
 | 
						|
from nova import db
 | 
						|
from nova import exception
 | 
						|
from nova import flags
 | 
						|
from nova import quota
 | 
						|
from nova import utils
 | 
						|
from nova.auth import manager
 | 
						|
from nova.cloudpipe import pipelib
 | 
						|
 | 
						|
 | 
						|
FLAGS = flags.FLAGS
 | 
						|
flags.DECLARE('fixed_range', 'nova.network.manager')
 | 
						|
flags.DECLARE('num_networks', 'nova.network.manager')
 | 
						|
flags.DECLARE('network_size', 'nova.network.manager')
 | 
						|
flags.DECLARE('vlan_start', 'nova.network.manager')
 | 
						|
flags.DECLARE('vpn_start', 'nova.network.manager')
 | 
						|
 | 
						|
 | 
						|
class VpnCommands(object):
 | 
						|
    """Class for managing VPNs."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.manager = manager.AuthManager()
 | 
						|
        self.pipe = pipelib.CloudPipe()
 | 
						|
 | 
						|
    def list(self, project=None):
 | 
						|
        """Print a listing of the VPN data for one or all projects.
 | 
						|
 | 
						|
        args: [project=all]"""
 | 
						|
        print "%-12s\t" % 'project',
 | 
						|
        print "%-20s\t" % 'ip:port',
 | 
						|
        print "%-20s\t" % 'private_ip',
 | 
						|
        print "%s" % 'state'
 | 
						|
        if project:
 | 
						|
            projects = [self.manager.get_project(project)]
 | 
						|
        else:
 | 
						|
            projects = self.manager.get_projects()
 | 
						|
            # NOTE(vish): This hits the database a lot.  We could optimize
 | 
						|
            #             by getting all networks in one query and all vpns
 | 
						|
            #             in aother query, then doing lookups by project
 | 
						|
        for project in projects:
 | 
						|
            print "%-12s\t" % project.name,
 | 
						|
            ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
 | 
						|
            print "%-20s\t" % ipport,
 | 
						|
            ctxt = context.get_admin_context()
 | 
						|
            vpn = db.instance_get_project_vpn(ctxt, project.id)
 | 
						|
            if vpn:
 | 
						|
                address = None
 | 
						|
                state = 'down'
 | 
						|
                if vpn.get('fixed_ip', None):
 | 
						|
                    address = vpn['fixed_ip']['address']
 | 
						|
                if project.vpn_ip and utils.vpn_ping(project.vpn_ip,
 | 
						|
                                                     project.vpn_port):
 | 
						|
                    state = 'up'
 | 
						|
                print address,
 | 
						|
                print vpn['host'],
 | 
						|
                print vpn['ec2_id'],
 | 
						|
                print vpn['state_description'],
 | 
						|
                print state
 | 
						|
            else:
 | 
						|
                print None
 | 
						|
 | 
						|
    def spawn(self):
 | 
						|
        """Run all VPNs."""
 | 
						|
        for p in reversed(self.manager.get_projects()):
 | 
						|
            if not self._vpn_for(p.id):
 | 
						|
                print 'spawning %s' % p.id
 | 
						|
                self.pipe.launch_vpn_instance(p.id)
 | 
						|
                time.sleep(10)
 | 
						|
 | 
						|
    def run(self, project_id):
 | 
						|
        """Start the VPN for a given project."""
 | 
						|
        self.pipe.launch_vpn_instance(project_id)
 | 
						|
 | 
						|
    def change(self, project_id, ip, port):
 | 
						|
        """Change the ip and port for a vpn.
 | 
						|
 | 
						|
        args: project, ip, port"""
 | 
						|
        project = self.manager.get_project(project_id)
 | 
						|
        if not project:
 | 
						|
            print 'No project %s' % (project_id)
 | 
						|
            return
 | 
						|
        admin = context.get_admin_context()
 | 
						|
        network_ref = db.project_get_network(admin, project_id)
 | 
						|
        db.network_update(admin,
 | 
						|
                          network_ref['id'],
 | 
						|
                          {'vpn_public_address': ip,
 | 
						|
                           'vpn_public_port': int(port)})
 | 
						|
 | 
						|
 | 
						|
class ShellCommands(object):
 | 
						|
    def bpython(self):
 | 
						|
        """Runs a bpython shell.
 | 
						|
 | 
						|
        Falls back to Ipython/python shell if unavailable"""
 | 
						|
        self.run('bpython')
 | 
						|
 | 
						|
    def ipython(self):
 | 
						|
        """Runs an Ipython shell.
 | 
						|
 | 
						|
        Falls back to Python shell if unavailable"""
 | 
						|
        self.run('ipython')
 | 
						|
 | 
						|
    def python(self):
 | 
						|
        """Runs a python shell.
 | 
						|
 | 
						|
        Falls back to Python shell if unavailable"""
 | 
						|
        self.run('python')
 | 
						|
 | 
						|
    def run(self, shell=None):
 | 
						|
        """Runs a Python interactive interpreter.
 | 
						|
 | 
						|
        args: [shell=bpython]"""
 | 
						|
        if not shell:
 | 
						|
            shell = 'bpython'
 | 
						|
 | 
						|
        if shell == 'bpython':
 | 
						|
            try:
 | 
						|
                import bpython
 | 
						|
                bpython.embed()
 | 
						|
            except ImportError:
 | 
						|
                shell = 'ipython'
 | 
						|
        if shell == 'ipython':
 | 
						|
            try:
 | 
						|
                import IPython
 | 
						|
                # Explicitly pass an empty list as arguments, because
 | 
						|
                # otherwise IPython would use sys.argv from this script.
 | 
						|
                shell = IPython.Shell.IPShell(argv=[])
 | 
						|
                shell.mainloop()
 | 
						|
            except ImportError:
 | 
						|
                shell = 'python'
 | 
						|
 | 
						|
        if shell == 'python':
 | 
						|
            import code
 | 
						|
            try:
 | 
						|
                # Try activating rlcompleter, because it's handy.
 | 
						|
                import readline
 | 
						|
            except ImportError:
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                # We don't have to wrap the following import in a 'try',
 | 
						|
                # because we already know 'readline' was imported successfully.
 | 
						|
                import rlcompleter
 | 
						|
                readline.parse_and_bind("tab:complete")
 | 
						|
            code.interact()
 | 
						|
 | 
						|
    def script(self, path):
 | 
						|
        """Runs the script from the specifed path with flags set properly.
 | 
						|
        arguments: path"""
 | 
						|
        exec(compile(open(path).read(), path, 'exec'), locals(), globals())
 | 
						|
 | 
						|
 | 
						|
class RoleCommands(object):
 | 
						|
    """Class for managing roles."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.manager = manager.AuthManager()
 | 
						|
 | 
						|
    def add(self, user, role, project=None):
 | 
						|
        """adds role to user
 | 
						|
        if project is specified, adds project specific role
 | 
						|
        arguments: user, role [project]"""
 | 
						|
        self.manager.add_role(user, role, project)
 | 
						|
 | 
						|
    def has(self, user, role, project=None):
 | 
						|
        """checks to see if user has role
 | 
						|
        if project is specified, returns True if user has
 | 
						|
        the global role and the project role
 | 
						|
        arguments: user, role [project]"""
 | 
						|
        print self.manager.has_role(user, role, project)
 | 
						|
 | 
						|
    def remove(self, user, role, project=None):
 | 
						|
        """removes role from user
 | 
						|
        if project is specified, removes project specific role
 | 
						|
        arguments: user, role [project]"""
 | 
						|
        self.manager.remove_role(user, role, project)
 | 
						|
 | 
						|
 | 
						|
class UserCommands(object):
 | 
						|
    """Class for managing users."""
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _print_export(user):
 | 
						|
        """Print export variables to use with API."""
 | 
						|
        print 'export EC2_ACCESS_KEY=%s' % user.access
 | 
						|
        print 'export EC2_SECRET_KEY=%s' % user.secret
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.manager = manager.AuthManager()
 | 
						|
 | 
						|
    def admin(self, name, access=None, secret=None):
 | 
						|
        """creates a new admin and prints exports
 | 
						|
        arguments: name [access] [secret]"""
 | 
						|
        user = self.manager.create_user(name, access, secret, True)
 | 
						|
        self._print_export(user)
 | 
						|
 | 
						|
    def create(self, name, access=None, secret=None):
 | 
						|
        """creates a new user and prints exports
 | 
						|
        arguments: name [access] [secret]"""
 | 
						|
        user = self.manager.create_user(name, access, secret, False)
 | 
						|
        self._print_export(user)
 | 
						|
 | 
						|
    def delete(self, name):
 | 
						|
        """deletes an existing user
 | 
						|
        arguments: name"""
 | 
						|
        self.manager.delete_user(name)
 | 
						|
 | 
						|
    def exports(self, name):
 | 
						|
        """prints access and secrets for user in export format
 | 
						|
        arguments: name"""
 | 
						|
        user = self.manager.get_user(name)
 | 
						|
        if user:
 | 
						|
            self._print_export(user)
 | 
						|
        else:
 | 
						|
            print "User %s doesn't exist" % name
 | 
						|
 | 
						|
    def list(self):
 | 
						|
        """lists all users
 | 
						|
        arguments: <none>"""
 | 
						|
        for user in self.manager.get_users():
 | 
						|
            print user.name
 | 
						|
 | 
						|
    def modify(self, name, access_key, secret_key, is_admin):
 | 
						|
        """update a users keys & admin flag
 | 
						|
        arguments: accesskey secretkey admin
 | 
						|
        leave any field blank to ignore it, admin should be 'T', 'F', or blank
 | 
						|
        """
 | 
						|
        if not is_admin:
 | 
						|
            is_admin = None
 | 
						|
        elif is_admin.upper()[0] == 'T':
 | 
						|
            is_admin = True
 | 
						|
        else:
 | 
						|
            is_admin = False
 | 
						|
        self.manager.modify_user(name, access_key, secret_key, is_admin)
 | 
						|
 | 
						|
    def revoke(self, user_id, project_id=None):
 | 
						|
        """revoke certs for a user
 | 
						|
        arguments: user_id [project_id]"""
 | 
						|
        if project_id:
 | 
						|
            crypto.revoke_certs_by_user_and_project(user_id, project_id)
 | 
						|
        else:
 | 
						|
            crypto.revoke_certs_by_user(user_id)
 | 
						|
 | 
						|
 | 
						|
class ProjectCommands(object):
 | 
						|
    """Class for managing projects."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.manager = manager.AuthManager()
 | 
						|
 | 
						|
    def add(self, project_id, user_id):
 | 
						|
        """Adds user to project
 | 
						|
        arguments: project_id user_id"""
 | 
						|
        self.manager.add_to_project(user_id, project_id)
 | 
						|
 | 
						|
    def create(self, name, project_manager, description=None):
 | 
						|
        """Creates a new project
 | 
						|
        arguments: name project_manager [description]"""
 | 
						|
        self.manager.create_project(name, project_manager, description)
 | 
						|
 | 
						|
    def delete(self, name):
 | 
						|
        """Deletes an existing project
 | 
						|
        arguments: name"""
 | 
						|
        self.manager.delete_project(name)
 | 
						|
 | 
						|
    def environment(self, project_id, user_id, filename='novarc'):
 | 
						|
        """Exports environment variables to an sourcable file
 | 
						|
        arguments: project_id user_id [filename='novarc]"""
 | 
						|
        rc = self.manager.get_environment_rc(user_id, project_id)
 | 
						|
        with open(filename, 'w') as f:
 | 
						|
            f.write(rc)
 | 
						|
 | 
						|
    def list(self):
 | 
						|
        """Lists all projects
 | 
						|
        arguments: <none>"""
 | 
						|
        for project in self.manager.get_projects():
 | 
						|
            print project.name
 | 
						|
 | 
						|
    def quota(self, project_id, key=None, value=None):
 | 
						|
        """Set or display quotas for project
 | 
						|
        arguments: project_id [key] [value]"""
 | 
						|
        ctxt = context.get_admin_context()
 | 
						|
        if key:
 | 
						|
            quo = {'project_id': project_id, key: value}
 | 
						|
            try:
 | 
						|
                db.quota_update(ctxt, project_id, quo)
 | 
						|
            except exception.NotFound:
 | 
						|
                db.quota_create(ctxt, quo)
 | 
						|
        project_quota = quota.get_quota(ctxt, project_id)
 | 
						|
        for key, value in project_quota.iteritems():
 | 
						|
            print '%s: %s' % (key, value)
 | 
						|
 | 
						|
    def remove(self, project_id, user_id):
 | 
						|
        """Removes user from project
 | 
						|
        arguments: project_id user_id"""
 | 
						|
        self.manager.remove_from_project(user_id, project_id)
 | 
						|
 | 
						|
    def scrub(self, project_id):
 | 
						|
        """Deletes data associated with project
 | 
						|
        arguments: project_id"""
 | 
						|
        ctxt = context.get_admin_context()
 | 
						|
        network_ref = db.project_get_network(ctxt, project_id)
 | 
						|
        db.network_disassociate(ctxt, network_ref['id'])
 | 
						|
        groups = db.security_group_get_by_project(ctxt, project_id)
 | 
						|
        for group in groups:
 | 
						|
            db.security_group_destroy(ctxt, group['id'])
 | 
						|
 | 
						|
    def zipfile(self, project_id, user_id, filename='nova.zip'):
 | 
						|
        """Exports credentials for project to a zip file
 | 
						|
        arguments: project_id user_id [filename='nova.zip]"""
 | 
						|
        try:
 | 
						|
            zip_file = self.manager.get_credentials(user_id, project_id)
 | 
						|
            with open(filename, 'w') as f:
 | 
						|
                f.write(zip_file)
 | 
						|
        except db.api.NoMoreNetworks:
 | 
						|
            print ('No more networks available. If this is a new '
 | 
						|
                   'installation, you need\nto call something like this:\n\n'
 | 
						|
                   '    nova-manage network create 10.0.0.0/8 10 64\n\n')
 | 
						|
 | 
						|
 | 
						|
class FloatingIpCommands(object):
 | 
						|
    """Class for managing floating ip."""
 | 
						|
 | 
						|
    def create(self, host, range):
 | 
						|
        """Creates floating ips for host by range
 | 
						|
        arguments: host ip_range"""
 | 
						|
        for address in IPy.IP(range):
 | 
						|
            db.floating_ip_create(context.get_admin_context(),
 | 
						|
                                  {'address': str(address),
 | 
						|
                                   'host': host})
 | 
						|
 | 
						|
    def delete(self, ip_range):
 | 
						|
        """Deletes floating ips by range
 | 
						|
        arguments: range"""
 | 
						|
        for address in IPy.IP(ip_range):
 | 
						|
            db.floating_ip_destroy(context.get_admin_context(),
 | 
						|
                                   str(address))
 | 
						|
 | 
						|
    def list(self, host=None):
 | 
						|
        """Lists all floating ips (optionally by host)
 | 
						|
        arguments: [host]"""
 | 
						|
        ctxt = context.get_admin_context()
 | 
						|
        if host == None:
 | 
						|
            floating_ips = db.floating_ip_get_all(ctxt)
 | 
						|
        else:
 | 
						|
            floating_ips = db.floating_ip_get_all_by_host(ctxt, host)
 | 
						|
        for floating_ip in floating_ips:
 | 
						|
            instance = None
 | 
						|
            if floating_ip['fixed_ip']:
 | 
						|
                instance = floating_ip['fixed_ip']['instance']['ec2_id']
 | 
						|
            print "%s\t%s\t%s" % (floating_ip['host'],
 | 
						|
                                  floating_ip['address'],
 | 
						|
                                  instance)
 | 
						|
 | 
						|
 | 
						|
class NetworkCommands(object):
 | 
						|
    """Class for managing networks."""
 | 
						|
 | 
						|
    def create(self, fixed_range=None, num_networks=None,
 | 
						|
               network_size=None, vlan_start=None, vpn_start=None):
 | 
						|
        """Creates fixed ips for host by range
 | 
						|
        arguments: [fixed_range=FLAG], [num_networks=FLAG],
 | 
						|
                   [network_size=FLAG], [vlan_start=FLAG],
 | 
						|
                   [vpn_start=FLAG]"""
 | 
						|
        if not fixed_range:
 | 
						|
            fixed_range = FLAGS.fixed_range
 | 
						|
        if not num_networks:
 | 
						|
            num_networks = FLAGS.num_networks
 | 
						|
        if not network_size:
 | 
						|
            network_size = FLAGS.network_size
 | 
						|
        if not vlan_start:
 | 
						|
            vlan_start = FLAGS.vlan_start
 | 
						|
        if not vpn_start:
 | 
						|
            vpn_start = FLAGS.vpn_start
 | 
						|
        net_manager = utils.import_object(FLAGS.network_manager)
 | 
						|
        net_manager.create_networks(context.get_admin_context(),
 | 
						|
                                    fixed_range, int(num_networks),
 | 
						|
                                    int(network_size), int(vlan_start),
 | 
						|
                                    int(vpn_start))
 | 
						|
 | 
						|
 | 
						|
class ServiceCommands(object):
 | 
						|
    """Enable and disable running services"""
 | 
						|
 | 
						|
    def list(self, host=None, service=None):
 | 
						|
        """Show a list of all running services. Filter by host & service name.
 | 
						|
        args: [host] [service]"""
 | 
						|
        ctxt = context.get_admin_context()
 | 
						|
        now = datetime.datetime.utcnow()
 | 
						|
        services = db.service_get_all(ctxt)
 | 
						|
        if host:
 | 
						|
            services = [s for s in services if s['host'] == host]
 | 
						|
        if service:
 | 
						|
            services = [s for s in services if s['binary'] == service]
 | 
						|
        for svc in services:
 | 
						|
            delta = now - (svc['updated_at'] or svc['created_at'])
 | 
						|
            alive = (delta.seconds <= 15)
 | 
						|
            art = (alive and ":-)") or "XXX"
 | 
						|
            active = 'enabled'
 | 
						|
            if svc['disabled']:
 | 
						|
                active = 'disabled'
 | 
						|
            print "%-10s %-10s %-8s %s %s" % (svc['host'], svc['binary'],
 | 
						|
                                              active, art,
 | 
						|
                                              svc['updated_at'])
 | 
						|
 | 
						|
    def enable(self, host, service):
 | 
						|
        """Enable scheduling for a service
 | 
						|
        args: host service"""
 | 
						|
        ctxt = context.get_admin_context()
 | 
						|
        svc = db.service_get_by_args(ctxt, host, service)
 | 
						|
        if not svc:
 | 
						|
            print "Unable to find service"
 | 
						|
            return
 | 
						|
        db.service_update(ctxt, svc['id'], {'disabled': False})
 | 
						|
 | 
						|
    def disable(self, host, service):
 | 
						|
        """Disable scheduling for a service
 | 
						|
        args: host service"""
 | 
						|
        ctxt = context.get_admin_context()
 | 
						|
        svc = db.service_get_by_args(ctxt, host, service)
 | 
						|
        if not svc:
 | 
						|
            print "Unable to find service"
 | 
						|
            return
 | 
						|
        db.service_update(ctxt, svc['id'], {'disabled': True})
 | 
						|
 | 
						|
 | 
						|
class LogCommands(object):
 | 
						|
    def request(self, request_id, logfile='/var/log/nova.log'):
 | 
						|
        """Show all fields in the log for the given request.  Assumes you
 | 
						|
        haven't changed the log format too much.
 | 
						|
        ARGS: request_id [logfile]"""
 | 
						|
        lines = utils.execute("cat %s | grep '\[%s '" % (logfile, request_id))
 | 
						|
        print re.sub('#012', "\n", "\n".join(lines))
 | 
						|
 | 
						|
 | 
						|
CATEGORIES = [
 | 
						|
    ('user', UserCommands),
 | 
						|
    ('project', ProjectCommands),
 | 
						|
    ('role', RoleCommands),
 | 
						|
    ('shell', ShellCommands),
 | 
						|
    ('vpn', VpnCommands),
 | 
						|
    ('floating', FloatingIpCommands),
 | 
						|
    ('network', NetworkCommands),
 | 
						|
    ('service', ServiceCommands),
 | 
						|
    ('log', LogCommands)]
 | 
						|
 | 
						|
 | 
						|
def lazy_match(name, key_value_tuples):
 | 
						|
    """Finds all objects that have a key that case insensitively contains
 | 
						|
    [name] key_value_tuples is a list of tuples of the form (key, value)
 | 
						|
    returns a list of tuples of the form (key, value)"""
 | 
						|
    result = []
 | 
						|
    for (k, v) in key_value_tuples:
 | 
						|
        if k.lower().find(name.lower()) == 0:
 | 
						|
            result.append((k, v))
 | 
						|
    if len(result) == 0:
 | 
						|
        print "%s does not match any options:" % name
 | 
						|
        for k, _v in key_value_tuples:
 | 
						|
            print "\t%s" % k
 | 
						|
        sys.exit(2)
 | 
						|
    if len(result) > 1:
 | 
						|
        print "%s matched multiple options:" % name
 | 
						|
        for k, _v in result:
 | 
						|
            print "\t%s" % k
 | 
						|
        sys.exit(2)
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
def methods_of(obj):
 | 
						|
    """Get all callable methods of an object that don't start with underscore
 | 
						|
    returns a list of tuples of the form (method_name, method)"""
 | 
						|
    result = []
 | 
						|
    for i in dir(obj):
 | 
						|
        if callable(getattr(obj, i)) and not i.startswith('_'):
 | 
						|
            result.append((i, getattr(obj, i)))
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    """Parse options and call the appropriate class/method."""
 | 
						|
    utils.default_flagfile()
 | 
						|
    argv = FLAGS(sys.argv)
 | 
						|
 | 
						|
    script_name = argv.pop(0)
 | 
						|
    if len(argv) < 1:
 | 
						|
        print script_name + " category action [<args>]"
 | 
						|
        print "Available categories:"
 | 
						|
        for k, _ in CATEGORIES:
 | 
						|
            print "\t%s" % k
 | 
						|
        sys.exit(2)
 | 
						|
    category = argv.pop(0)
 | 
						|
    matches = lazy_match(category, CATEGORIES)
 | 
						|
    # instantiate the command group object
 | 
						|
    category, fn = matches[0]
 | 
						|
    command_object = fn()
 | 
						|
    actions = methods_of(command_object)
 | 
						|
    if len(argv) < 1:
 | 
						|
        print script_name + " category action [<args>]"
 | 
						|
        print "Available actions for %s category:" % category
 | 
						|
        for k, _v in actions:
 | 
						|
            print "\t%s" % k
 | 
						|
        sys.exit(2)
 | 
						|
    action = argv.pop(0)
 | 
						|
    matches = lazy_match(action, actions)
 | 
						|
    action, fn = matches[0]
 | 
						|
    # call the action with the remaining arguments
 | 
						|
    try:
 | 
						|
        fn(*argv)
 | 
						|
        sys.exit(0)
 | 
						|
    except TypeError:
 | 
						|
        print "Possible wrong number of arguments supplied"
 | 
						|
        print "%s %s: %s" % (category, action, fn.__doc__)
 | 
						|
        raise
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main()
 |