github.com/sylr/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 }