← Back to team overview

cloud-init-dev team mailing list archive

Re: [Merge] ~chad.smith/cloud-init:feature/cli-cloudinit-query into cloud-init:master

 

minor comments inline.
only thought is to talk about names of things and expected values ('cloudname') and such.

we need to hhave that somewhere, and i didn't read close enough to see it.


Diff comments:

> diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
> index 0eee583..5a43702 100644
> --- a/cloudinit/cmd/main.py
> +++ b/cloudinit/cmd/main.py
> @@ -842,6 +846,12 @@ def main(sysv_args=None):
>              clean_parser(parser_clean)
>              parser_clean.set_defaults(
>                  action=('clean', handle_clean_args))
> +        elif sysv_args[0] == 'query':
> +            from cloudinit.cmd.query import (

why the import here?

> +                get_parser as query_parser, handle_args as handle_query_args)
> +            query_parser(parser_query)
> +            parser_query.set_defaults(
> +                action=('render', handle_query_args))
>          elif sysv_args[0] == 'status':
>              from cloudinit.cmd.status import (
>                  get_parser as status_parser, handle_status_args)
> diff --git a/cloudinit/cmd/query.py b/cloudinit/cmd/query.py
> new file mode 100644
> index 0000000..194dd73
> --- /dev/null
> +++ b/cloudinit/cmd/query.py
> @@ -0,0 +1,158 @@
> +# This file is part of cloud-init. See LICENSE file for license information.
> +
> +"""Query standardized instance metadata from the command line."""
> +
> +import argparse
> +import os
> +import six
> +import sys
> +
> +from cloudinit.handlers.jinja_template import (
> +    convert_jinja_instance_data, render_jinja_payload)
> +from cloudinit.cmd.devel import addLogHandlerCLI, read_cfg_paths
> +from cloudinit import log
> +from cloudinit.sources import (
> +    INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE, REDACT_SENSITIVE_VALUE)
> +from cloudinit import util
> +
> +NAME = 'query'
> +LOG = log.getLogger(NAME)
> +
> +
> +def get_parser(parser=None):
> +    """Build or extend an arg parser for query utility.
> +
> +    @param parser: Optional existing ArgumentParser instance representing the
> +        query subcommand which will be extended to support the args of
> +        this utility.
> +
> +    @returns: ArgumentParser with proper argument configuration.
> +    """
> +    if not parser:
> +        parser = argparse.ArgumentParser(
> +            prog=NAME, description='Query cloud-init instance data')
> +    parser.add_argument(
> +        '-d', '--debug', action='store_true', default=False,
> +        help='Add verbose messages during template render')
> +    parser.add_argument(
> +        '-i', '--instance-data', type=str,
> +        help=('Path to instance-data.json file. Default is /run/cloud-init/%s'
> +              % INSTANCE_JSON_FILE))
> +    parser.add_argument(
> +        '-l', '--list-keys', action='store_true', default=False,
> +        help=('List query keys available at the provided instance-data'
> +              ' <varname>.'))
> +    parser.add_argument(
> +        '-u', '--user-data', type=str,
> +        help=('Path to user-data file. Default is'
> +              ' /var/lib/cloud/instance/user-data.txt'))
> +    parser.add_argument(
> +        '-v', '--vendor-data', type=str,
> +        help=('Path to vendor-data file. Default is'
> +              ' /var/lib/cloud/instance/vendor-data.txt'))
> +    parser.add_argument(
> +        'varname', type=str, nargs='?',
> +        help=('A dot-delimited instance data variable to query from'
> +              ' instance-data query. For example: v2.local_hostname'))
> +    parser.add_argument(
> +        '-a', '--all', action='store_true', default=False, dest='dump_all',
> +        help='Dump all available instance-data')
> +    parser.add_argument(
> +        '-f', '--format', type=str, dest='format',
> +        help=('Optionally specify a custom output format string. Any'
> +              ' instance-data variable can be specified between double-curly'
> +              ' braces. For example -f "{{ v2.cloud_name }}"'))
> +    return parser
> +
> +
> +def handle_args(name, args):
> +    """Handle calls to 'cloud-init query' as a subcommand."""
> +    paths = None
> +    addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING)
> +    if not any([args.list_keys, args.varname, args.format, args.dump_all]):
> +        LOG.error(
> +            'Expected one of the options: --all, --format,'
> +            ' --list-keys or varname')
> +        get_parser().print_help()
> +        return 1
> +
> +    if not args.instance_data:
> +        paths = read_cfg_paths()
> +        if os.getuid() == 0:
> +            default_json_fn = INSTANCE_JSON_SENSITIVE_FILE

this could just as well be done in the 'get_parser' above.
then it would show up in help.

> +        else:
> +            default_json_fn = INSTANCE_JSON_FILE  # World readable
> +        instance_data_fn = os.path.join(paths.run_dir, default_json_fn)
> +    else:
> +        instance_data_fn = args.instance_data
> +    if not args.user_data:
> +        if not paths:
> +            paths = read_cfg_paths()
> +        user_data_fn = paths.get_ipath('userdata')
> +    else:
> +        user_data_fn = args.user_data
> +    if not args.vendor_data:
> +        if not paths:
> +            paths = read_cfg_paths()
> +        vendor_data_fn = paths.get_ipath('vendordata')
> +    else:
> +        vendor_data_fn = args.vendor_data
> +
> +    try:
> +        with open(instance_data_fn) as stream:
> +            instance_json = stream.read()
> +    except IOError:
> +        LOG.error('Missing instance-data.json file: %s', instance_data_fn)
> +        return 1
> +
> +    instance_data = util.load_json(instance_json)
> +    if os.getuid() != 0:
> +        instance_data['userdata'] = (
> +            '<%s> file:%s' % (REDACT_SENSITIVE_VALUE, user_data_fn))
> +        instance_data['vendordata'] = (
> +            '<%s> file:%s' % (REDACT_SENSITIVE_VALUE, vendor_data_fn))
> +    else:
> +        instance_data['userdata'] = util.load_file(user_data_fn)
> +        instance_data['vendordata'] = util.load_file(vendor_data_fn)
> +    if args.format:
> +        payload = '## template: jinja\n{fmt}'.format(fmt=args.format)
> +        rendered_payload = render_jinja_payload(
> +            payload=payload, payload_fn='query commandline',
> +            instance_data=instance_data,
> +            debug=True if args.debug else False)
> +        if rendered_payload:
> +            print(rendered_payload)
> +            return 0
> +        return 1
> +
> +    response = convert_jinja_instance_data(instance_data)
> +    if args.varname:
> +        try:
> +            for var in args.varname.split('.'):
> +                response = response[var]
> +        except KeyError:
> +            LOG.error('Undefined instance-data key %s', args.varname)
> +            return 1
> +        if args.list_keys:
> +            if not isinstance(response, dict):
> +                LOG.error("--list-keys provided but '%s' is not a dict", var)
> +                return 1
> +            response = '\n'.join(sorted(response.keys()))
> +    elif args.list_keys:
> +        response = '\n'.join(sorted(response.keys()))
> +    if not isinstance(response, six.string_types):
> +        response = util.json_dumps(response)
> +    print(response)
> +    return 0
> +
> +
> +def main():
> +    """Tool to query specific instance-data values."""
> +    parser = get_parser()
> +    sys.exit(handle_args(NAME, parser.parse_args()))
> +
> +
> +if __name__ == '__main__':
> +    main()
> +
> +# vi: ts=4 expandtab


-- 
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/354891
Your team cloud-init commiters is requested to review the proposed merge of ~chad.smith/cloud-init:feature/cli-cloudinit-query into cloud-init:master.