mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
436 lines
9.2 KiB
Go
436 lines
9.2 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
|
|
}
|
|
|
|
// 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
|
|
}
|