diff --git a/tower_cli/cli/types.py b/tower_cli/cli/types.py index 945641a2..f74bd9bf 100644 --- a/tower_cli/cli/types.py +++ b/tower_cli/cli/types.py @@ -17,6 +17,7 @@ import os import re import six +import json import click @@ -72,14 +73,26 @@ class StructuredInput(Variables): name = 'structured_input' __name__ = 'structured_input' + def __init__(self, allow_kv=False, as_string=False): + self.allow_kv = allow_kv + # The API is inconsistent about which format it requires + # some fields must string-ify variables, and others can + # pass variables as a dictionary + self.as_string = as_string + super(StructuredInput, self).__init__() + def convert(self, value, param, ctx): s = super(StructuredInput, self).convert(value, param, ctx) try: - return string_to_dict(s, allow_kv=False) - except Exception: + data = string_to_dict(s, allow_kv=self.allow_kv) + if self.as_string: + return json.dumps(data) + return data + except Exception as e: raise exc.UsageError( 'Error loading structured input given by %s parameter. Please ' - 'check the validity of your JSON/YAML format.' % param.name + 'check the validity of your JSON/YAML format. ' + 'Parse error: %s' % (param.name, e) ) diff --git a/tower_cli/resources/group.py b/tower_cli/resources/group.py index dc3625b1..732002bf 100644 --- a/tower_cli/resources/group.py +++ b/tower_cli/resources/group.py @@ -29,7 +29,8 @@ class Resource(models.Resource): name = models.Field(unique=True) description = models.Field(required=False, display=False) inventory = models.Field(type=types.Related('inventory')) - variables = models.Field(type=types.Variables(), required=False, display=False, + variables = models.Field(type=types.StructuredInput(allow_kv=True, as_string=True), + required=False, display=False, help_text='Group variables, use "@" to get from file.') def lookup_with_inventory(self, group, inventory=None): diff --git a/tower_cli/resources/host.py b/tower_cli/resources/host.py index 7533c59f..73fca7a2 100644 --- a/tower_cli/resources/host.py +++ b/tower_cli/resources/host.py @@ -30,7 +30,8 @@ class Resource(models.Resource): description = models.Field(required=False, display=False) inventory = models.Field(type=types.Related('inventory')) enabled = models.Field(type=bool, required=False) - variables = models.Field(type=types.Variables(), required=False, display=False, + variables = models.Field(type=types.StructuredInput(allow_kv=True, as_string=True), + required=False, display=False, help_text='Host variables, use "@" to get from file.') insights_system_id = models.Field(required=False, display=False) diff --git a/tower_cli/resources/inventory.py b/tower_cli/resources/inventory.py index 685aefc0..9fa120ab 100644 --- a/tower_cli/resources/inventory.py +++ b/tower_cli/resources/inventory.py @@ -31,7 +31,8 @@ class Resource(models.Resource): name = models.Field(unique=True) description = models.Field(required=False, display=False) organization = models.Field(type=types.Related('organization')) - variables = models.Field(type=types.Variables(), required=False, display=False, + variables = models.Field(type=types.StructuredInput(allow_kv=True, as_string=True), + required=False, display=False, help_text='Inventory variables, use "@" to get from file.') kind = models.Field(type=click.Choice(['', 'smart']), required=False, display=False, help_text='The kind field. Cannot be modified after created.') diff --git a/tower_cli/utils/parser.py b/tower_cli/utils/parser.py index a837959a..ff7ae6e4 100644 --- a/tower_cli/utils/parser.py +++ b/tower_cli/utils/parser.py @@ -84,26 +84,23 @@ def string_to_dict(var_string, allow_kv=True, require_dict=True): Attempts processing string by 3 different methods in order: 1. as JSON 2. as YAML 3. as custom key=value syntax Throws an error if all of these fail in the standard ways.""" - # try: - # # Accept all valid "key":value types of json - # return_dict = json.loads(var_string) - # assert type(return_dict) is dict - # except (TypeError, AttributeError, ValueError, AssertionError): try: # Accept all JSON and YAML return_dict = yaml.load(var_string) if require_dict: assert type(return_dict) is dict - except (AttributeError, yaml.YAMLError, AssertionError): + except (AttributeError, yaml.YAMLError, AssertionError) as yaml_e: # if these fail, parse by key=value syntax - try: - assert allow_kv - return_dict = parse_kv(var_string) - except Exception: - raise exc.TowerCLIError( - 'failed to parse some of the extra ' - 'variables.\nvariables: \n%s' % var_string - ) + msg = six.text_type( + 'failed to parse some of the ' + 'variables.\nvariables: \n{0}\nYAML error: \n{1}' + ).format(var_string, yaml_e) + if allow_kv: + try: + return parse_kv(var_string) + except Exception as kv_e: + msg += six.text_type('\nkey=value error: {0}').format(kv_e) + raise exc.TowerCLIError(msg) return return_dict