Пример #1
0
def main():
    ## Command Handling
    parser = argparse.ArgumentParser("linode-cli", add_help=False)
    parser.add_argument('command',
                        metavar='COMMAND',
                        nargs='?',
                        type=str,
                        help="The command to invoke in the CLI.")
    parser.add_argument('action',
                        metavar='ACTION',
                        nargs='?',
                        type=str,
                        help="The action to perform in this command.")
    parser.add_argument('--help',
                        action="store_true",
                        help="Display information about a command, action, or "
                        "the CLI overall.")
    parser.add_argument(
        '--text',
        action="store_true",
        help="Display text output with a delimiter (defaults to tabs).")
    parser.add_argument('--delimiter',
                        metavar='DELIMITER',
                        type=str,
                        help="The delimiter when displaying raw output.")
    parser.add_argument('--json',
                        action='store_true',
                        help="Display output as JSON")
    parser.add_argument('--markdown',
                        action='store_true',
                        help="Display output in Markdown format.")
    parser.add_argument('--pretty',
                        action='store_true',
                        help="If set, pretty-print JSON output")
    parser.add_argument('--no-headers',
                        action='store_true',
                        help="If set, does not display headers in output.")
    parser.add_argument(
        '--page',
        metavar='PAGE',
        type=int,
        default=1,
        help="For listing actions, specifies the page to request")
    parser.add_argument(
        '--all',
        action='store_true',
        help="If set, displays all possible columns instead of "
        "the default columns.  This may not work well on "
        "some terminals.")
    parser.add_argument(
        '--format',
        metavar='FORMAT',
        type=str,
        help="The columns to display in output.  Provide a comma-"
        "separated list of column names.")
    parser.add_argument(
        '--no-defaults',
        action='store_true',
        help="Suppress default values for arguments.  Default values "
        "are configured on initial setup or with linode-cli configure")
    parser.add_argument(
        '--as-user',
        metavar='USERNAME',
        type=str,
        help="The username to execute this command as.  This user must "
        "be configured.")
    parser.add_argument(
        '--suppress-warnings',
        action='store_true',
        help="Suppress warnings that are intended for human users. "
        "This is useful for scripting the CLI's behavior.")
    parser.add_argument('--version',
                        '-v',
                        action="store_true",
                        help="Prints version information and exits.")
    parser.add_argument('--debug',
                        action='store_true',
                        help="Enable verbose HTTP debug output")

    parsed, args = parser.parse_known_args()

    # setup cli class
    if parsed.text:
        cli.output_handler.mode = OutputMode.delimited
    elif parsed.json:
        cli.output_handler.mode = OutputMode.json
        cli.output_handler.columns = '*'
    elif parsed.markdown:
        cli.output_handler.mode = OutputMode.markdown
    if parsed.delimiter:
        cli.output_handler.delimiter = parsed.delimiter
    if parsed.pretty:
        cli.output_handler.mode = OutputMode.json
        cli.output_handler.pretty_json = True
        cli.output_handler.columns = '*'
    if parsed.no_headers:
        cli.output_handler.headers = False
    if parsed.all:
        cli.output_handler.columns = '*'
    elif parsed.format:
        cli.output_handler.columns = parsed.format

    cli.defaults = not parsed.no_defaults
    cli.suppress_warnings = parsed.suppress_warnings
    cli.page = parsed.page
    cli.debug_request = parsed.debug

    if parsed.as_user:
        # if they are acting as a non-default user, set it up early
        cli.config.set_user(parsed.as_user)

    if parsed.version:
        if not parsed.command:
            # print version info and exit - but only if no command was given
            print("linode-cli {}".format(VERSION))
            print("Built off spec version {}".format(cli.spec_version))
            exit(0)
        else:
            # something else might want to parse version
            # find where it was originally, as it was removed from args
            index = argv.index('--version') - 3  # executable command action
            args = args[:index] + ['--version'] + args[index:]

    # handle a bake - this is used to parse a spec and bake it as a pickle
    if parsed.command == "bake":
        if parsed.action is None:
            print("No spec provided, cannot bake")
            exit(9)
        print("Baking...")
        spec_loc = parsed.action
        try:
            if os.path.exists(os.path.expanduser(spec_loc)):
                with open(os.path.expanduser(spec_loc)) as f:
                    spec = yaml.safe_load(f.read())
            else:  # try to GET it
                resp = requests.get(spec_loc)
                if resp.status_code == 200:
                    spec = yaml.safe_load(resp.content)
                else:
                    raise RuntimeError("Request failed to {}".format(spec_loc))
        except Exception as e:
            print("Could not load spec: {}".format(e))
            exit(2)

        cli.bake(spec)
        print("Baking bash completions...")
        # this step would normally happen on laod
        if '_base_url' in cli.ops:
            del cli.ops['_base_url']
        if '_spec_version' in cli.ops:
            del cli.ops['_spec_version']
        # do the baking
        cli.bake_completions()
        print("Done.")
        exit(0)
    elif cli.ops is None:
        # if not spec was found and we weren't baking, we're doomed
        exit(3)

    if parsed.command == 'register-plugin':
        # this is how the CLI discovers third-party plugins - the user registers
        # them!  Registering a plugin gets it set up for all CLI users.
        if parsed.action is None:
            print('register-plugin requires a module name!')
            exit(9)
        module = parsed.action

        # attempt to import the module to prove it is installed and exists
        try:
            plugin = import_module(module)
        except ImportError:
            print('Module {} not installed'.format(module))
            exit(10)

        # get the plugin name
        try:
            plugin_name = plugin.PLUGIN_NAME
        except AttributeError:
            print('{} is not a valid Linode CLI plugin - missing PLUGIN_NAME'.
                  format(module))
            exit(11)

        # prove it's callable
        try:
            call_func = plugin.call
        except AttributeError:
            print('{} is not a valid Linode CLI plugin - missing call'.format(
                module))
            exit(11)

        reregistering = False
        # check for naming conflicts
        if plugin_name in cli.ops:
            print(
                'Plugin name conflicts with CLI operation - registration failed.'
            )
            exit(12)
        elif plugin_name in plugins.available_local:
            # conflicts with an internal plugin - can't do that
            print(
                'Plugin name conflicts with internal CLI plugin - registration failed.'
            )
            exit(13)
        elif plugin_name in plugins.available(cli.config):
            from linodecli.configuration import input_helper

            # this isn't an internal plugin, so warn that we're re-registering it
            print("WARNING: Plugin {} is already registered.".format(
                plugin_name))
            print("")
            answer = input_helper(
                "Allow re-registration of {}? [y/N] ".format(plugin_name))

            if not answer or answer not in 'yY':
                print('Registration aborted.')
                exit(0)

            reregistering = True

        # looks good - register it
        already_registered = []
        if cli.config.config.has_option('DEFAULT', 'registered-plugins'):
            already_registered = cli.config.config.get(
                'DEFAULT', 'registered-plugins').split(',')

        if reregistering:
            already_registered.remove(plugin_name)
            cli.config.config.remove_option(
                'DEFAULT', 'plugin-name-{}'.format(plugin_name))

        already_registered.append(plugin_name)
        cli.config.config.set('DEFAULT', 'registered-plugins',
                              ','.join(already_registered))
        cli.config.config.set('DEFAULT', 'plugin-name-{}'.format(plugin_name),
                              module)
        cli.config.write_config()

        print('Plugin registered successfully!')
        print('Invoke this plugin by running the following:')
        print('  linode-cli {}'.format(plugin_name))
        exit(0)

    if parsed.command == 'remove-plugin':
        if parsed.action is None:
            print('remove-plugin requires a plugin name to remove!')
            exit(9)

        # is this plugin registered?
        plugin_name = parsed.action
        if plugin_name in plugins.available_local:
            # can't remove first-party plugins
            print('{} is bundled with the CLI and cannot be removed'.format(
                plugin_name))
            exit(13)
        elif plugin_name not in plugins.available(cli.config):
            print('{} is not a registered plugin'.format(plugin_name))
            exit(14)

        # do the removal
        current_plugins = cli.config.config.get(
            'DEFAULT', 'registered-plugins').split(',')
        current_plugins.remove(plugin_name)
        cli.config.config.set('DEFAULT', 'registered-plugins',
                              ','.join(current_plugins))

        if cli.config.config.has_option('DEFAULT',
                                        'plugin-name-{}'.format(plugin_name)):
            # if the config if malformed, don't blow up
            cli.config.config.remove_option(
                'DEFAULT', 'plugin-name-{}'.format(plugin_name))

        cli.config.write_config()

        print("Plugin {} removed".format(plugin_name))
        exit(0)

    # handle a help for the CLI
    if parsed.command is None or (parsed.command is None and parsed.help):
        parser.print_help()

        # commands to manage CLI users (don't call out to API)
        print()
        print('CLI user management commands:')
        um_commands = [['configure', 'set-user', 'show-users'],
                       ['remove-user']]
        table = SingleTable(um_commands)
        table.inner_heading_row_border = False
        print(table.table)

        # commands to manage plugins (don't call out to API)
        print()
        print('CLI Plugin management commands:')
        pm_commands = [['register-plugin', 'remove-plugin']]
        table = SingleTable(pm_commands)
        table.inner_heading_row_border = False
        print(table.table)

        # commands generated from the spec (call the API directly)
        print()
        print("Available commands:")

        content = [c for c in cli.ops.keys()]
        proc = []
        for i in range(0, len(content), 3):
            proc.append(content[i:i + 3])
        if content[i + 3:]:
            proc.append(content[i + 3:])

        table = SingleTable(proc)
        table.inner_heading_row_border = False
        print(table.table)

        # plugins registered to the CLI (do arbitrary things)
        if plugins.available(cli.config):
            # only show this if there are any available plugins
            print("Available plugins:")

            plugin_content = [p for p in plugins.available(cli.config)]
            plugin_proc = []

            for i in range(0, len(plugin_content), 3):
                plugin_proc.append(plugin_content[i:i + 3])
            if plugin_content[i + 3:]:
                plugin_proc.append(plugin_content[i + 3:])

            plugin_table = SingleTable(plugin_proc)
            plugin_table.inner_heading_row_border = False

            print(plugin_table.table)

        print()
        print("To reconfigure, call `linode-cli configure`")
        print(
            "For comprehensive documentation, visit https://www.linode.com/docs/api/"
        )
        exit(0)

    # configure
    if parsed.command == "configure":
        if parsed.help:
            print('linode-cli configure')
            print()
            print(
                'Configured the Linode CLI.  This command can be used to change'
            )
            print(
                'defaults selected for the current user, or to configure additional'
            )
            print('users.')
            exit(0)
        else:
            cli.configure()
            exit(0)

    # block of commands for user-focused operations
    if parsed.command == "set-user":
        if parsed.help or not parsed.action:
            print('linode-cli set-user [USER]')
            print()
            print(
                'Sets the active user for the CLI out of users you have configured.'
            )
            print('To configure a new user, see `linode-cli configure`')
            exit(0)
        else:
            cli.config.set_default_user(parsed.action)
            exit(0)

    if parsed.command == "show-users":
        if parsed.help:
            print('linode-cli show-users')
            print()
            print(
                'Lists configured users.  Configured users can be set as the')
            print('active user (used for all commands going forward) with the')
            print('`set-user` command, or used for a single command with the')
            print(
                '`--as-user` flag.  New users can be added with `linode-cli configure`.'
            )
            print('The user that is currently active is indicated with a `*`')
            exit(0)
        else:
            cli.config.print_users()
            exit(0)

    if parsed.command == "remove-user":
        if parsed.help or not parsed.action:
            print('linode-cli remove-user [USER]')
            print()
            print(
                'Removes a user the CLI was configured with.  This does not change'
            )
            print(
                'your Linode account, only this CLI installation.  Once removed,'
            )
            print(
                'the user may not be set as active or used for commands unless'
            )
            print('configured again.')
            exit(0)
        else:
            cli.config.remove_user(parsed.action)
            exit(0)

    # special command to bake shell completion script
    if parsed.command == 'bake-bash':
        cli.bake_completions()

    # check for plugin invocation
    if parsed.command not in cli.ops and parsed.command in plugins.available(
            cli.config):
        context = plugins.PluginContext(cli.config.get_token(), cli)

        # reconstruct arguments to send to the plugin
        plugin_args = argv[1:]  # don't include the program name
        plugin_args.remove(parsed.command)  # don't include the plugin name tho

        plugins.invoke(parsed.command, plugin_args, context)
        exit(0)

    if parsed.command not in cli.ops and parsed.command not in plugins.available(
            cli.config):
        # unknown commands
        print('Unrecognized command {}'.format(parsed.command))

    # handle a help for a command - either --help or no action triggers this
    if parsed.command is not None and parsed.action is None:
        if parsed.command in cli.ops:
            actions = cli.ops[parsed.command]
            print("linode-cli {} [ACTION]".format(parsed.command))
            print()
            print("Available actions: ")
            content = [[action, op.summary] for action, op in actions.items()]

            header = ['action', 'summary']
            table = SingleTable([header] + content)
            print(table.table)
            exit(0)

    # handle a help for an action
    if parsed.command is not None and parsed.action is not None and parsed.help:
        if parsed.command in cli.ops and parsed.action in cli.ops[
                parsed.command]:
            operation = cli.ops[parsed.command][parsed.action]
            print("linode-cli {} {}".format(parsed.command, parsed.action),
                  end='')
            for param in operation.params:
                # clean up parameter names - we add an '_' at the end of them
                # during baking if it conflicts with the name of an argument.
                # Remove the trailing underscores on output (they're not
                # important to the end user).
                pname = param.name.upper()
                if pname[-1] == '_':
                    pname = pname[:-1]
                print(' [{}]'.format(pname), end='')
            print()
            print(operation.summary)
            print()
            if operation.args:
                print("Arguments:")
                for arg in sorted(operation.args,
                                  key=lambda s: not s.required):
                    print("  --{}: {}{}".format(
                        arg.path, "(required) " if operation.method == 'post'
                        and arg.required else '', arg.description))
            elif operation.method == 'get' and parsed.action == 'list':
                filterable_attrs = ([
                    attr for attr in operation.response_model.attrs
                    if attr.filterable
                ])

                if filterable_attrs:
                    print("You may filter results with:")
                    for attr in filterable_attrs:
                        print("  --{}".format(attr.name))
        exit(0)

    if parsed.command is not None and parsed.action is not None:
        cli.handle_command(parsed.command, parsed.action, args)
