github.com/tomaszheflik/terraform@v0.7.3-0.20160827060421-32f990b41594/command/flag_kv.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "regexp" 7 "strings" 8 9 "github.com/hashicorp/hcl" 10 "github.com/mitchellh/go-homedir" 11 ) 12 13 // FlagTypedKVis a flag.Value implementation for parsing user variables 14 // from the command-line in the format of '-var key=value', where value is 15 // a type intended for use as a Terraform variable 16 type FlagTypedKV map[string]interface{} 17 18 func (v *FlagTypedKV) String() string { 19 return "" 20 } 21 22 func (v *FlagTypedKV) Set(raw string) error { 23 key, value, err := parseVarFlagAsHCL(raw) 24 if err != nil { 25 return err 26 } 27 28 if *v == nil { 29 *v = make(map[string]interface{}) 30 } 31 32 (*v)[key] = value 33 return nil 34 } 35 36 // FlagStringKV is a flag.Value implementation for parsing user variables 37 // from the command-line in the format of '-var key=value', where value is 38 // only ever a primitive. 39 type FlagStringKV map[string]string 40 41 func (v *FlagStringKV) String() string { 42 return "" 43 } 44 45 func (v *FlagStringKV) Set(raw string) error { 46 idx := strings.Index(raw, "=") 47 if idx == -1 { 48 return fmt.Errorf("No '=' value in arg: %s", raw) 49 } 50 51 if *v == nil { 52 *v = make(map[string]string) 53 } 54 55 key, value := raw[0:idx], raw[idx+1:] 56 (*v)[key] = value 57 return nil 58 } 59 60 // FlagKVFile is a flag.Value implementation for parsing user variables 61 // from the command line in the form of files. i.e. '-var-file=foo' 62 type FlagKVFile map[string]interface{} 63 64 func (v *FlagKVFile) String() string { 65 return "" 66 } 67 68 func (v *FlagKVFile) Set(raw string) error { 69 vs, err := loadKVFile(raw) 70 if err != nil { 71 return err 72 } 73 74 if *v == nil { 75 *v = make(map[string]interface{}) 76 } 77 78 for key, value := range vs { 79 (*v)[key] = value 80 } 81 82 return nil 83 } 84 85 func loadKVFile(rawPath string) (map[string]interface{}, error) { 86 path, err := homedir.Expand(rawPath) 87 if err != nil { 88 return nil, fmt.Errorf( 89 "Error expanding path: %s", err) 90 } 91 92 // Read the HCL file and prepare for parsing 93 d, err := ioutil.ReadFile(path) 94 if err != nil { 95 return nil, fmt.Errorf( 96 "Error reading %s: %s", path, err) 97 } 98 99 // Parse it 100 obj, err := hcl.Parse(string(d)) 101 if err != nil { 102 return nil, fmt.Errorf( 103 "Error parsing %s: %s", path, err) 104 } 105 106 var result map[string]interface{} 107 if err := hcl.DecodeObject(&result, obj); err != nil { 108 return nil, fmt.Errorf( 109 "Error decoding Terraform vars file: %s\n\n"+ 110 "The vars file should be in the format of `key = \"value\"`.\n"+ 111 "Decoding errors are usually caused by an invalid format.", 112 err) 113 } 114 115 return result, nil 116 } 117 118 // FlagStringSlice is a flag.Value implementation for parsing targets from the 119 // command line, e.g. -target=aws_instance.foo -target=aws_vpc.bar 120 121 type FlagStringSlice []string 122 123 func (v *FlagStringSlice) String() string { 124 return "" 125 } 126 func (v *FlagStringSlice) Set(raw string) error { 127 *v = append(*v, raw) 128 129 return nil 130 } 131 132 // parseVarFlagAsHCL parses the value of a single variable as would have been specified 133 // on the command line via -var or in an environment variable named TF_VAR_x, where x is 134 // the name of the variable. In order to get around the restriction of HCL requiring a 135 // top level object, we prepend a sentinel key, decode the user-specified value as its 136 // value and pull the value back out of the resulting map. 137 func parseVarFlagAsHCL(input string) (string, interface{}, error) { 138 idx := strings.Index(input, "=") 139 if idx == -1 { 140 return "", nil, fmt.Errorf("No '=' value in variable: %s", input) 141 } 142 probablyName := input[0:idx] 143 144 parsed, err := hcl.Parse(input) 145 if err != nil { 146 // This covers flags of the form `foo=bar` which is not valid HCL 147 // At this point, probablyName is actually the name, and the remainder 148 // of the expression after the equals sign is the value. 149 if regexp.MustCompile(`Unknown token: \d+:\d+ IDENT`).Match([]byte(err.Error())) { 150 value := input[idx+1:] 151 return probablyName, value, nil 152 } 153 return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", probablyName, input, err) 154 } 155 156 var decoded map[string]interface{} 157 if hcl.DecodeObject(&decoded, parsed); err != nil { 158 return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", probablyName, input, err) 159 } 160 161 // Cover cases such as key= 162 if len(decoded) == 0 { 163 return probablyName, "", nil 164 } 165 166 if len(decoded) > 1 { 167 return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", probablyName, input) 168 } 169 170 for k, v := range decoded { 171 return k, v, nil 172 } 173 174 // Should be unreachable 175 return "", nil, fmt.Errorf("No value for variable: %s", input) 176 }