2011-02-25 02:00:13 -08:00
|
|
|
# Copyright 2010 Jacob Kaplan-Moss
|
2011-02-24 13:54:10 -04:00
|
|
|
|
|
|
|
# Copyright 2011 OpenStack LLC.
|
|
|
|
# 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.
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
"""
|
2011-02-08 09:27:22 -04:00
|
|
|
Command-line interface to the OpenStack Nova API.
|
2011-01-25 14:01:22 -06:00
|
|
|
"""
|
|
|
|
|
|
|
|
import argparse
|
2011-02-26 05:04:40 -04:00
|
|
|
import novaclient
|
2011-01-25 14:01:22 -06:00
|
|
|
import getpass
|
|
|
|
import httplib2
|
|
|
|
import os
|
|
|
|
import prettytable
|
|
|
|
import sys
|
|
|
|
import textwrap
|
2011-06-09 10:38:01 -05:00
|
|
|
import uuid
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
# Choices for flags.
|
2011-02-26 05:04:40 -04:00
|
|
|
DAY_CHOICES = [getattr(novaclient, i).lower()
|
|
|
|
for i in dir(novaclient)
|
2011-01-25 14:01:22 -06:00
|
|
|
if i.startswith('BACKUP_WEEKLY_')]
|
2011-02-26 05:04:40 -04:00
|
|
|
HOUR_CHOICES = [getattr(novaclient, i).lower()
|
|
|
|
for i in dir(novaclient)
|
2011-01-25 14:01:22 -06:00
|
|
|
if i.startswith('BACKUP_DAILY_')]
|
|
|
|
|
|
|
|
|
|
|
|
def pretty_choice_list(l):
|
|
|
|
return ', '.join("'%s'" % i for i in l)
|
|
|
|
|
|
|
|
# Sentinal for boot --key
|
|
|
|
AUTO_KEY = object()
|
|
|
|
|
|
|
|
|
|
|
|
# Decorator for args
|
|
|
|
def arg(*args, **kwargs):
|
|
|
|
def _decorator(func):
|
|
|
|
# Because of the sematics of decorator composition if we just append
|
|
|
|
# to the options list positional options will appear to be backwards.
|
|
|
|
func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
|
|
|
|
return func
|
|
|
|
return _decorator
|
|
|
|
|
|
|
|
|
|
|
|
class CommandError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def env(e):
|
|
|
|
return os.environ.get(e, '')
|
|
|
|
|
|
|
|
|
2011-02-08 09:27:22 -04:00
|
|
|
class OpenStackShell(object):
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
# Hook for the test suite to inject a fake server.
|
2011-02-26 05:04:40 -04:00
|
|
|
_api_class = novaclient.OpenStack
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.parser = argparse.ArgumentParser(
|
2011-02-24 13:54:10 -04:00
|
|
|
prog='nova',
|
2011-01-25 14:01:22 -06:00
|
|
|
description=__doc__.strip(),
|
2011-02-24 13:54:10 -04:00
|
|
|
epilog='See "nova help COMMAND" '\
|
2011-01-25 14:01:22 -06:00
|
|
|
'for help on a specific command.',
|
|
|
|
add_help=False,
|
2011-02-08 09:27:22 -04:00
|
|
|
formatter_class=OpenStackHelpFormatter,
|
2011-01-25 14:01:22 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
# Global arguments
|
|
|
|
self.parser.add_argument('-h', '--help',
|
|
|
|
action='help',
|
|
|
|
help=argparse.SUPPRESS,
|
|
|
|
)
|
|
|
|
|
|
|
|
self.parser.add_argument('--debug',
|
|
|
|
default=False,
|
|
|
|
action='store_true',
|
|
|
|
help=argparse.SUPPRESS)
|
|
|
|
|
|
|
|
self.parser.add_argument('--username',
|
2011-02-14 14:27:21 -08:00
|
|
|
default=env('NOVA_USERNAME'),
|
|
|
|
help='Defaults to env[NOVA_USERNAME].')
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
self.parser.add_argument('--apikey',
|
2011-02-14 14:27:21 -08:00
|
|
|
default=env('NOVA_API_KEY'),
|
|
|
|
help='Defaults to env[NOVA_API_KEY].')
|
2011-01-25 14:01:22 -06:00
|
|
|
|
2011-06-09 11:57:46 +04:00
|
|
|
self.parser.add_argument('--projectid',
|
2011-06-09 10:39:13 +04:00
|
|
|
default=env('NOVA_PROJECT_ID'),
|
|
|
|
help='Defaults to env[NOVA_PROJECT_ID].')
|
|
|
|
|
2011-02-14 14:27:21 -08:00
|
|
|
auth_url = env('NOVA_URL')
|
2011-01-25 14:01:22 -06:00
|
|
|
if auth_url == '':
|
|
|
|
auth_url = 'https://auth.api.rackspacecloud.com/v1.0'
|
|
|
|
self.parser.add_argument('--url',
|
|
|
|
default=auth_url,
|
2011-02-14 14:27:21 -08:00
|
|
|
help='Defaults to env[NOVA_URL].')
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
# Subcommands
|
|
|
|
subparsers = self.parser.add_subparsers(metavar='<subcommand>')
|
|
|
|
self.subcommands = {}
|
|
|
|
|
|
|
|
# Everything that's do_* is a subcommand.
|
|
|
|
for attr in (a for a in dir(self) if a.startswith('do_')):
|
|
|
|
# I prefer to be hypen-separated instead of underscores.
|
|
|
|
command = attr[3:].replace('_', '-')
|
|
|
|
callback = getattr(self, attr)
|
|
|
|
desc = callback.__doc__ or ''
|
|
|
|
help = desc.strip().split('\n')[0]
|
|
|
|
arguments = getattr(callback, 'arguments', [])
|
|
|
|
|
|
|
|
subparser = subparsers.add_parser(command,
|
|
|
|
help=help,
|
|
|
|
description=desc,
|
|
|
|
add_help=False,
|
2011-02-08 14:45:21 -04:00
|
|
|
formatter_class=OpenStackHelpFormatter
|
2011-01-25 14:01:22 -06:00
|
|
|
)
|
|
|
|
subparser.add_argument('-h', '--help',
|
|
|
|
action='help',
|
|
|
|
help=argparse.SUPPRESS,
|
|
|
|
)
|
|
|
|
self.subcommands[command] = subparser
|
|
|
|
for (args, kwargs) in arguments:
|
|
|
|
subparser.add_argument(*args, **kwargs)
|
|
|
|
subparser.set_defaults(func=callback)
|
|
|
|
|
|
|
|
def main(self, argv):
|
|
|
|
# Parse args and call whatever callback was selected
|
|
|
|
args = self.parser.parse_args(argv)
|
|
|
|
|
|
|
|
# Short-circuit and deal with help right away.
|
|
|
|
if args.func == self.do_help:
|
|
|
|
self.do_help(args)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
# Deal with global arguments
|
|
|
|
if args.debug:
|
|
|
|
httplib2.debuglevel = 1
|
|
|
|
|
2011-07-08 10:54:43 -07:00
|
|
|
user, apikey, projectid, url = args.username, args.apikey, \
|
|
|
|
args.projectid, args.url
|
2011-06-15 09:48:24 -07:00
|
|
|
|
2011-07-08 10:54:43 -07:00
|
|
|
#FIXME(usrleon): Here should be restrict for project id same as
|
|
|
|
# for username or apikey but for compatibility it is not.
|
2011-06-15 09:48:24 -07:00
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
if not user:
|
|
|
|
raise CommandError("You must provide a username, either via "
|
2011-02-14 14:27:21 -08:00
|
|
|
"--username or via env[NOVA_USERNAME]")
|
2011-01-25 14:01:22 -06:00
|
|
|
if not apikey:
|
|
|
|
raise CommandError("You must provide an API key, either via "
|
2011-02-14 14:27:21 -08:00
|
|
|
"--apikey or via env[NOVA_API_KEY]")
|
2011-01-25 14:01:22 -06:00
|
|
|
|
2011-06-09 10:39:13 +04:00
|
|
|
self.cs = self._api_class(user, apikey, projectid, url)
|
2011-01-25 14:01:22 -06:00
|
|
|
try:
|
|
|
|
self.cs.authenticate()
|
2011-02-26 05:04:40 -04:00
|
|
|
except novaclient.Unauthorized:
|
2011-02-08 09:27:22 -04:00
|
|
|
raise CommandError("Invalid OpenStack Nova credentials.")
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
args.func(args)
|
|
|
|
|
|
|
|
@arg('command', metavar='<subcommand>', nargs='?',
|
|
|
|
help='Display help for <subcommand>')
|
|
|
|
def do_help(self, args):
|
|
|
|
"""
|
|
|
|
Display help about this program or one of its subcommands.
|
|
|
|
"""
|
|
|
|
if args.command:
|
|
|
|
if args.command in self.subcommands:
|
|
|
|
self.subcommands[args.command].print_help()
|
|
|
|
else:
|
|
|
|
raise CommandError("'%s' is not a valid subcommand." %
|
|
|
|
args.command)
|
|
|
|
else:
|
|
|
|
self.parser.print_help()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('--enable', dest='enabled', default=None, action='store_true',
|
|
|
|
help='Enable backups.')
|
|
|
|
@arg('--disable', dest='enabled', action='store_false',
|
|
|
|
help='Disable backups.')
|
|
|
|
@arg('--weekly', metavar='<day>', choices=DAY_CHOICES,
|
|
|
|
help='Schedule a weekly backup for <day> (one of: %s).' %
|
|
|
|
pretty_choice_list(DAY_CHOICES))
|
|
|
|
@arg('--daily', metavar='<time-window>', choices=HOUR_CHOICES,
|
|
|
|
help='Schedule a daily backup during <time-window> (one of: %s).' %
|
|
|
|
pretty_choice_list(HOUR_CHOICES))
|
|
|
|
def do_backup_schedule(self, args):
|
|
|
|
"""
|
|
|
|
Show or edit the backup schedule for a server.
|
|
|
|
|
|
|
|
With no flags, the backup schedule will be shown. If flags are given,
|
|
|
|
the backup schedule will be modified accordingly.
|
|
|
|
"""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
|
|
|
|
# If we have some flags, update the backup
|
|
|
|
backup = {}
|
|
|
|
if args.daily:
|
2011-02-26 05:04:40 -04:00
|
|
|
backup['daily'] = getattr(novaclient, 'BACKUP_DAILY_%s' %
|
2011-01-25 14:01:22 -06:00
|
|
|
args.daily.upper())
|
|
|
|
if args.weekly:
|
2011-02-26 05:04:40 -04:00
|
|
|
backup['weekly'] = getattr(novaclient, 'BACKUP_WEEKLY_%s' %
|
2011-01-25 14:01:22 -06:00
|
|
|
args.weekly.upper())
|
|
|
|
if args.enabled is not None:
|
|
|
|
backup['enabled'] = args.enabled
|
|
|
|
if backup:
|
|
|
|
server.backup_schedule.update(**backup)
|
|
|
|
else:
|
|
|
|
print_dict(server.backup_schedule._info)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_backup_schedule_delete(self, args):
|
|
|
|
"""
|
|
|
|
Delete the backup schedule for a server.
|
|
|
|
"""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
server.backup_schedule.delete()
|
|
|
|
|
2011-06-23 22:39:10 -07:00
|
|
|
def _boot(self, args, reservation_id=None, min_count=None, max_count=None):
|
2011-01-25 14:01:22 -06:00
|
|
|
"""Boot a new server."""
|
2011-06-23 22:39:10 -07:00
|
|
|
if min_count is None:
|
|
|
|
min_count = 1
|
|
|
|
if max_count is None:
|
|
|
|
max_count = min_count
|
|
|
|
if min_count > max_count:
|
2011-06-20 16:26:57 +00:00
|
|
|
raise CommandError("min_instances should be <= max_instances")
|
|
|
|
if not min_count or not max_count:
|
|
|
|
raise CommandError("min_instances nor max_instances should be 0")
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
flavor = args.flavor or self.cs.flavors.find(ram=256)
|
|
|
|
image = args.image or self.cs.images.find(name="Ubuntu 10.04 LTS "\
|
|
|
|
"(lucid)")
|
|
|
|
|
|
|
|
# Map --ipgroup <name> to an ID.
|
|
|
|
# XXX do this for flavor/image?
|
|
|
|
if args.ipgroup:
|
|
|
|
ipgroup = self._find_ipgroup(args.ipgroup)
|
|
|
|
else:
|
|
|
|
ipgroup = None
|
|
|
|
|
|
|
|
metadata = dict(v.split('=') for v in args.meta)
|
|
|
|
|
|
|
|
files = {}
|
|
|
|
for f in args.files:
|
|
|
|
dst, src = f.split('=', 1)
|
|
|
|
try:
|
|
|
|
files[dst] = open(src)
|
|
|
|
except IOError, e:
|
|
|
|
raise CommandError("Can't open '%s': %s" % (src, e))
|
|
|
|
|
|
|
|
if args.key is AUTO_KEY:
|
|
|
|
possible_keys = [os.path.join(os.path.expanduser('~'), '.ssh', k)
|
|
|
|
for k in ('id_dsa.pub', 'id_rsa.pub')]
|
|
|
|
for k in possible_keys:
|
|
|
|
if os.path.exists(k):
|
|
|
|
keyfile = k
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise CommandError("Couldn't find a key file: tried "
|
|
|
|
"~/.ssh/id_dsa.pub or ~/.ssh/id_rsa.pub")
|
|
|
|
elif args.key:
|
|
|
|
keyfile = args.key
|
|
|
|
else:
|
|
|
|
keyfile = None
|
|
|
|
|
|
|
|
if keyfile:
|
|
|
|
try:
|
|
|
|
files['/root/.ssh/authorized_keys2'] = open(keyfile)
|
|
|
|
except IOError, e:
|
|
|
|
raise CommandError("Can't open '%s': %s" % (keyfile, e))
|
|
|
|
|
2011-05-30 18:04:13 -07:00
|
|
|
return (args.name, image, flavor, ipgroup, metadata, files,
|
2011-06-20 16:26:57 +00:00
|
|
|
reservation_id, min_count, max_count)
|
2011-05-30 18:04:13 -07:00
|
|
|
|
|
|
|
@arg('--flavor',
|
|
|
|
default=None,
|
|
|
|
metavar='<flavor>',
|
|
|
|
help="Flavor ID (see 'novaclient flavors'). "\
|
|
|
|
"Defaults to 256MB RAM instance.")
|
|
|
|
@arg('--image',
|
|
|
|
default=None,
|
|
|
|
metavar='<image>',
|
|
|
|
help="Image ID (see 'novaclient images'). "\
|
|
|
|
"Defaults to Ubuntu 10.04 LTS.")
|
|
|
|
@arg('--ipgroup',
|
|
|
|
default=None,
|
|
|
|
metavar='<group>',
|
|
|
|
help="IP group name or ID (see 'novaclient ipgroup-list').")
|
|
|
|
@arg('--meta',
|
|
|
|
metavar="<key=value>",
|
|
|
|
action='append',
|
|
|
|
default=[],
|
|
|
|
help="Record arbitrary key/value metadata. "\
|
|
|
|
"May be give multiple times.")
|
|
|
|
@arg('--file',
|
|
|
|
metavar="<dst-path=src-path>",
|
|
|
|
action='append',
|
|
|
|
dest='files',
|
|
|
|
default=[],
|
|
|
|
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
|
|
|
"on the new server. You may store up to 5 files.")
|
|
|
|
@arg('--key',
|
|
|
|
metavar='<path>',
|
|
|
|
nargs='?',
|
|
|
|
const=AUTO_KEY,
|
|
|
|
help="Key the server with an SSH keypair. "\
|
|
|
|
"Looks in ~/.ssh for a key, "\
|
|
|
|
"or takes an explicit <path> to one.")
|
|
|
|
@arg('name', metavar='<name>', help='Name for the new server')
|
|
|
|
def do_boot(self, args):
|
|
|
|
"""Boot a new server."""
|
2011-06-20 19:16:15 -07:00
|
|
|
name, image, flavor, ipgroup, metadata, files, reservation_id, \
|
|
|
|
min_count, max_count = self._boot(args)
|
2011-05-30 18:04:13 -07:00
|
|
|
|
|
|
|
server = self.cs.servers.create(args.name, image, flavor,
|
|
|
|
ipgroup=ipgroup,
|
|
|
|
meta=metadata,
|
2011-06-20 16:26:57 +00:00
|
|
|
files=files,
|
|
|
|
min_count=min_count,
|
|
|
|
max_count=max_count)
|
2011-01-25 14:01:22 -06:00
|
|
|
print_dict(server._info)
|
|
|
|
|
2011-06-13 15:13:33 -05:00
|
|
|
@arg('--flavor',
|
|
|
|
default=None,
|
|
|
|
metavar='<flavor>',
|
|
|
|
help="Flavor ID (see 'novaclient flavors'). "\
|
|
|
|
"Defaults to 256MB RAM instance.")
|
|
|
|
@arg('--image',
|
|
|
|
default=None,
|
|
|
|
metavar='<image>',
|
|
|
|
help="Image ID (see 'novaclient images'). "\
|
|
|
|
"Defaults to Ubuntu 10.04 LTS.")
|
|
|
|
@arg('--ipgroup',
|
|
|
|
default=None,
|
|
|
|
metavar='<group>',
|
|
|
|
help="IP group name or ID (see 'novaclient ipgroup-list').")
|
|
|
|
@arg('--meta',
|
|
|
|
metavar="<key=value>",
|
|
|
|
action='append',
|
|
|
|
default=[],
|
|
|
|
help="Record arbitrary key/value metadata. "\
|
|
|
|
"May be give multiple times.")
|
|
|
|
@arg('--file',
|
|
|
|
metavar="<dst-path=src-path>",
|
|
|
|
action='append',
|
|
|
|
dest='files',
|
|
|
|
default=[],
|
|
|
|
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
|
|
|
"on the new server. You may store up to 5 files.")
|
|
|
|
@arg('--key',
|
|
|
|
metavar='<path>',
|
|
|
|
nargs='?',
|
|
|
|
const=AUTO_KEY,
|
|
|
|
help="Key the server with an SSH keypair. "\
|
|
|
|
"Looks in ~/.ssh for a key, "\
|
|
|
|
"or takes an explicit <path> to one.")
|
|
|
|
@arg('account', metavar='<account>', help='Account to build this'\
|
2011-06-13 15:18:11 -05:00
|
|
|
' server for')
|
2011-06-13 15:13:33 -05:00
|
|
|
@arg('name', metavar='<name>', help='Name for the new server')
|
|
|
|
def do_boot_for_account(self, args):
|
|
|
|
"""Boot a new server in an account."""
|
2011-06-20 19:16:15 -07:00
|
|
|
name, image, flavor, ipgroup, metadata, files, reservation_id, \
|
|
|
|
min_count, max_count = self._boot(args)
|
2011-06-13 15:13:33 -05:00
|
|
|
|
|
|
|
server = self.cs.accounts.create_instance_for(args.account, args.name,
|
|
|
|
image, flavor,
|
|
|
|
ipgroup=ipgroup,
|
|
|
|
meta=metadata,
|
|
|
|
files=files)
|
|
|
|
print_dict(server._info)
|
|
|
|
|
2011-05-30 18:04:13 -07:00
|
|
|
@arg('--flavor',
|
|
|
|
default=None,
|
|
|
|
metavar='<flavor>',
|
|
|
|
help="Flavor ID (see 'novaclient flavors'). "\
|
|
|
|
"Defaults to 256MB RAM instance.")
|
|
|
|
@arg('--image',
|
|
|
|
default=None,
|
|
|
|
metavar='<image>',
|
|
|
|
help="Image ID (see 'novaclient images'). "\
|
|
|
|
"Defaults to Ubuntu 10.04 LTS.")
|
|
|
|
@arg('--ipgroup',
|
|
|
|
default=None,
|
|
|
|
metavar='<group>',
|
|
|
|
help="IP group name or ID (see 'novaclient ipgroup-list').")
|
|
|
|
@arg('--meta',
|
|
|
|
metavar="<key=value>",
|
|
|
|
action='append',
|
|
|
|
default=[],
|
|
|
|
help="Record arbitrary key/value metadata. "\
|
|
|
|
"May be give multiple times.")
|
|
|
|
@arg('--file',
|
|
|
|
metavar="<dst-path=src-path>",
|
|
|
|
action='append',
|
|
|
|
dest='files',
|
|
|
|
default=[],
|
|
|
|
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
|
|
|
"on the new server. You may store up to 5 files.")
|
|
|
|
@arg('--key',
|
|
|
|
metavar='<path>',
|
|
|
|
nargs='?',
|
|
|
|
const=AUTO_KEY,
|
|
|
|
help="Key the server with an SSH keypair. "\
|
|
|
|
"Looks in ~/.ssh for a key, "\
|
|
|
|
"or takes an explicit <path> to one.")
|
|
|
|
@arg('--reservation_id',
|
|
|
|
default=None,
|
|
|
|
metavar='<reservation_id>',
|
|
|
|
help="Reservation ID (a UUID). "\
|
|
|
|
"If unspecified will be generated by the server.")
|
2011-06-20 16:26:57 +00:00
|
|
|
@arg('--min_instances',
|
2011-06-23 22:39:10 -07:00
|
|
|
default=None,
|
|
|
|
type=int,
|
2011-06-20 16:26:57 +00:00
|
|
|
metavar='<number>',
|
|
|
|
help="The minimum number of instances to build. "\
|
|
|
|
"Defaults to 1.")
|
|
|
|
@arg('--max_instances',
|
|
|
|
default=None,
|
2011-06-23 22:39:10 -07:00
|
|
|
type=int,
|
2011-06-20 16:26:57 +00:00
|
|
|
metavar='<number>',
|
|
|
|
help="The maximum number of instances to build. "\
|
|
|
|
"Defaults to 'min_instances' setting.")
|
2011-05-30 18:04:13 -07:00
|
|
|
@arg('name', metavar='<name>', help='Name for the new server')
|
|
|
|
def do_zone_boot(self, args):
|
|
|
|
"""Boot a new server, potentially across Zones."""
|
|
|
|
reservation_id = args.reservation_id
|
2011-06-20 19:16:15 -07:00
|
|
|
min_count = args.min_instances
|
|
|
|
max_count = args.max_instances
|
2011-06-20 16:26:57 +00:00
|
|
|
name, image, flavor, ipgroup, metadata, \
|
|
|
|
files, reservation_id, min_count, max_count = \
|
2011-05-30 18:04:13 -07:00
|
|
|
self._boot(args,
|
2011-06-20 19:16:15 -07:00
|
|
|
reservation_id=reservation_id,
|
|
|
|
min_count=min_count,
|
|
|
|
max_count=max_count)
|
2011-05-30 18:04:13 -07:00
|
|
|
|
|
|
|
reservation_id = self.cs.zones.boot(args.name, image, flavor,
|
|
|
|
ipgroup=ipgroup,
|
|
|
|
meta=metadata,
|
|
|
|
files=files,
|
2011-06-20 16:26:57 +00:00
|
|
|
reservation_id=reservation_id,
|
|
|
|
min_count=min_count,
|
|
|
|
max_count=max_count)
|
2011-05-30 18:04:13 -07:00
|
|
|
print "Reservation ID=", reservation_id
|
|
|
|
|
2011-04-08 15:36:39 -03:00
|
|
|
def _translate_flavor_keys(self, collection):
|
|
|
|
convert = [('ram', 'memory_mb'), ('disk', 'local_gb')]
|
|
|
|
for item in collection:
|
|
|
|
keys = item.__dict__.keys()
|
|
|
|
for from_key, to_key in convert:
|
|
|
|
if from_key in keys and to_key not in keys:
|
|
|
|
setattr(item, to_key, item._info[from_key])
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
def do_flavor_list(self, args):
|
|
|
|
"""Print a list of available 'flavors' (sizes of servers)."""
|
2011-04-08 15:36:39 -03:00
|
|
|
flavors = self.cs.flavors.list()
|
|
|
|
self._translate_flavor_keys(flavors)
|
|
|
|
print_list(flavors, [
|
2011-02-16 11:10:13 -06:00
|
|
|
'ID',
|
|
|
|
'Name',
|
|
|
|
'Memory_MB',
|
|
|
|
'Swap',
|
|
|
|
'Local_GB',
|
|
|
|
'VCPUs',
|
|
|
|
'RXTX_Quota',
|
|
|
|
'RXTX_Cap'])
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
def do_image_list(self, args):
|
|
|
|
"""Print a list of available images to boot from."""
|
|
|
|
print_list(self.cs.images.list(), ['ID', 'Name', 'Status'])
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
2011-06-24 14:48:14 -05:00
|
|
|
@arg('name', metavar='<name>', help='Name of backup or snapshot.')
|
|
|
|
@arg('--image-type',
|
|
|
|
metavar='<backup|snapshot>',
|
|
|
|
default='snapshot',
|
|
|
|
help='type of image (default: snapshot)')
|
|
|
|
@arg('--backup-type',
|
|
|
|
metavar='<daily|weekly>',
|
|
|
|
default=None,
|
|
|
|
help='type of backup')
|
2011-06-24 12:24:26 -05:00
|
|
|
@arg('--rotation',
|
|
|
|
default=None,
|
2011-06-24 13:22:20 -05:00
|
|
|
type=int,
|
2011-06-24 12:24:26 -05:00
|
|
|
metavar='<rotation>',
|
2011-06-24 14:48:14 -05:00
|
|
|
help="Number of backups to retain. Used for backup image_type.")
|
2011-01-25 14:01:22 -06:00
|
|
|
def do_image_create(self, args):
|
|
|
|
"""Create a new image by taking a snapshot of a running server."""
|
|
|
|
server = self._find_server(args.server)
|
2011-06-24 14:48:14 -05:00
|
|
|
image = self.cs.images.create(server, args.name,
|
|
|
|
image_type=args.image_type,
|
|
|
|
backup_type=args.backup_type,
|
|
|
|
rotation=args.rotation)
|
2011-01-25 14:01:22 -06:00
|
|
|
print_dict(image._info)
|
|
|
|
|
|
|
|
@arg('image', metavar='<image>', help='Name or ID of image.')
|
|
|
|
def do_image_delete(self, args):
|
|
|
|
"""
|
|
|
|
Delete an image.
|
|
|
|
|
2011-02-09 11:16:51 -04:00
|
|
|
It should go without saying, but you can only delete images you
|
2011-01-25 14:01:22 -06:00
|
|
|
created.
|
|
|
|
"""
|
|
|
|
image = self._find_image(args.image)
|
|
|
|
image.delete()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('group', metavar='<group>', help='Name or ID of group.')
|
|
|
|
@arg('address', metavar='<address>', help='IP address to share.')
|
|
|
|
def do_ip_share(self, args):
|
|
|
|
"""Share an IP address from the given IP group onto a server."""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
group = self._find_ipgroup(args.group)
|
|
|
|
server.share_ip(group, args.address)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('address', metavar='<address>',
|
|
|
|
help='Shared IP address to remove from the server.')
|
|
|
|
def do_ip_unshare(self, args):
|
|
|
|
"""Stop sharing an given address with a server."""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
server.unshare_ip(args.address)
|
|
|
|
|
|
|
|
def do_ipgroup_list(self, args):
|
|
|
|
"""Show IP groups."""
|
|
|
|
def pretty_server_list(ipgroup):
|
|
|
|
return ", ".join(self.cs.servers.get(id).name
|
|
|
|
for id in ipgroup.servers)
|
|
|
|
|
|
|
|
print_list(self.cs.ipgroups.list(),
|
|
|
|
fields=['ID', 'Name', 'Server List'],
|
|
|
|
formatters={'Server List': pretty_server_list})
|
|
|
|
|
|
|
|
@arg('group', metavar='<group>', help='Name or ID of group.')
|
|
|
|
def do_ipgroup_show(self, args):
|
|
|
|
"""Show details about a particular IP group."""
|
|
|
|
group = self._find_ipgroup(args.group)
|
|
|
|
print_dict(group._info)
|
|
|
|
|
|
|
|
@arg('name', metavar='<name>', help='What to name this new group.')
|
|
|
|
@arg('server', metavar='<server>', nargs='?',
|
|
|
|
help='Server (name or ID) to make a member of this new group.')
|
|
|
|
def do_ipgroup_create(self, args):
|
|
|
|
"""Create a new IP group."""
|
|
|
|
if args.server:
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
else:
|
|
|
|
server = None
|
|
|
|
group = self.cs.ipgroups.create(args.name, server)
|
|
|
|
print_dict(group._info)
|
|
|
|
|
|
|
|
@arg('group', metavar='<group>', help='Name or ID of group.')
|
|
|
|
def do_ipgroup_delete(self, args):
|
|
|
|
"""Delete an IP group."""
|
|
|
|
self._find_ipgroup(args.group).delete()
|
|
|
|
|
2011-06-23 09:50:20 -07:00
|
|
|
@arg('--fixed_ip',
|
|
|
|
dest='fixed_ip',
|
|
|
|
metavar='<fixed_ip>',
|
|
|
|
default=None,
|
|
|
|
help='Only match against fixed IP.')
|
2011-06-01 11:50:32 -07:00
|
|
|
@arg('--reservation_id',
|
|
|
|
dest='reservation_id',
|
|
|
|
metavar='<reservation_id>',
|
|
|
|
default=None,
|
|
|
|
help='Only return instances that match reservation_id.')
|
2011-06-23 09:50:20 -07:00
|
|
|
@arg('--recurse_zones',
|
|
|
|
dest='recurse_zones',
|
2011-06-23 15:07:05 -07:00
|
|
|
metavar='<0|1>',
|
|
|
|
nargs='?',
|
|
|
|
type=int,
|
|
|
|
const=1,
|
|
|
|
default=0,
|
|
|
|
help='Recurse through all zones if set.')
|
2011-07-12 02:12:03 -07:00
|
|
|
@arg('--ip',
|
|
|
|
dest='ip',
|
|
|
|
metavar='<ip_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by IP address')
|
|
|
|
@arg('--ip6',
|
|
|
|
dest='ip6',
|
|
|
|
metavar='<ip6_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by IPv6 address')
|
|
|
|
@arg('--server_name',
|
|
|
|
dest='server_name',
|
|
|
|
metavar='<name_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by server name')
|
|
|
|
@arg('--name',
|
|
|
|
dest='display_name',
|
|
|
|
metavar='<name_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by display name')
|
|
|
|
@arg('--instance_name',
|
|
|
|
dest='name',
|
|
|
|
metavar='<name_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by instance name')
|
2011-01-25 14:01:22 -06:00
|
|
|
def do_list(self, args):
|
|
|
|
"""List active servers."""
|
2011-06-23 15:07:05 -07:00
|
|
|
recurse_zones = args.recurse_zones
|
2011-07-12 02:12:03 -07:00
|
|
|
search_opts = {
|
|
|
|
'reservation_id': args.reservation_id,
|
|
|
|
'fixed_ip': args.fixed_ip,
|
|
|
|
'recurse_zones': recurse_zones,
|
|
|
|
'ip': args.ip,
|
|
|
|
'ip6': args.ip6,
|
|
|
|
'name': args.name,
|
|
|
|
'server_name': args.server_name,
|
|
|
|
'display_name': args.display_name}
|
2011-06-23 09:50:20 -07:00
|
|
|
if recurse_zones:
|
|
|
|
to_print = ['UUID', 'Name', 'Status', 'Public IP', 'Private IP']
|
|
|
|
else:
|
|
|
|
to_print = ['ID', 'Name', 'Status', 'Public IP', 'Private IP']
|
2011-07-12 02:12:03 -07:00
|
|
|
print_list(self.cs.servers.list(search_opts=search_opts),
|
|
|
|
to_print)
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
@arg('--hard',
|
|
|
|
dest='reboot_type',
|
|
|
|
action='store_const',
|
2011-02-26 05:04:40 -04:00
|
|
|
const=novaclient.REBOOT_HARD,
|
|
|
|
default=novaclient.REBOOT_SOFT,
|
2011-01-25 14:01:22 -06:00
|
|
|
help='Perform a hard reboot (instead of a soft one).')
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_reboot(self, args):
|
|
|
|
"""Reboot a server."""
|
|
|
|
self._find_server(args.server).reboot(args.reboot_type)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('image', metavar='<image>', help="Name or ID of new image.")
|
|
|
|
def do_rebuild(self, args):
|
|
|
|
"""Shutdown, re-image, and re-boot a server."""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
image = self._find_image(args.image)
|
|
|
|
server.rebuild(image)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name (old name) or ID of server.')
|
|
|
|
@arg('name', metavar='<name>', help='New name for the server.')
|
|
|
|
def do_rename(self, args):
|
|
|
|
"""Rename a server."""
|
|
|
|
self._find_server(args.server).update(name=args.name)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('flavor', metavar='<flavor>', help="Name or ID of new flavor.")
|
|
|
|
def do_resize(self, args):
|
|
|
|
"""Resize a server."""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
flavor = self._find_flavor(args.flavor)
|
|
|
|
server.resize(flavor)
|
|
|
|
|
2011-07-07 13:33:17 -05:00
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_migrate(self, args):
|
|
|
|
"""Migrate a server."""
|
|
|
|
self._find_server(args.server).migrate()
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_pause(self, args):
|
|
|
|
"""Pause a server."""
|
|
|
|
self._find_server(args.server).pause()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_unpause(self, args):
|
|
|
|
"""Unpause a server."""
|
|
|
|
self._find_server(args.server).unpause()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_suspend(self, args):
|
|
|
|
"""Suspend a server."""
|
|
|
|
self._find_server(args.server).suspend()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_resume(self, args):
|
|
|
|
"""Resume a server."""
|
|
|
|
self._find_server(args.server).resume()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_rescue(self, args):
|
|
|
|
"""Rescue a server."""
|
|
|
|
self._find_server(args.server).rescue()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_unrescue(self, args):
|
|
|
|
"""Unrescue a server."""
|
|
|
|
self._find_server(args.server).unrescue()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_diagnostics(self, args):
|
|
|
|
"""Retrieve server diagnostics."""
|
|
|
|
print_dict(self.cs.servers.diagnostics(args.server)[1])
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_actions(self, args):
|
|
|
|
"""Retrieve server actions."""
|
|
|
|
print_list(
|
|
|
|
self.cs.servers.actions(args.server),
|
|
|
|
["Created_At", "Action", "Error"])
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_resize_confirm(self, args):
|
|
|
|
"""Confirm a previous resize."""
|
|
|
|
self._find_server(args.server).confirm_resize()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_resize_revert(self, args):
|
|
|
|
"""Revert a previous resize (and return to the previous VM)."""
|
|
|
|
self._find_server(args.server).revert_resize()
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_root_password(self, args):
|
|
|
|
"""
|
|
|
|
Change the root password for a server.
|
|
|
|
"""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
p1 = getpass.getpass('New password: ')
|
|
|
|
p2 = getpass.getpass('Again: ')
|
|
|
|
if p1 != p2:
|
|
|
|
raise CommandError("Passwords do not match.")
|
|
|
|
server.update(password=p1)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_show(self, args):
|
|
|
|
"""Show details about the given server."""
|
2011-06-23 09:50:20 -07:00
|
|
|
s = self._find_server(args.server)
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
info = s._info.copy()
|
|
|
|
addresses = info.pop('addresses')
|
|
|
|
for addrtype in addresses:
|
|
|
|
info['%s ip' % addrtype] = ', '.join(addresses[addrtype])
|
|
|
|
|
2011-05-19 06:58:41 -07:00
|
|
|
flavorId = info.get('flavorId', None)
|
|
|
|
if flavorId:
|
2011-03-16 05:43:50 -07:00
|
|
|
info['flavor'] = self._find_flavor(info.pop('flavorId')).name
|
2011-05-19 06:58:41 -07:00
|
|
|
imageId = info.get('imageId', None)
|
|
|
|
if imageId:
|
2011-03-16 05:43:50 -07:00
|
|
|
info['image'] = self._find_image(info.pop('imageId')).name
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
print_dict(info)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_delete(self, args):
|
|
|
|
"""Immediately shut down and delete a server."""
|
|
|
|
self._find_server(args.server).delete()
|
|
|
|
|
2011-02-09 18:31:22 -04:00
|
|
|
# --zone_username is required since --username is already used.
|
2011-02-15 11:34:06 -04:00
|
|
|
@arg('zone', metavar='<zone_id>', help='ID of the zone', default=None)
|
2011-02-09 18:31:22 -04:00
|
|
|
@arg('--api_url', dest='api_url', default=None, help='New URL.')
|
|
|
|
@arg('--zone_username', dest='zone_username', default=None,
|
|
|
|
help='New zone username.')
|
|
|
|
@arg('--password', dest='password', default=None, help='New password.')
|
2011-06-21 04:54:01 -07:00
|
|
|
@arg('--weight_offset', dest='weight_offset', default=None,
|
|
|
|
help='Child Zone weight offset.')
|
|
|
|
@arg('--weight_scale', dest='weight_scale', default=None,
|
|
|
|
help='Child Zone weight scale.')
|
2011-02-09 11:16:51 -04:00
|
|
|
def do_zone(self, args):
|
2011-02-15 11:34:06 -04:00
|
|
|
"""Show or edit a child zone. No zone arg for this zone."""
|
2011-02-09 11:16:51 -04:00
|
|
|
zone = self.cs.zones.get(args.zone)
|
|
|
|
|
|
|
|
# If we have some flags, update the zone
|
|
|
|
zone_delta = {}
|
2011-02-09 18:31:22 -04:00
|
|
|
if args.api_url:
|
|
|
|
zone_delta['api_url'] = args.api_url
|
|
|
|
if args.zone_username:
|
|
|
|
zone_delta['username'] = args.zone_username
|
|
|
|
if args.password:
|
|
|
|
zone_delta['password'] = args.password
|
2011-06-21 04:54:01 -07:00
|
|
|
if args.weight_offset:
|
|
|
|
zone_delta['weight_offset'] = args.weight_offset
|
|
|
|
if args.weight_scale:
|
|
|
|
zone_delta['weight_scale'] = args.weight_scale
|
2011-02-09 11:16:51 -04:00
|
|
|
if zone_delta:
|
|
|
|
zone.update(**zone_delta)
|
|
|
|
else:
|
|
|
|
print_dict(zone._info)
|
|
|
|
|
2011-02-15 11:16:30 -08:00
|
|
|
def do_zone_info(self, args):
|
|
|
|
"""Get this zones name and capabilities."""
|
|
|
|
zone = self.cs.zones.info()
|
|
|
|
print_dict(zone._info)
|
|
|
|
|
2011-02-09 18:31:22 -04:00
|
|
|
@arg('api_url', metavar='<api_url>', help="URL for the Zone's API")
|
|
|
|
@arg('zone_username', metavar='<zone_username>',
|
|
|
|
help='Authentication username.')
|
|
|
|
@arg('password', metavar='<password>', help='Authentication password.')
|
2011-06-21 04:54:01 -07:00
|
|
|
@arg('weight_offset', metavar='<weight_offset>',
|
2011-06-21 12:11:49 -07:00
|
|
|
help='Child Zone weight offset (typically 0.0).')
|
2011-06-21 04:54:01 -07:00
|
|
|
@arg('weight_scale', metavar='<weight_scale>',
|
2011-06-21 12:11:49 -07:00
|
|
|
help='Child Zone weight scale (typically 1.0).')
|
2011-02-09 11:16:51 -04:00
|
|
|
def do_zone_add(self, args):
|
|
|
|
"""Add a new child zone."""
|
2011-02-09 18:31:22 -04:00
|
|
|
zone = self.cs.zones.create(args.api_url, args.zone_username,
|
2011-06-21 04:54:01 -07:00
|
|
|
args.password, args.weight_offset,
|
|
|
|
args.weight_scale)
|
2011-02-09 11:16:51 -04:00
|
|
|
print_dict(zone._info)
|
|
|
|
|
2011-07-07 22:51:27 +00:00
|
|
|
@arg('zone', metavar='<zone>', help='Name or ID of the zone')
|
2011-02-09 11:16:51 -04:00
|
|
|
def do_zone_delete(self, args):
|
|
|
|
"""Delete a zone."""
|
|
|
|
self.cs.zones.delete(args.zone)
|
|
|
|
|
|
|
|
def do_zone_list(self, args):
|
|
|
|
"""List the children of a zone."""
|
2011-06-21 12:11:49 -07:00
|
|
|
print_list(self.cs.zones.list(), ['ID', 'Name', 'Is Active', \
|
|
|
|
'API URL', 'Weight Offset', 'Weight Scale'])
|
2011-02-09 11:16:51 -04:00
|
|
|
|
2011-07-08 10:54:43 -07:00
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('network_id', metavar='<network_id>', help='Network ID.')
|
|
|
|
def do_add_fixed_ip(self, args):
|
|
|
|
"""Add new IP address to network."""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
server.add_fixed_ip(args.network_id)
|
|
|
|
|
|
|
|
@arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@arg('address', metavar='<address>', help='IP Address.')
|
|
|
|
def do_remove_fixed_ip(self, args):
|
|
|
|
"""Remove an IP address from a server."""
|
|
|
|
server = self._find_server(args.server)
|
|
|
|
server.remove_fixed_ip(args.address)
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
def _find_server(self, server):
|
|
|
|
"""Get a server by name or ID."""
|
|
|
|
return self._find_resource(self.cs.servers, server)
|
|
|
|
|
|
|
|
def _find_ipgroup(self, group):
|
|
|
|
"""Get an IP group by name or ID."""
|
|
|
|
return self._find_resource(self.cs.ipgroups, group)
|
|
|
|
|
|
|
|
def _find_image(self, image):
|
|
|
|
"""Get an image by name or ID."""
|
|
|
|
return self._find_resource(self.cs.images, image)
|
|
|
|
|
|
|
|
def _find_flavor(self, flavor):
|
|
|
|
"""Get a flavor by name, ID, or RAM size."""
|
|
|
|
try:
|
|
|
|
return self._find_resource(self.cs.flavors, flavor)
|
2011-02-26 05:04:40 -04:00
|
|
|
except novaclient.NotFound:
|
2011-01-25 14:01:22 -06:00
|
|
|
return self.cs.flavors.find(ram=flavor)
|
|
|
|
|
|
|
|
def _find_resource(self, manager, name_or_id):
|
|
|
|
"""Helper for the _find_* methods."""
|
|
|
|
try:
|
|
|
|
if isinstance(name_or_id, int) or name_or_id.isdigit():
|
|
|
|
return manager.get(int(name_or_id))
|
2011-06-09 10:38:01 -05:00
|
|
|
|
|
|
|
try:
|
|
|
|
uuid.UUID(name_or_id)
|
|
|
|
return manager.get(name_or_id)
|
|
|
|
except ValueError:
|
2011-01-25 14:01:22 -06:00
|
|
|
return manager.find(name=name_or_id)
|
2011-02-26 05:04:40 -04:00
|
|
|
except novaclient.NotFound:
|
2011-01-25 14:01:22 -06:00
|
|
|
raise CommandError("No %s with a name or ID of '%s' exists." %
|
|
|
|
(manager.resource_class.__name__.lower(), name_or_id))
|
|
|
|
|
|
|
|
|
|
|
|
# I'm picky about my shell help.
|
2011-02-08 09:27:22 -04:00
|
|
|
class OpenStackHelpFormatter(argparse.HelpFormatter):
|
2011-01-25 14:01:22 -06:00
|
|
|
def start_section(self, heading):
|
|
|
|
# Title-case the headings
|
|
|
|
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
2011-02-08 09:27:22 -04:00
|
|
|
super(OpenStackHelpFormatter, self).start_section(heading)
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
|
|
|
|
# Helpers
|
|
|
|
def print_list(objs, fields, formatters={}):
|
|
|
|
pt = prettytable.PrettyTable([f for f in fields], caching=False)
|
|
|
|
pt.aligns = ['l' for f in fields]
|
|
|
|
|
|
|
|
for o in objs:
|
|
|
|
row = []
|
|
|
|
for field in fields:
|
|
|
|
if field in formatters:
|
|
|
|
row.append(formatters[field](o))
|
|
|
|
else:
|
2011-07-12 14:41:56 -07:00
|
|
|
field_name = field.lower().replace(' ', '_')
|
|
|
|
data = getattr(o, field_name, '')
|
|
|
|
row.append(data)
|
2011-01-25 14:01:22 -06:00
|
|
|
pt.add_row(row)
|
|
|
|
|
|
|
|
pt.printt(sortby=fields[0])
|
|
|
|
|
|
|
|
|
|
|
|
def print_dict(d):
|
|
|
|
pt = prettytable.PrettyTable(['Property', 'Value'], caching=False)
|
|
|
|
pt.aligns = ['l', 'l']
|
|
|
|
[pt.add_row(list(r)) for r in d.iteritems()]
|
|
|
|
pt.printt(sortby='Property')
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
try:
|
2011-02-08 09:27:22 -04:00
|
|
|
OpenStackShell().main(sys.argv[1:])
|
2011-02-24 13:54:10 -04:00
|
|
|
|
|
|
|
except Exception, e:
|
|
|
|
if httplib2.debuglevel == 1:
|
|
|
|
raise # dump stack.
|
|
|
|
else:
|
|
|
|
print >> sys.stderr, e
|
2011-01-25 14:01:22 -06:00
|
|
|
sys.exit(1)
|