github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/config/interpolate.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/hashicorp/hil/ast" 9 ) 10 11 // An InterpolatedVariable is a variable reference within an interpolation. 12 // 13 // Implementations of this interface represents various sources where 14 // variables can come from: user variables, resources, etc. 15 type InterpolatedVariable interface { 16 FullKey() string 17 } 18 19 // CountVariable is a variable for referencing information about 20 // the count. 21 type CountVariable struct { 22 Type CountValueType 23 key string 24 } 25 26 // CountValueType is the type of the count variable that is referenced. 27 type CountValueType byte 28 29 const ( 30 CountValueInvalid CountValueType = iota 31 CountValueIndex 32 ) 33 34 // A ModuleVariable is a variable that is referencing the output 35 // of a module, such as "${module.foo.bar}" 36 type ModuleVariable struct { 37 Name string 38 Field string 39 key string 40 } 41 42 // A PathVariable is a variable that references path information about the 43 // module. 44 type PathVariable struct { 45 Type PathValueType 46 key string 47 } 48 49 type PathValueType byte 50 51 const ( 52 PathValueInvalid PathValueType = iota 53 PathValueCwd 54 PathValueModule 55 PathValueRoot 56 ) 57 58 // A ResourceVariable is a variable that is referencing the field 59 // of a resource, such as "${aws_instance.foo.ami}" 60 type ResourceVariable struct { 61 Mode ResourceMode 62 Type string // Resource type, i.e. "aws_instance" 63 Name string // Resource name 64 Field string // Resource field 65 66 Multi bool // True if multi-variable: aws_instance.foo.*.id 67 Index int // Index for multi-variable: aws_instance.foo.1.id == 1 68 69 key string 70 } 71 72 // SelfVariable is a variable that is referencing the same resource 73 // it is running on: "${self.address}" 74 type SelfVariable struct { 75 Field string 76 77 key string 78 } 79 80 // SimpleVariable is an unprefixed variable, which can show up when users have 81 // strings they are passing down to resources that use interpolation 82 // internally. The template_file resource is an example of this. 83 type SimpleVariable struct { 84 Key string 85 } 86 87 // TerraformVariable is a "terraform."-prefixed variable used to access 88 // metadata about the Terraform run. 89 type TerraformVariable struct { 90 Field string 91 key string 92 } 93 94 // A UserVariable is a variable that is referencing a user variable 95 // that is inputted from outside the configuration. This looks like 96 // "${var.foo}" 97 type UserVariable struct { 98 Name string 99 Elem string 100 101 key string 102 } 103 104 func NewInterpolatedVariable(v string) (InterpolatedVariable, error) { 105 if strings.HasPrefix(v, "count.") { 106 return NewCountVariable(v) 107 } else if strings.HasPrefix(v, "path.") { 108 return NewPathVariable(v) 109 } else if strings.HasPrefix(v, "self.") { 110 return NewSelfVariable(v) 111 } else if strings.HasPrefix(v, "terraform.") { 112 return NewTerraformVariable(v) 113 } else if strings.HasPrefix(v, "var.") { 114 return NewUserVariable(v) 115 } else if strings.HasPrefix(v, "module.") { 116 return NewModuleVariable(v) 117 } else if !strings.ContainsRune(v, '.') { 118 return NewSimpleVariable(v) 119 } else { 120 return NewResourceVariable(v) 121 } 122 } 123 124 func NewCountVariable(key string) (*CountVariable, error) { 125 var fieldType CountValueType 126 parts := strings.SplitN(key, ".", 2) 127 switch parts[1] { 128 case "index": 129 fieldType = CountValueIndex 130 } 131 132 return &CountVariable{ 133 Type: fieldType, 134 key: key, 135 }, nil 136 } 137 138 func (c *CountVariable) FullKey() string { 139 return c.key 140 } 141 142 func NewModuleVariable(key string) (*ModuleVariable, error) { 143 parts := strings.SplitN(key, ".", 3) 144 if len(parts) < 3 { 145 return nil, fmt.Errorf( 146 "%s: module variables must be three parts: module.name.attr", 147 key) 148 } 149 150 return &ModuleVariable{ 151 Name: parts[1], 152 Field: parts[2], 153 key: key, 154 }, nil 155 } 156 157 func (v *ModuleVariable) FullKey() string { 158 return v.key 159 } 160 161 func (v *ModuleVariable) GoString() string { 162 return fmt.Sprintf("*%#v", *v) 163 } 164 165 func NewPathVariable(key string) (*PathVariable, error) { 166 var fieldType PathValueType 167 parts := strings.SplitN(key, ".", 2) 168 switch parts[1] { 169 case "cwd": 170 fieldType = PathValueCwd 171 case "module": 172 fieldType = PathValueModule 173 case "root": 174 fieldType = PathValueRoot 175 } 176 177 return &PathVariable{ 178 Type: fieldType, 179 key: key, 180 }, nil 181 } 182 183 func (v *PathVariable) FullKey() string { 184 return v.key 185 } 186 187 func NewResourceVariable(key string) (*ResourceVariable, error) { 188 var mode ResourceMode 189 var parts []string 190 if strings.HasPrefix(key, "data.") { 191 mode = DataResourceMode 192 parts = strings.SplitN(key, ".", 4) 193 if len(parts) < 4 { 194 return nil, fmt.Errorf( 195 "%s: data variables must be four parts: data.TYPE.NAME.ATTR", 196 key) 197 } 198 199 // Don't actually need the "data." prefix for parsing, since it's 200 // always constant. 201 parts = parts[1:] 202 } else { 203 mode = ManagedResourceMode 204 parts = strings.SplitN(key, ".", 3) 205 if len(parts) < 3 { 206 return nil, fmt.Errorf( 207 "%s: resource variables must be three parts: TYPE.NAME.ATTR", 208 key) 209 } 210 } 211 212 field := parts[2] 213 multi := false 214 var index int 215 216 if idx := strings.Index(field, "."); idx != -1 { 217 indexStr := field[:idx] 218 multi = indexStr == "*" 219 index = -1 220 221 if !multi { 222 indexInt, err := strconv.ParseInt(indexStr, 0, 0) 223 if err == nil { 224 multi = true 225 index = int(indexInt) 226 } 227 } 228 229 if multi { 230 field = field[idx+1:] 231 } 232 } 233 234 return &ResourceVariable{ 235 Mode: mode, 236 Type: parts[0], 237 Name: parts[1], 238 Field: field, 239 Multi: multi, 240 Index: index, 241 key: key, 242 }, nil 243 } 244 245 func (v *ResourceVariable) ResourceId() string { 246 switch v.Mode { 247 case ManagedResourceMode: 248 return fmt.Sprintf("%s.%s", v.Type, v.Name) 249 case DataResourceMode: 250 return fmt.Sprintf("data.%s.%s", v.Type, v.Name) 251 default: 252 panic(fmt.Errorf("unknown resource mode %s", v.Mode)) 253 } 254 } 255 256 func (v *ResourceVariable) FullKey() string { 257 return v.key 258 } 259 260 func NewSelfVariable(key string) (*SelfVariable, error) { 261 field := key[len("self."):] 262 263 return &SelfVariable{ 264 Field: field, 265 266 key: key, 267 }, nil 268 } 269 270 func (v *SelfVariable) FullKey() string { 271 return v.key 272 } 273 274 func (v *SelfVariable) GoString() string { 275 return fmt.Sprintf("*%#v", *v) 276 } 277 278 func NewSimpleVariable(key string) (*SimpleVariable, error) { 279 return &SimpleVariable{key}, nil 280 } 281 282 func (v *SimpleVariable) FullKey() string { 283 return v.Key 284 } 285 286 func (v *SimpleVariable) GoString() string { 287 return fmt.Sprintf("*%#v", *v) 288 } 289 290 func NewTerraformVariable(key string) (*TerraformVariable, error) { 291 field := key[len("terraform."):] 292 return &TerraformVariable{ 293 Field: field, 294 key: key, 295 }, nil 296 } 297 298 func (v *TerraformVariable) FullKey() string { 299 return v.key 300 } 301 302 func (v *TerraformVariable) GoString() string { 303 return fmt.Sprintf("*%#v", *v) 304 } 305 306 func NewUserVariable(key string) (*UserVariable, error) { 307 name := key[len("var."):] 308 elem := "" 309 if idx := strings.Index(name, "."); idx > -1 { 310 elem = name[idx+1:] 311 name = name[:idx] 312 } 313 314 if len(elem) > 0 { 315 return nil, fmt.Errorf("Invalid dot index found: 'var.%s.%s'. Values in maps and lists can be referenced using square bracket indexing, like: 'var.mymap[\"key\"]' or 'var.mylist[1]'.", name, elem) 316 } 317 318 return &UserVariable{ 319 key: key, 320 321 Name: name, 322 Elem: elem, 323 }, nil 324 } 325 326 func (v *UserVariable) FullKey() string { 327 return v.key 328 } 329 330 func (v *UserVariable) GoString() string { 331 return fmt.Sprintf("*%#v", *v) 332 } 333 334 // DetectVariables takes an AST root and returns all the interpolated 335 // variables that are detected in the AST tree. 336 func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) { 337 var result []InterpolatedVariable 338 var resultErr error 339 340 // Visitor callback 341 fn := func(n ast.Node) ast.Node { 342 if resultErr != nil { 343 return n 344 } 345 346 switch vn := n.(type) { 347 case *ast.VariableAccess: 348 v, err := NewInterpolatedVariable(vn.Name) 349 if err != nil { 350 resultErr = err 351 return n 352 } 353 result = append(result, v) 354 case *ast.Index: 355 if va, ok := vn.Target.(*ast.VariableAccess); ok { 356 v, err := NewInterpolatedVariable(va.Name) 357 if err != nil { 358 resultErr = err 359 return n 360 } 361 result = append(result, v) 362 } 363 if va, ok := vn.Key.(*ast.VariableAccess); ok { 364 v, err := NewInterpolatedVariable(va.Name) 365 if err != nil { 366 resultErr = err 367 return n 368 } 369 result = append(result, v) 370 } 371 default: 372 return n 373 } 374 375 return n 376 } 377 378 // Visitor pattern 379 root.Accept(fn) 380 381 if resultErr != nil { 382 return nil, resultErr 383 } 384 385 return result, nil 386 }