← 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

 

It occurred to me yesterday that user-data can be big.  EC2 is limited to 16K, but many other platforms have higher limits.  That means we potentially have a very big blob of base64 encoded text in an other-wise reasonably small file.

That, coupled with the fact that for quite some time user-data has been available in /var/lib/cloud/instance/user-data.txt (badly named...) seems to mean to me that we should just not put user-data in the json.

We could still have cloud-init query make it available.
The same seems true of vendor-data.


And then, needs to update bash_completion also.


Diff comments:

> diff --git a/cloudinit/cmd/query.py b/cloudinit/cmd/query.py
> new file mode 100644
> index 0000000..42d7eac
> --- /dev/null
> +++ b/cloudinit/cmd/query.py
> @@ -0,0 +1,123 @@
> +# 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
> +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')
> +    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(
> +        '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."""
> +    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()
> +        instance_data_fn = os.path.join(paths.run_dir, INSTANCE_JSON_FILE)
> +    else:
> +        instance_data_fn = args.instance_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 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):

convert_jinja_instance_data  makes bytes into base64 encoded strings?
If so, then this section is fine, but
  cloud-init query v1.binary_thing
will output base64 encoded content.. which probably isn't really what user wanted.

If *not*, then this needs to handle that.

> +        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.