diff --git a/uncloud/cli/commands/helper.py b/uncloud/cli/commands/helper.py deleted file mode 100755 index 6bef690..0000000 --- a/uncloud/cli/commands/helper.py +++ /dev/null @@ -1,62 +0,0 @@ -import json -import binascii -import click -import requests - -from os.path import join as join_path - -from pyotp import TOTP -from uncloud.settings import settings - - -def load_dump_pretty(content): - if isinstance(content, bytes): - content = content.decode('utf-8') - parsed = json.loads(content) - return json.dumps(parsed, indent=4, sort_keys=True) - - -def make_request(*args, data=None, request_method=requests.post): - r = request_method( - join_path(settings['client']['api_server'], *args), json=data - ) - try: - print(load_dump_pretty(r.content)) - except Exception: - print('Error occurred while getting output from api server.') - - -def get_token(_, param, value): - if value is not None: - try: - token = TOTP(value).now() - except binascii.Error: - raise click.BadParameter('') - else: - param.name = 'token' - return token - - -def add_otp_options(f): - options = [ - click.option('--name', show_default='name mentioned in config file.', prompt=True), - click.option('--realm', show_default='realm mentioned in config file.', prompt=True), - click.option('--seed', callback=get_token, show_default='seed mentioned in config file', - prompt=True) - ] - - for opt in reversed(options): - f = opt(f) - - return f - - -def add_vm_options(f): - options = [ - click.option('--vm-name', required=True), - click.option('--action', required=True, default=f.__name__) - ] - for opt in reversed(options): - f = opt(f) - - return f diff --git a/uncloud/cli/commands/host.py b/uncloud/cli/commands/host.py deleted file mode 100755 index 29ee417..0000000 --- a/uncloud/cli/commands/host.py +++ /dev/null @@ -1,31 +0,0 @@ -import click -import requests - -from .helper import add_otp_options, make_request - - -@click.group() -def host(): - pass - - -@host.command('create') -@add_otp_options -@click.option('--hostname', required=True) -@click.option('--cpu', required=True, type=int) -@click.option('--ram', required=True) -@click.option('--os-ssd', required=True) -@click.option('--hdd', default=list(), multiple=True) -def create(**kwargs): - kwargs['specs'] = { - 'cpu': kwargs.pop('cpu'), - 'ram': kwargs.pop('ram'), - 'os-ssd': kwargs.pop('os_ssd'), - 'hdd': kwargs.pop('hdd') - } - make_request('host', 'create', data=kwargs) - - -@host.command('list') -def list_host(): - make_request('host', 'list', request_method=requests.get) diff --git a/uncloud/cli/commands/image.py b/uncloud/cli/commands/image.py deleted file mode 100755 index 6f9fe86..0000000 --- a/uncloud/cli/commands/image.py +++ /dev/null @@ -1,24 +0,0 @@ -import click -import requests - -from .helper import make_request - - -@click.group() -def image(): - pass - - -@image.command('list') -@click.option('--public', is_flag=True) -def _list(public): - if public: - make_request('image', 'list-public', request_method=requests.get) - - -@image.command('create-from-file') -@click.option('--name', required=True) -@click.option('--uuid', required=True) -@click.option('--image-store-name', 'image_store', required=True) -def create_from_file(**kwargs): - make_request('image', 'create', data=kwargs) diff --git a/uncloud/cli/commands/network.py b/uncloud/cli/commands/network.py deleted file mode 100644 index 81e22d5..0000000 --- a/uncloud/cli/commands/network.py +++ /dev/null @@ -1,17 +0,0 @@ -import click - -from .helper import add_otp_options, make_request - - -@click.group() -def network(): - pass - - -@network.command('create') -@add_otp_options -@click.option('--network-name', required=True) -@click.option('--network-type', 'type', required=True) -@click.option('--user', required=True, type=bool, default=False) -def create(**kwargs): - make_request('network', 'create', data=kwargs) diff --git a/uncloud/cli/commands/user.py b/uncloud/cli/commands/user.py deleted file mode 100755 index 2116520..0000000 --- a/uncloud/cli/commands/user.py +++ /dev/null @@ -1,48 +0,0 @@ -import click - -from .helper import add_otp_options, make_request - - -@click.group() -def user(): - pass - - -@user.command('files') -@add_otp_options -def list_files(**kwargs): - make_request('user', 'files', data=kwargs) - - -@user.command('vms') -@add_otp_options -def list_vms(**kwargs): - make_request('user', 'vms', data=kwargs) - - -@user.command('networks') -@add_otp_options -def list_networks(**kwargs): - make_request('user', 'network', data=kwargs) - - -@user.command('add-ssh') -@add_otp_options -@click.option('--key-name', required=True) -@click.option('--key', required=True) -def add_ssh(**kwargs): - make_request('user', 'add-ssh', data=kwargs) - - -@user.command('remove-ssh') -@add_otp_options -@click.option('--key-name', required=True) -def remove_ssh(**kwargs): - make_request('user', 'remove-ssh', data=kwargs) - - -@user.command('get-ssh') -@add_otp_options -@click.option('--key-name', default='') -def get_ssh(**kwargs): - make_request('user', 'get-ssh', data=kwargs) diff --git a/uncloud/cli/commands/vm.py b/uncloud/cli/commands/vm.py deleted file mode 100755 index 8527ac2..0000000 --- a/uncloud/cli/commands/vm.py +++ /dev/null @@ -1,63 +0,0 @@ -import click - -from .helper import add_otp_options, make_request, add_vm_options - - -@click.group() -def vm(): - pass - - -@vm.command('create') -@add_otp_options -@add_vm_options -@click.option('--cpu', required=True, type=int) -@click.option('--ram', required=True) -@click.option('--os-ssd', required=True) -@click.option('--hdd', default=list(), multiple=True) -@click.option('--image', required=True) -@click.option('--network', default=list(), multiple=True) -def create(**kwargs): - kwargs['specs'] = { - 'cpu': kwargs.pop('cpu'), - 'ram': kwargs.pop('ram'), - 'os-ssd': kwargs.pop('os_ssd'), - 'hdd': kwargs.pop('hdd') - } - make_request('vm', kwargs.pop('action'), data=kwargs) - - -@vm.command('start') -@add_otp_options -@add_vm_options -def start(**kwargs): - make_request('vm', 'action', data=kwargs) - - -@vm.command('stop') -@add_otp_options -@add_vm_options -def stop(**kwargs): - make_request('vm', 'action', data=kwargs) - - -@vm.command('delete') -@add_otp_options -@add_vm_options -def delete(**kwargs): - make_request('vm', 'action', data=kwargs) - - -@vm.command('status') -@add_otp_options -@click.option('--vm-name', required=True) -def status(**kwargs): - make_request('vm', 'status', data=kwargs) - - -@vm.command('migrate') -@add_otp_options -@click.option('--vm-name', required=True) -@click.option('--destination', required=True) -def vm_migration(**kwargs): - make_request('vm', 'migrate', data=kwargs) diff --git a/uncloud/cli/helper.py b/uncloud/cli/helper.py new file mode 100644 index 0000000..bdcce78 --- /dev/null +++ b/uncloud/cli/helper.py @@ -0,0 +1,42 @@ +import requests +import json +import argparse +import binascii + +from pyotp import TOTP +from os.path import join as join_path +from uncloud.settings import settings + + +def get_otp_parser(): + otp_parser = argparse.ArgumentParser('otp') + otp_parser.add_argument('--name', default=settings['client']['name']) + otp_parser.add_argument('--realm', default=settings['client']['realm']) + otp_parser.add_argument('--seed', type=get_token, default=settings['client']['seed'], + dest='token', metavar='SEED') + return otp_parser + + +def load_dump_pretty(content): + if isinstance(content, bytes): + content = content.decode('utf-8') + parsed = json.loads(content) + return json.dumps(parsed, indent=4, sort_keys=True) + + +def make_request(*args, data=None, request_method=requests.post): + r = request_method(join_path(settings['client']['api_server'], *args), json=data) + try: + print(load_dump_pretty(r.content)) + except Exception: + print('Error occurred while getting output from api server.') + + +def get_token(seed): + if seed is not None: + try: + token = TOTP(seed).now() + except binascii.Error: + raise argparse.ArgumentTypeError('Invalid seed') + else: + return token diff --git a/uncloud/cli/host.py b/uncloud/cli/host.py new file mode 100644 index 0000000..e912567 --- /dev/null +++ b/uncloud/cli/host.py @@ -0,0 +1,45 @@ +import requests + +from uncloud.cli.helper import make_request, get_otp_parser +from uncloud.common.parser import BaseParser + + +class HostParser(BaseParser): + def __init__(self): + super().__init__('host') + + def create(self, **kwargs): + p = self.subparser.add_parser('create', parents=[get_otp_parser()], **kwargs) + p.add_argument('--hostname', required=True) + p.add_argument('--cpu', required=True, type=int) + p.add_argument('--ram', required=True) + p.add_argument('--os-ssd', required=True) + p.add_argument('--hdd', default=list()) + + def list(self, **kwargs): + self.subparser.add_parser('list', **kwargs) + + +parser = HostParser() +arg_parser = parser.arg_parser + + +def main(**kwargs): + subcommand = kwargs.pop('host_subcommand') + if not subcommand: + arg_parser.print_help() + else: + request_method = requests.post + data = None + if subcommand == 'create': + kwargs['specs'] = { + 'cpu': kwargs.pop('cpu'), + 'ram': kwargs.pop('ram'), + 'os-ssd': kwargs.pop('os_ssd'), + 'hdd': kwargs.pop('hdd') + } + data = kwargs + elif subcommand == 'list': + request_method = requests.get + + make_request('host', subcommand, data=data, request_method=request_method) diff --git a/uncloud/cli/image.py b/uncloud/cli/image.py new file mode 100644 index 0000000..3db9577 --- /dev/null +++ b/uncloud/cli/image.py @@ -0,0 +1,38 @@ +import requests + +from uncloud.cli.helper import make_request +from uncloud.common.parser import BaseParser + + +class ImageParser(BaseParser): + def __init__(self): + super().__init__('image') + + def create(self, **kwargs): + p = self.subparser.add_parser('create', **kwargs) + p.add_argument('--name', required=True) + p.add_argument('--uuid', required=True) + p.add_argument('--image-store-name', default='image_store') + + def list(self, **kwargs): + self.subparser.add_parser('list', add_help=False, **kwargs) + + +parser = ImageParser() +arg_parser = parser.arg_parser + + +def main(**kwargs): + subcommand = kwargs.pop('image_subcommand') + if not subcommand: + arg_parser.print_help() + else: + data = None + request_method = requests.post + if subcommand == 'list': + subcommand = 'list-public' + request_method = requests.get + elif subcommand == 'create': + data = kwargs + + make_request('image', subcommand, data=data, request_method=request_method) diff --git a/uncloud/cli/main.py b/uncloud/cli/main.py index 7d625c4..7f5e367 100644 --- a/uncloud/cli/main.py +++ b/uncloud/cli/main.py @@ -1,24 +1,23 @@ #!/usr/bin/env python3 -import click +import argparse +import importlib -from uncloud.cli.commands.vm import vm -from uncloud.cli.commands.user import user -from uncloud.cli.commands.host import host -from uncloud.cli.commands.image import image -from uncloud.cli.commands.network import network +arg_parser = argparse.ArgumentParser('cli', add_help=False) +subparser = arg_parser.add_subparsers(dest='subcommand') + +for component in ['user', 'host', 'image', 'network', 'vm']: + module = importlib.import_module('uncloud.cli.{}'.format(component)) + parser = getattr(module, 'arg_parser') + subparser.add_parser(name=parser.prog, parents=[parser]) -@click.group() -def cli(): - pass - - -cli.add_command(vm) -cli.add_command(user) -cli.add_command(image) -cli.add_command(host) -cli.add_command(network) - -if __name__ == '__main__': - cli() +def main(**kwargs): + if not kwargs['subcommand']: + arg_parser.print_help() + else: + name = kwargs.pop('subcommand') + kwargs.pop('debug') + mod = importlib.import_module('uncloud.cli.{}'.format(name)) + _main = getattr(mod, 'main') + _main(**kwargs) diff --git a/uncloud/cli/network.py b/uncloud/cli/network.py new file mode 100644 index 0000000..33e41a9 --- /dev/null +++ b/uncloud/cli/network.py @@ -0,0 +1,32 @@ +import requests + +from uncloud.cli.helper import make_request, get_otp_parser +from uncloud.common.parser import BaseParser + + +class NetworkParser(BaseParser): + def __init__(self): + super().__init__('network') + + def create(self, **kwargs): + p = self.subparser.add_parser('create', add_help=False, parents=[get_otp_parser()], **kwargs) + p.add_argument('--network-name', required=True) + p.add_argument('--network-type', required=True, dest='type') + p.add_argument('--user', action='store_true') + + +parser = NetworkParser() +arg_parser = parser.arg_parser + + +def main(**kwargs): + subcommand = kwargs.pop('network_subcommand') + if not subcommand: + arg_parser.print_help() + else: + data = None + request_method = requests.post + if subcommand == 'create': + data = kwargs + + make_request('network', subcommand, data=data, request_method=request_method) diff --git a/uncloud/cli/user.py b/uncloud/cli/user.py new file mode 100755 index 0000000..3a4cc4e --- /dev/null +++ b/uncloud/cli/user.py @@ -0,0 +1,41 @@ +from uncloud.cli.helper import make_request, get_otp_parser +from uncloud.common.parser import BaseParser + + +class UserParser(BaseParser): + def __init__(self): + super().__init__('user') + + def files(self, **kwargs): + self.subparser.add_parser('files', parents=[get_otp_parser()], **kwargs) + + def vms(self, **kwargs): + self.subparser.add_parser('vms', parents=[get_otp_parser()], **kwargs) + + def networks(self, **kwargs): + self.subparser.add_parser('networks', parents=[get_otp_parser()], **kwargs) + + def add_ssh(self, **kwargs): + p = self.subparser.add_parser('add-ssh', parents=[get_otp_parser()], **kwargs) + p.add_argument('--key-name', required=True) + p.add_argument('--key', required=True) + + def get_ssh(self, **kwargs): + p = self.subparser.add_parser('get-ssh', parents=[get_otp_parser()], **kwargs) + p.add_argument('--key-name', default='') + + def remove_ssh(self, **kwargs): + p = self.subparser.add_parser('remove-ssh', parents=[get_otp_parser()], **kwargs) + p.add_argument('--key-name', required=True) + + +parser = UserParser() +arg_parser = parser.arg_parser + + +def main(**kwargs): + subcommand = kwargs.pop('user_subcommand') + if not subcommand: + arg_parser.print_help() + else: + make_request('user', subcommand, data=kwargs) diff --git a/uncloud/cli/vm.py b/uncloud/cli/vm.py new file mode 100644 index 0000000..396530e --- /dev/null +++ b/uncloud/cli/vm.py @@ -0,0 +1,62 @@ +from uncloud.common.parser import BaseParser +from uncloud.cli.helper import make_request, get_otp_parser + + +class VMParser(BaseParser): + def __init__(self): + super().__init__('vm') + + def start(self, **args): + p = self.subparser.add_parser('start', parents=[get_otp_parser()], **args) + p.add_argument('--vm-name', required=True) + + def stop(self, **args): + p = self.subparser.add_parser('stop', parents=[get_otp_parser()], **args) + p.add_argument('--vm-name', required=True) + + def status(self, **args): + p = self.subparser.add_parser('status', parents=[get_otp_parser()], **args) + p.add_argument('--vm-name', required=True) + + def delete(self, **args): + p = self.subparser.add_parser('delete', parents=[get_otp_parser()], **args) + p.add_argument('--vm-name', required=True) + + def migrate(self, **args): + p = self.subparser.add_parser('migrate', parents=[get_otp_parser()], **args) + p.add_argument('--vm-name', required=True) + p.add_argument('--destination', required=True) + + def create(self, **args): + p = self.subparser.add_parser('create', parents=[get_otp_parser()], **args) + p.add_argument('--cpu', required=True) + p.add_argument('--ram', required=True) + p.add_argument('--os-ssd', required=True) + p.add_argument('--hdd', action='append', default=list()) + p.add_argument('--image', required=True) + p.add_argument('--network', action='append', default=[]) + p.add_argument('--vm-name', required=True) + + +parser = VMParser() +arg_parser = parser.arg_parser + + +def main(**kwargs): + subcommand = kwargs.pop('vm_subcommand') + if not subcommand: + arg_parser.print_help() + else: + data = kwargs + endpoint = subcommand + if subcommand in ['start', 'stop', 'delete']: + endpoint = 'action' + data['action'] = subcommand + elif subcommand == 'create': + kwargs['specs'] = { + 'cpu': kwargs.pop('cpu'), + 'ram': kwargs.pop('ram'), + 'os-ssd': kwargs.pop('os_ssd'), + 'hdd': kwargs.pop('hdd') + } + make_request('vm', endpoint, data=data) diff --git a/uncloud/cli/commands/__init__.py b/uncloud/common/parser.py old mode 100755 new mode 100644 similarity index 100% rename from uncloud/cli/commands/__init__.py rename to uncloud/common/parser.py