github.com/jpedro/terraform@v0.11.12-beta1/helper/variables/parse.go (about)

     1  package variables
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl"
    10  )
    11  
    12  // ParseInput parses a manually inputed variable to a richer value.
    13  //
    14  // This will turn raw input into rich types such as `[]` to a real list or
    15  // `{}` to a real map. This function should be used to parse any manual untyped
    16  // input for variables in order to provide a consistent experience.
    17  func ParseInput(value string) (interface{}, error) {
    18  	trimmed := strings.TrimSpace(value)
    19  
    20  	// If the value is a simple number, don't parse it as hcl because the
    21  	// variable type may actually be a string, and HCL will convert it to the
    22  	// numberic value. We could check this in the validation later, but the
    23  	// conversion may alter the string value.
    24  	if _, err := strconv.ParseInt(trimmed, 10, 64); err == nil {
    25  		return value, nil
    26  	}
    27  	if _, err := strconv.ParseFloat(trimmed, 64); err == nil {
    28  		return value, nil
    29  	}
    30  
    31  	// HCL will also parse hex as a number
    32  	if strings.HasPrefix(trimmed, "0x") {
    33  		if _, err := strconv.ParseInt(trimmed[2:], 16, 64); err == nil {
    34  			return value, nil
    35  		}
    36  	}
    37  
    38  	// If the value is a boolean value, also convert it to a simple string
    39  	// since Terraform core doesn't accept primitives as anything other
    40  	// than string for now.
    41  	if _, err := strconv.ParseBool(trimmed); err == nil {
    42  		return value, nil
    43  	}
    44  
    45  	parsed, err := hcl.Parse(fmt.Sprintf("foo=%s", trimmed))
    46  	if err != nil {
    47  		// If it didn't parse as HCL, we check if it doesn't match our
    48  		// whitelist of TF-accepted HCL types for inputs. If not, then
    49  		// we let it through as a raw string.
    50  		if !varFlagHCLRe.MatchString(trimmed) {
    51  			return value, nil
    52  		}
    53  
    54  		// This covers flags of the form `foo=bar` which is not valid HCL
    55  		// At this point, probablyName is actually the name, and the remainder
    56  		// of the expression after the equals sign is the value.
    57  		if regexp.MustCompile(`Unknown token: \d+:\d+ IDENT`).Match([]byte(err.Error())) {
    58  			return value, nil
    59  		}
    60  
    61  		return nil, fmt.Errorf(
    62  			"Cannot parse value for variable (%q) as valid HCL: %s",
    63  			value, err)
    64  	}
    65  
    66  	var decoded map[string]interface{}
    67  	if hcl.DecodeObject(&decoded, parsed); err != nil {
    68  		return nil, fmt.Errorf(
    69  			"Cannot parse value for variable (%q) as valid HCL: %s",
    70  			value, err)
    71  	}
    72  
    73  	// Cover cases such as key=
    74  	if len(decoded) == 0 {
    75  		return "", nil
    76  	}
    77  
    78  	if len(decoded) > 1 {
    79  		return nil, fmt.Errorf(
    80  			"Cannot parse value for variable (%q) as valid HCL. "+
    81  				"Only one value may be specified.",
    82  			value)
    83  	}
    84  
    85  	err = flattenMultiMaps(decoded)
    86  	if err != nil {
    87  		return "", err
    88  	}
    89  
    90  	return decoded["foo"], nil
    91  }
    92  
    93  var (
    94  	// This regular expression is how we check if a value for a variable
    95  	// matches what we'd expect a rich HCL value to be. For example: {
    96  	// definitely signals a map. If a value DOESN'T match this, we return
    97  	// it as a raw string.
    98  	varFlagHCLRe = regexp.MustCompile(`^["\[\{]`)
    99  )
   100  
   101  // Variables don't support any type that can be configured via multiple
   102  // declarations of the same HCL map, so any instances of
   103  // []map[string]interface{} are either a single map that can be flattened, or
   104  // are invalid config.
   105  func flattenMultiMaps(m map[string]interface{}) error {
   106  	for k, v := range m {
   107  		switch v := v.(type) {
   108  		case []map[string]interface{}:
   109  			switch {
   110  			case len(v) > 1:
   111  				return fmt.Errorf("multiple map declarations not supported for variables")
   112  			case len(v) == 1:
   113  				m[k] = v[0]
   114  			}
   115  		}
   116  	}
   117  	return nil
   118  }