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 try: instance_data = _read_instance_data( args.instance_data, args.user_data, args.vendor_data ) except (IOError, OSError): return 1 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 # If not rendering a structured format above, query output will be either: # - JSON dump of all instance-data/jinja variables # - JSON dump of a value at an dict path into the instance-data dict. # - a list of keys for a specific dict path into the instance-data dict. response = convert_jinja_instance_data(instance_data) if args.varname: jinja_vars_with_aliases = convert_jinja_instance_data( instance_data, include_key_aliases=True ) try: response = _find_instance_data_leaf_by_varname_path( jinja_vars_without_aliases=response, jinja_vars_with_aliases=jinja_vars_with_aliases, varname=args.varname, list_keys=args.list_keys, ) except (KeyError, ValueError) as e: LOG.error(e) return 1 if args.list_keys: if not isinstance(response, dict): LOG.error( "--list-keys provided but '%s' is not a dict", args.varname ) return 1 response = "\n".join(sorted(response.keys())) if not isinstance(response, str): response = util.json_dumps(response) print(response) return 0
def test_convert_instance_data_promotes_versioned_keys_to_top_level(self): """Any versioned keys are promoted as top-level keys This provides any cloud-init standardized keys up at a top-level to allow ease of reference for users. Intsead of v1.availability_zone, the name availability_zone can be used in templates. """ data = { 'ds': { 'dskey1': 1, 'dskey2': 2 }, 'v1': { 'v1key1': 'v1.1' }, 'v2': { 'v2key1': 'v2.1' } } expected_data = copy.deepcopy(data) expected_data.update({'v1key1': 'v1.1', 'v2key1': 'v2.1'}) converted_data = convert_jinja_instance_data(data=data) self.assertCountEqual(['ds', 'v1', 'v2', 'v1key1', 'v2key1'], converted_data.keys()) self.assertEqual(expected_data, converted_data)
def test_convert_instance_data_promotes_versioned_keys_to_top_level(self): """Any versioned keys are promoted as top-level keys This provides any cloud-init standardized keys up at a top-level to allow ease of reference for users. Intsead of v1.availability_zone, the name availability_zone can be used in templates. """ data = { "ds": { "dskey1": 1, "dskey2": 2 }, "v1": { "v1key1": "v1.1" }, "v2": { "v2key1": "v2.1" }, } expected_data = copy.deepcopy(data) expected_data.update({"v1key1": "v1.1", "v2key1": "v2.1"}) converted_data = convert_jinja_instance_data(data=data) assert sorted(["ds", "v1", "v2", "v1key1", "v2key1"]) == sorted(converted_data.keys()) assert expected_data == converted_data
def test_convert_instance_data_decodes_decode_paths(self): """Any decode_paths provided are decoded by convert_instance_data.""" data = {"key1": {"subkey1": "aGkgbW9t"}, "key2": "aGkgZGFk"} expected_data = copy.deepcopy(data) expected_data["key1"]["subkey1"] = "hi mom" converted_data = convert_jinja_instance_data( data=data, decode_paths=("key1/subkey1", )) assert expected_data == converted_data
def test_convert_instance_data_decodes_decode_paths(self): """Any decode_paths provided are decoded by convert_instance_data.""" data = {'key1': {'subkey1': 'aGkgbW9t'}, 'key2': 'aGkgZGFk'} expected_data = copy.deepcopy(data) expected_data['key1']['subkey1'] = 'hi mom' converted_data = convert_jinja_instance_data( data=data, decode_paths=('key1/subkey1', )) self.assertEqual(expected_data, converted_data)
def test_convert_instance_data_hyphens_to_underscores(self): """Replace hyphenated keys with underscores in instance-data.""" data = {'hyphenated-key': 'hyphenated-val', 'underscore_delim_key': 'underscore_delimited_val'} expected_data = {'hyphenated_key': 'hyphenated-val', 'underscore_delim_key': 'underscore_delimited_val'} self.assertEqual( expected_data, convert_jinja_instance_data(data=data))
def test_convert_instance_data_hyphens_to_underscores(self): """Replace hyphenated keys with underscores in instance-data.""" data = {'hyphenated-key': 'hyphenated-val', 'underscore_delim_key': 'underscore_delimited_val'} expected_data = {'hyphenated_key': 'hyphenated-val', 'underscore_delim_key': 'underscore_delimited_val'} self.assertEqual( expected_data, convert_jinja_instance_data(data=data))
def test_convert_instance_data_decodes_decode_paths(self): """Any decode_paths provided are decoded by convert_instance_data.""" data = {'key1': {'subkey1': 'aGkgbW9t'}, 'key2': 'aGkgZGFk'} expected_data = copy.deepcopy(data) expected_data['key1']['subkey1'] = 'hi mom' converted_data = convert_jinja_instance_data( data=data, decode_paths=('key1/subkey1',)) self.assertEqual( expected_data, converted_data)
def test_convert_instance_data_most_recent_version_of_promoted_keys(self): """The most-recent versioned key value is promoted to top-level.""" data = {'v1': {'key1': 'old v1 key1', 'key2': 'old v1 key2'}, 'v2': {'key1': 'newer v2 key1', 'key3': 'newer v2 key3'}, 'v3': {'key1': 'newest v3 key1'}} expected_data = copy.deepcopy(data) expected_data.update( {'key1': 'newest v3 key1', 'key2': 'old v1 key2', 'key3': 'newer v2 key3'}) converted_data = convert_jinja_instance_data(data=data) self.assertEqual( expected_data, converted_data)
def test_convert_instance_data_most_recent_version_of_promoted_keys(self): """The most-recent versioned key value is promoted to top-level.""" data = {'v1': {'key1': 'old v1 key1', 'key2': 'old v1 key2'}, 'v2': {'key1': 'newer v2 key1', 'key3': 'newer v2 key3'}, 'v3': {'key1': 'newest v3 key1'}} expected_data = copy.deepcopy(data) expected_data.update( {'key1': 'newest v3 key1', 'key2': 'old v1 key2', 'key3': 'newer v2 key3'}) converted_data = convert_jinja_instance_data(data=data) self.assertEqual( expected_data, converted_data)
def test_convert_instance_data_promotes_versioned_keys_to_top_level(self): """Any versioned keys are promoted as top-level keys This provides any cloud-init standardized keys up at a top-level to allow ease of reference for users. Intsead of v1.availability_zone, the name availability_zone can be used in templates. """ data = {'ds': {'dskey1': 1, 'dskey2': 2}, 'v1': {'v1key1': 'v1.1'}, 'v2': {'v2key1': 'v2.1'}} expected_data = copy.deepcopy(data) expected_data.update({'v1key1': 'v1.1', 'v2key1': 'v2.1'}) converted_data = convert_jinja_instance_data(data=data) self.assertItemsEqual( ['ds', 'v1', 'v2', 'v1key1', 'v2key1'], converted_data.keys()) self.assertEqual( expected_data, converted_data)
def test_convert_instance_data_most_recent_version_of_promoted_keys(self): """The most-recent versioned key value is promoted to top-level.""" data = { "v1": { "key1": "old v1 key1", "key2": "old v1 key2" }, "v2": { "key1": "newer v2 key1", "key3": "newer v2 key3" }, "v3": { "key1": "newest v3 key1" }, } expected_data = copy.deepcopy(data) expected_data.update({ "key1": "newest v3 key1", "key2": "old v1 key2", "key3": "newer v2 key3", }) converted_data = convert_jinja_instance_data(data=data) assert expected_data == converted_data
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 uid = os.getuid() if not all([args.instance_data, args.user_data, args.vendor_data]): paths = read_cfg_paths() if args.instance_data: instance_data_fn = args.instance_data else: redacted_data_fn = os.path.join(paths.run_dir, INSTANCE_JSON_FILE) if uid == 0: sensitive_data_fn = os.path.join(paths.run_dir, INSTANCE_JSON_SENSITIVE_FILE) if os.path.exists(sensitive_data_fn): instance_data_fn = sensitive_data_fn else: LOG.warning( 'Missing root-readable %s. Using redacted %s instead.', sensitive_data_fn, redacted_data_fn) instance_data_fn = redacted_data_fn else: instance_data_fn = redacted_data_fn if args.user_data: user_data_fn = args.user_data else: user_data_fn = os.path.join(paths.instance_link, 'user-data.txt') if args.vendor_data: vendor_data_fn = args.vendor_data else: vendor_data_fn = os.path.join(paths.instance_link, 'vendor-data.txt') try: instance_json = util.load_file(instance_data_fn) except (IOError, OSError) as e: if e.errno == EACCES: LOG.error("No read permission on '%s'. Try sudo", instance_data_fn) else: LOG.error('Missing instance-data file: %s', instance_data_fn) return 1 instance_data = util.load_json(instance_json) if uid != 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'] = load_userdata(user_data_fn) instance_data['vendordata'] = load_userdata(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, str): response = util.json_dumps(response) print(response) return 0
def test_convert_instance_data_operators_to_underscores( self, include_key_aliases, data, expected): """Replace Jinja operators keys with underscores in instance-data.""" assert expected == convert_jinja_instance_data( data=data, include_key_aliases=include_key_aliases)
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 uid = os.getuid() if not all([args.instance_data, args.user_data, args.vendor_data]): paths = read_cfg_paths() if not args.instance_data: if uid == 0: default_json_fn = INSTANCE_JSON_SENSITIVE_FILE 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: user_data_fn = os.path.join(paths.instance_link, 'user-data.txt') else: user_data_fn = args.user_data if not args.vendor_data: vendor_data_fn = os.path.join(paths.instance_link, 'vendor-data.txt') else: vendor_data_fn = args.vendor_data try: instance_json = util.load_file(instance_data_fn) except IOError: LOG.error('Missing instance-data.json file: %s', instance_data_fn) return 1 instance_data = util.load_json(instance_json) if uid != 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