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