mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
ccb328cc1f
Having a reference to the originating source range will allow us to generate decent error messages if certain references can't be resolved at interpolation time. This is not yet populated or used. It will never be populated nor used by the current HCL/HIL-based interpolation path, but will be used by the experimental HCL2-based interpolation path to give it the necessary info to produce diagnostics.
440 lines
9.3 KiB
Go
440 lines
9.3 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
|
)
|
|
|
|
// An InterpolatedVariable is a variable reference within an interpolation.
|
|
//
|
|
// Implementations of this interface represents various sources where
|
|
// variables can come from: user variables, resources, etc.
|
|
type InterpolatedVariable interface {
|
|
FullKey() string
|
|
SourceRange() tfdiags.SourceRange
|
|
}
|
|
|
|
// varRange can be embedded into an InterpolatedVariable implementation to
|
|
// implement the SourceRange method.
|
|
type varRange struct {
|
|
rng tfdiags.SourceRange
|
|
}
|
|
|
|
func (r varRange) SourceRange() tfdiags.SourceRange {
|
|
return r.rng
|
|
}
|
|
|
|
func makeVarRange(rng tfdiags.SourceRange) varRange {
|
|
return varRange{rng}
|
|
}
|
|
|
|
// CountVariable is a variable for referencing information about
|
|
// the count.
|
|
type CountVariable struct {
|
|
Type CountValueType
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
// CountValueType is the type of the count variable that is referenced.
|
|
type CountValueType byte
|
|
|
|
const (
|
|
CountValueInvalid CountValueType = iota
|
|
CountValueIndex
|
|
)
|
|
|
|
// A ModuleVariable is a variable that is referencing the output
|
|
// of a module, such as "${module.foo.bar}"
|
|
type ModuleVariable struct {
|
|
Name string
|
|
Field string
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
// A PathVariable is a variable that references path information about the
|
|
// module.
|
|
type PathVariable struct {
|
|
Type PathValueType
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
type PathValueType byte
|
|
|
|
const (
|
|
PathValueInvalid PathValueType = iota
|
|
PathValueCwd
|
|
PathValueModule
|
|
PathValueRoot
|
|
)
|
|
|
|
// A ResourceVariable is a variable that is referencing the field
|
|
// of a resource, such as "${aws_instance.foo.ami}"
|
|
type ResourceVariable struct {
|
|
Mode ResourceMode
|
|
Type string // Resource type, i.e. "aws_instance"
|
|
Name string // Resource name
|
|
Field string // Resource field
|
|
|
|
Multi bool // True if multi-variable: aws_instance.foo.*.id
|
|
Index int // Index for multi-variable: aws_instance.foo.1.id == 1
|
|
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
// SelfVariable is a variable that is referencing the same resource
|
|
// it is running on: "${self.address}"
|
|
type SelfVariable struct {
|
|
Field string
|
|
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
// SimpleVariable is an unprefixed variable, which can show up when users have
|
|
// strings they are passing down to resources that use interpolation
|
|
// internally. The template_file resource is an example of this.
|
|
type SimpleVariable struct {
|
|
Key string
|
|
varRange
|
|
}
|
|
|
|
// TerraformVariable is a "terraform."-prefixed variable used to access
|
|
// metadata about the Terraform run.
|
|
type TerraformVariable struct {
|
|
Field string
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
// A UserVariable is a variable that is referencing a user variable
|
|
// that is inputted from outside the configuration. This looks like
|
|
// "${var.foo}"
|
|
type UserVariable struct {
|
|
Name string
|
|
Elem string
|
|
|
|
key string
|
|
varRange
|
|
}
|
|
|
|
// A LocalVariable is a variable that references a local value defined within
|
|
// the current module, via a "locals" block. This looks like "${local.foo}".
|
|
type LocalVariable struct {
|
|
Name string
|
|
varRange
|
|
}
|
|
|
|
func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
|
|
if strings.HasPrefix(v, "count.") {
|
|
return NewCountVariable(v)
|
|
} else if strings.HasPrefix(v, "path.") {
|
|
return NewPathVariable(v)
|
|
} else if strings.HasPrefix(v, "self.") {
|
|
return NewSelfVariable(v)
|
|
} else if strings.HasPrefix(v, "terraform.") {
|
|
return NewTerraformVariable(v)
|
|
} else if strings.HasPrefix(v, "var.") {
|
|
return NewUserVariable(v)
|
|
} else if strings.HasPrefix(v, "local.") {
|
|
return NewLocalVariable(v)
|
|
} else if strings.HasPrefix(v, "module.") {
|
|
return NewModuleVariable(v)
|
|
} else if !strings.ContainsRune(v, '.') {
|
|
return NewSimpleVariable(v)
|
|
} else {
|
|
return NewResourceVariable(v)
|
|
}
|
|
}
|
|
|
|
func NewCountVariable(key string) (*CountVariable, error) {
|
|
var fieldType CountValueType
|
|
parts := strings.SplitN(key, ".", 2)
|
|
switch parts[1] {
|
|
case "index":
|
|
fieldType = CountValueIndex
|
|
}
|
|
|
|
return &CountVariable{
|
|
Type: fieldType,
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (c *CountVariable) FullKey() string {
|
|
return c.key
|
|
}
|
|
|
|
func NewModuleVariable(key string) (*ModuleVariable, error) {
|
|
parts := strings.SplitN(key, ".", 3)
|
|
if len(parts) < 3 {
|
|
return nil, fmt.Errorf(
|
|
"%s: module variables must be three parts: module.name.attr",
|
|
key)
|
|
}
|
|
|
|
return &ModuleVariable{
|
|
Name: parts[1],
|
|
Field: parts[2],
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (v *ModuleVariable) FullKey() string {
|
|
return v.key
|
|
}
|
|
|
|
func (v *ModuleVariable) GoString() string {
|
|
return fmt.Sprintf("*%#v", *v)
|
|
}
|
|
|
|
func NewPathVariable(key string) (*PathVariable, error) {
|
|
var fieldType PathValueType
|
|
parts := strings.SplitN(key, ".", 2)
|
|
switch parts[1] {
|
|
case "cwd":
|
|
fieldType = PathValueCwd
|
|
case "module":
|
|
fieldType = PathValueModule
|
|
case "root":
|
|
fieldType = PathValueRoot
|
|
}
|
|
|
|
return &PathVariable{
|
|
Type: fieldType,
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (v *PathVariable) FullKey() string {
|
|
return v.key
|
|
}
|
|
|
|
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
|
var mode ResourceMode
|
|
var parts []string
|
|
if strings.HasPrefix(key, "data.") {
|
|
mode = DataResourceMode
|
|
parts = strings.SplitN(key, ".", 4)
|
|
if len(parts) < 4 {
|
|
return nil, fmt.Errorf(
|
|
"%s: data variables must be four parts: data.TYPE.NAME.ATTR",
|
|
key)
|
|
}
|
|
|
|
// Don't actually need the "data." prefix for parsing, since it's
|
|
// always constant.
|
|
parts = parts[1:]
|
|
} else {
|
|
mode = ManagedResourceMode
|
|
parts = strings.SplitN(key, ".", 3)
|
|
if len(parts) < 3 {
|
|
return nil, fmt.Errorf(
|
|
"%s: resource variables must be three parts: TYPE.NAME.ATTR",
|
|
key)
|
|
}
|
|
}
|
|
|
|
field := parts[2]
|
|
multi := false
|
|
var index int
|
|
|
|
if idx := strings.Index(field, "."); idx != -1 {
|
|
indexStr := field[:idx]
|
|
multi = indexStr == "*"
|
|
index = -1
|
|
|
|
if !multi {
|
|
indexInt, err := strconv.ParseInt(indexStr, 0, 0)
|
|
if err == nil {
|
|
multi = true
|
|
index = int(indexInt)
|
|
}
|
|
}
|
|
|
|
if multi {
|
|
field = field[idx+1:]
|
|
}
|
|
}
|
|
|
|
return &ResourceVariable{
|
|
Mode: mode,
|
|
Type: parts[0],
|
|
Name: parts[1],
|
|
Field: field,
|
|
Multi: multi,
|
|
Index: index,
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (v *ResourceVariable) ResourceId() string {
|
|
switch v.Mode {
|
|
case ManagedResourceMode:
|
|
return fmt.Sprintf("%s.%s", v.Type, v.Name)
|
|
case DataResourceMode:
|
|
return fmt.Sprintf("data.%s.%s", v.Type, v.Name)
|
|
default:
|
|
panic(fmt.Errorf("unknown resource mode %s", v.Mode))
|
|
}
|
|
}
|
|
|
|
func (v *ResourceVariable) FullKey() string {
|
|
return v.key
|
|
}
|
|
|
|
func NewSelfVariable(key string) (*SelfVariable, error) {
|
|
field := key[len("self."):]
|
|
|
|
return &SelfVariable{
|
|
Field: field,
|
|
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (v *SelfVariable) FullKey() string {
|
|
return v.key
|
|
}
|
|
|
|
func (v *SelfVariable) GoString() string {
|
|
return fmt.Sprintf("*%#v", *v)
|
|
}
|
|
|
|
func NewSimpleVariable(key string) (*SimpleVariable, error) {
|
|
return &SimpleVariable{Key: key}, nil
|
|
}
|
|
|
|
func (v *SimpleVariable) FullKey() string {
|
|
return v.Key
|
|
}
|
|
|
|
func (v *SimpleVariable) GoString() string {
|
|
return fmt.Sprintf("*%#v", *v)
|
|
}
|
|
|
|
func NewTerraformVariable(key string) (*TerraformVariable, error) {
|
|
field := key[len("terraform."):]
|
|
return &TerraformVariable{
|
|
Field: field,
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (v *TerraformVariable) FullKey() string {
|
|
return v.key
|
|
}
|
|
|
|
func (v *TerraformVariable) GoString() string {
|
|
return fmt.Sprintf("*%#v", *v)
|
|
}
|
|
|
|
func NewUserVariable(key string) (*UserVariable, error) {
|
|
name := key[len("var."):]
|
|
elem := ""
|
|
if idx := strings.Index(name, "."); idx > -1 {
|
|
elem = name[idx+1:]
|
|
name = name[:idx]
|
|
}
|
|
|
|
if len(elem) > 0 {
|
|
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)
|
|
}
|
|
|
|
return &UserVariable{
|
|
key: key,
|
|
|
|
Name: name,
|
|
Elem: elem,
|
|
}, nil
|
|
}
|
|
|
|
func (v *UserVariable) FullKey() string {
|
|
return v.key
|
|
}
|
|
|
|
func (v *UserVariable) GoString() string {
|
|
return fmt.Sprintf("*%#v", *v)
|
|
}
|
|
|
|
func NewLocalVariable(key string) (*LocalVariable, error) {
|
|
name := key[len("local."):]
|
|
if idx := strings.Index(name, "."); idx > -1 {
|
|
return nil, fmt.Errorf("Can't use dot (.) attribute access in local.%s; use square bracket indexing", name)
|
|
}
|
|
|
|
return &LocalVariable{
|
|
Name: name,
|
|
}, nil
|
|
}
|
|
|
|
func (v *LocalVariable) FullKey() string {
|
|
return fmt.Sprintf("local.%s", v.Name)
|
|
}
|
|
|
|
func (v *LocalVariable) GoString() string {
|
|
return fmt.Sprintf("*%#v", *v)
|
|
}
|
|
|
|
// DetectVariables takes an AST root and returns all the interpolated
|
|
// variables that are detected in the AST tree.
|
|
func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) {
|
|
var result []InterpolatedVariable
|
|
var resultErr error
|
|
|
|
// Visitor callback
|
|
fn := func(n ast.Node) ast.Node {
|
|
if resultErr != nil {
|
|
return n
|
|
}
|
|
|
|
switch vn := n.(type) {
|
|
case *ast.VariableAccess:
|
|
v, err := NewInterpolatedVariable(vn.Name)
|
|
if err != nil {
|
|
resultErr = err
|
|
return n
|
|
}
|
|
result = append(result, v)
|
|
case *ast.Index:
|
|
if va, ok := vn.Target.(*ast.VariableAccess); ok {
|
|
v, err := NewInterpolatedVariable(va.Name)
|
|
if err != nil {
|
|
resultErr = err
|
|
return n
|
|
}
|
|
result = append(result, v)
|
|
}
|
|
if va, ok := vn.Key.(*ast.VariableAccess); ok {
|
|
v, err := NewInterpolatedVariable(va.Name)
|
|
if err != nil {
|
|
resultErr = err
|
|
return n
|
|
}
|
|
result = append(result, v)
|
|
}
|
|
default:
|
|
return n
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
// Visitor pattern
|
|
root.Accept(fn)
|
|
|
|
if resultErr != nil {
|
|
return nil, resultErr
|
|
}
|
|
|
|
return result, nil
|
|
}
|