Пример #2
0
def main():
    ## Command Handling
    parser = argparse.ArgumentParser("linode-cli", add_help=False)
    parser.add_argument('command',
                        metavar='COMMAND',
                        nargs='?',
                        type=str,
                        help="The command to invoke in the CLI.")
    parser.add_argument('action',
                        metavar='ACTION',
                        nargs='?',
                        type=str,
                        help="The action to perform in this command.")
    parser.add_argument('--help',
                        action="store_true",
                        help="Display information about a command, action, or "
                        "the CLI overall.")
    parser.add_argument(
        '--text',
        action="store_true",
        help="Display text output with a delimiter (defaults to tabs).")
    parser.add_argument('--delimiter',
                        metavar='DELIMITER',
                        type=str,
                        help="The delimiter when displaying raw output.")
    parser.add_argument('--json',
                        action='store_true',
                        help="Display output as JSON")
    parser.add_argument('--pretty',
                        action='store_true',
                        help="If set, pretty-print JSON output")
    parser.add_argument('--no-headers',
                        action='store_true',
                        help="If set, does not display headers in output.")
    parser.add_argument(
        '--page',
        metavar='PAGE',
        type=int,
        default=1,
        help="For listing actions, specifies the page to request")
    parser.add_argument(
        '--all',
        action='store_true',
        help="If set, displays all possible columns instead of "
        "the default columns.  This may not work well on "
        "some terminals.")
    parser.add_argument(
        '--format',
        metavar='FORMAT',
        type=str,
        help="The columns to display in output.  Provide a comma-"
        "separated list of column names.")
    parser.add_argument(
        '--no-defaults',
        action='store_true',
        help="Suppress default values for arguments.  Default values "
        "are configured on initial setup or with linode-cli configure")
    parser.add_argument(
        '--suppress-warnings',
        action='store_true',
        help="Suppress warnings that are intended for human users. "
        "This is useful for scripting the CLI's behavior.")
    parser.add_argument('--version',
                        '-v',
                        action="store_true",
                        help="Prints version information and exits.")

    parsed, args = parser.parse_known_args()

    # setup cli class
    if parsed.text:
        cli.output_handler.mode = OutputMode.delimited
    elif parsed.json:
        cli.output_handler.mode = OutputMode.json
    if parsed.delimiter:
        cli.output_handler.delimiter = parsed.delimiter
    if parsed.pretty:
        cli.output_handler.pretty_json = True
    if parsed.no_headers:
        cli.output_handler.headers = False
    if parsed.all:
        cli.output_handler.columns = '*'
    elif parsed.format:
        cli.output_handler.columns = parsed.format

    cli.defaults = not parsed.no_defaults
    cli.suppress_warnings = parsed.suppress_warnings
    cli.page = parsed.page

    if parsed.version:
        # print version info and exit
        print("linode-cli {}".format(VERSION))
        print("Built off spec version {}".format(cli.spec_version))
        exit(0)

    # handle a bake - this is used to parse a spec and bake it as a pickle
    if parsed.command == "bake":
        if parsed.action is None:
            print("No spec provided, cannot bake")
            exit(9)
        print("Baking...")
        spec_loc = parsed.action
        try:
            if os.path.exists(os.path.expanduser(spec_loc)):
                with open(os.path.expanduser(spec_loc)) as f:
                    spec = yaml.safe_load(f.read())
            else:  # try to GET it
                resp = requests.get(spec_loc)
                if resp.status_code == 200:
                    spec = yaml.safe_load(resp.content)
                else:
                    raise RuntimeError("Request failed to {}".format(spec_loc))
        except Exception as e:
            print("Could not load spec: {}".format(e))
            exit(2)

        cli.bake(spec)
        print("Baking bash completions...")
        # this step would normally happen on laod
        if '_base_url' in cli.ops:
            del cli.ops['_base_url']
        if '_spec_version' in cli.ops:
            del cli.ops['_spec_version']
        # do the baking
        cli.bake_completions()
        print("Done.")
        exit(0)
    elif cli.ops is None:
        # if not spec was found and we weren't baking, we're doomed
        exit(3)

    # handle a help for the CLI
    if parsed.command is None or (parsed.command is None and parsed.help):
        parser.print_help()
        print()
        print("Available commands:")

        content = [c for c in cli.ops.keys()]
        proc = []
        for i in range(0, len(content), 3):
            proc.append(content[i:i + 3])
        if content[i + 3:]:
            proc.append(content[i + 3:])

        table = SingleTable(proc)
        table.inner_heading_row_border = False
        print(table.table)

        if plugins.available:
            # only show this if there are any available plugins
            print("Available plugins:")

            plugin_content = [p for p in plugins.available]
            plugin_proc = []

            for i in range(0, len(plugin_content), 3):
                plugin_proc.append(plugin_content[i:i + 3])
            if plugin_content[i + 3:]:
                plugin_proc.append(plugin_content[i + 3:])

            plugin_table = SingleTable(plugin_proc)
            plugin_table.inner_heading_row_border = False

            print(plugin_table.table)

        print()
        print("To reconfigure, call `linode-cli configure`")
        print(
            "For comprehensive documentation, visit https://developers.linode.com"
        )
        exit(0)

    # configure
    if parsed.command == "configure":
        cli.configure(username=parsed.action)
        exit(0)

    # special command to bake shell completion script
    if parsed.command == 'bake-bash':
        cli.bake_completions()

    # check for plugin invocation
    if parsed.command not in cli.ops and parsed.command in plugins.available:
        context = plugins.PluginContext(cli.token, cli)

        # reconstruct arguments to send to the plugin
        plugin_args = argv[1:]  # don't include the program name
        plugin_args.remove(parsed.command)  # don't include the plugin name tho

        plugins.invoke(parsed.command, plugin_args, context)
        exit(0)

    if parsed.command not in cli.ops and parsed.command not in plugins.available:
        # unknown commands
        print('Unrecognized command {}'.format(parsed.command))

    # handle a help for a command - either --help or no action triggers this
    if parsed.command is not None and parsed.action is None:
        if parsed.command in cli.ops:
            actions = cli.ops[parsed.command]
            print("linode-cli {} [ACTION]".format(parsed.command))
            print()
            print("Available actions: ")
            content = [[action, op.summary] for action, op in actions.items()]

            header = ['action', 'summary']
            table = SingleTable([header] + content)
            print(table.table)
            exit(0)

    # handle a help for an action
    if parsed.command is not None and parsed.action is not None and parsed.help:
        if parsed.command in cli.ops and parsed.action in cli.ops[
                parsed.command]:
            operation = cli.ops[parsed.command][parsed.action]
            print("linode-cli {} {}".format(parsed.command, parsed.action),
                  end='')
            for param in operation.params:
                # clean up parameter names - we add an '_' at the end of them
                # during baking if it conflicts with the name of an argument.
                # Remove the trailing underscores on output (they're not
                # important to the end user).
                pname = param.name.upper()
                if pname[-1] == '_':
                    pname = pname[:-1]
                print(' [{}]'.format(pname), end='')
            print()
            print(operation.summary)
            print()
            if operation.args:
                print("Arguments:")
                for arg in sorted(operation.args,
                                  key=lambda s: not s.required):
                    print("  --{}: {}{}".format(
                        arg.path, "(required) " if operation.method == 'post'
                        and arg.required else '', arg.description))
            elif operation.method == 'get' and parsed.action == 'list':
                filterable_attrs = ([
                    attr for attr in operation.response_model.attrs
                    if attr.filterable
                ])

                if filterable_attrs:
                    print("You may filter results with:")
                    for attr in filterable_attrs:
                        print("  --{}".format(attr.name))
        exit(0)

    if parsed.command is not None and parsed.action is not None:
        cli.handle_command(parsed.command, parsed.action, args)