terraform: extract interpolation to its own struct
This is not really improving the way we do interpolation so much as its just shuffling bits around. I don't want to refactor interpolation in this branch so I needed to make the current way reusable so that I can reuse it in the new Context.
This commit is contained in:
parent
57ad9e2502
commit
76ce6e45f7
|
@ -3,7 +3,6 @@ package terraform
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -1505,125 +1504,30 @@ func (c *walkContext) computeVars(
|
|||
return nil
|
||||
}
|
||||
|
||||
// Copy the default variables
|
||||
vs := make(map[string]ast.Variable)
|
||||
for k, v := range c.defaultVariables {
|
||||
vs[k] = ast.Variable{
|
||||
Value: v,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
// Build the interpolater
|
||||
i := &Interpolater{
|
||||
Operation: c.Operation,
|
||||
Module: c.Context.module,
|
||||
State: c.Context.state,
|
||||
StateLock: &c.Context.sl,
|
||||
Variables: c.Variables,
|
||||
}
|
||||
scope := &InterpolationScope{
|
||||
Path: c.Path,
|
||||
Resource: r,
|
||||
}
|
||||
vs, err := i.Values(scope, raw.Variables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next, the actual computed variables
|
||||
for n, rawV := range raw.Variables {
|
||||
switch v := rawV.(type) {
|
||||
case *config.CountVariable:
|
||||
switch v.Type {
|
||||
case config.CountValueIndex:
|
||||
if r != nil {
|
||||
vs[n] = ast.Variable{
|
||||
Value: int(r.CountIndex),
|
||||
Type: ast.TypeInt,
|
||||
}
|
||||
}
|
||||
}
|
||||
case *config.ModuleVariable:
|
||||
if c.Operation == walkValidate {
|
||||
vs[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
value, err := c.computeModuleVariable(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vs[n] = ast.Variable{
|
||||
Value: value,
|
||||
// Copy the default variables
|
||||
for k, v := range c.defaultVariables {
|
||||
if _, ok := vs[k]; !ok {
|
||||
vs[k] = ast.Variable{
|
||||
Value: v,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case *config.PathVariable:
|
||||
switch v.Type {
|
||||
case config.PathValueCwd:
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Couldn't get cwd for var %s: %s",
|
||||
v.FullKey(), err)
|
||||
}
|
||||
|
||||
vs[n] = ast.Variable{
|
||||
Value: wd,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case config.PathValueModule:
|
||||
if t := c.Context.module.Child(c.Path[1:]); t != nil {
|
||||
vs[n] = ast.Variable{
|
||||
Value: t.Config().Dir,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
case config.PathValueRoot:
|
||||
vs[n] = ast.Variable{
|
||||
Value: c.Context.module.Config().Dir,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
case *config.ResourceVariable:
|
||||
if c.Operation == walkValidate {
|
||||
vs[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var attr string
|
||||
var err error
|
||||
if v.Multi && v.Index == -1 {
|
||||
attr, err = c.computeResourceMultiVariable(v)
|
||||
} else {
|
||||
attr, err = c.computeResourceVariable(v)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vs[n] = ast.Variable{
|
||||
Value: attr,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case *config.UserVariable:
|
||||
val, ok := c.Variables[v.Name]
|
||||
if ok {
|
||||
vs[n] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := vs[n]; !ok && c.Operation == walkValidate {
|
||||
vs[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Look up if we have any variables with this prefix because
|
||||
// those are map overrides. Include those.
|
||||
for k, val := range c.Variables {
|
||||
if strings.HasPrefix(k, v.Name+".") {
|
||||
vs["var."+k] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1631,224 +1535,6 @@ func (c *walkContext) computeVars(
|
|||
return raw.Interpolate(vs)
|
||||
}
|
||||
|
||||
func (c *walkContext) computeModuleVariable(
|
||||
v *config.ModuleVariable) (string, error) {
|
||||
// Build the path to our child
|
||||
path := make([]string, len(c.Path), len(c.Path)+1)
|
||||
copy(path, c.Path)
|
||||
path = append(path, v.Name)
|
||||
|
||||
// Grab some locks
|
||||
c.Context.sl.RLock()
|
||||
defer c.Context.sl.RUnlock()
|
||||
|
||||
// Get that module from our state
|
||||
mod := c.Context.state.ModuleByPath(path)
|
||||
if mod == nil {
|
||||
// If the module doesn't exist, then we can return an empty string.
|
||||
// This happens usually only in Refresh() when we haven't populated
|
||||
// a state. During validation, we semantically verify that all
|
||||
// modules reference other modules, and graph ordering should
|
||||
// ensure that the module is in the state, so if we reach this
|
||||
// point otherwise it really is a panic.
|
||||
return config.UnknownVariableValue, nil
|
||||
}
|
||||
|
||||
value, ok := mod.Outputs[v.Field]
|
||||
if !ok {
|
||||
// Same reasons as the comment above.
|
||||
return config.UnknownVariableValue, nil
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (c *walkContext) computeResourceVariable(
|
||||
v *config.ResourceVariable) (string, error) {
|
||||
id := v.ResourceId()
|
||||
if v.Multi {
|
||||
id = fmt.Sprintf("%s.%d", id, v.Index)
|
||||
}
|
||||
|
||||
c.Context.sl.RLock()
|
||||
defer c.Context.sl.RUnlock()
|
||||
|
||||
// Get the information about this resource variable, and verify
|
||||
// that it exists and such.
|
||||
module, _, err := c.resourceVariableInfo(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If we have no module in the state yet or count, return empty
|
||||
if module == nil || len(module.Resources) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get the resource out from the state. We know the state exists
|
||||
// at this point and if there is a state, we expect there to be a
|
||||
// resource with the given name.
|
||||
r, ok := module.Resources[id]
|
||||
if !ok && v.Multi && v.Index == 0 {
|
||||
r, ok = module.Resources[v.ResourceId()]
|
||||
}
|
||||
if !ok {
|
||||
r = nil
|
||||
}
|
||||
if r == nil {
|
||||
return "", fmt.Errorf(
|
||||
"Resource '%s' not found for variable '%s'",
|
||||
id,
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
if r.Primary == nil {
|
||||
goto MISSING
|
||||
}
|
||||
|
||||
if attr, ok := r.Primary.Attributes[v.Field]; ok {
|
||||
return attr, nil
|
||||
}
|
||||
|
||||
// At apply time, we can't do the "maybe has it" check below
|
||||
// that we need for plans since parent elements might be computed.
|
||||
// Therefore, it is an error and we're missing the key.
|
||||
//
|
||||
// TODO: test by creating a state and configuration that is referencing
|
||||
// a non-existent variable "foo.bar" where the state only has "foo"
|
||||
// and verify plan works, but apply doesn't.
|
||||
if c.Operation == walkApply {
|
||||
goto MISSING
|
||||
}
|
||||
|
||||
// We didn't find the exact field, so lets separate the dots
|
||||
// and see if anything along the way is a computed set. i.e. if
|
||||
// we have "foo.0.bar" as the field, check to see if "foo" is
|
||||
// a computed list. If so, then the whole thing is computed.
|
||||
if parts := strings.Split(v.Field, "."); len(parts) > 1 {
|
||||
for i := 1; i < len(parts); i++ {
|
||||
// Lists and sets make this
|
||||
key := fmt.Sprintf("%s.#", strings.Join(parts[:i], "."))
|
||||
if attr, ok := r.Primary.Attributes[key]; ok {
|
||||
return attr, nil
|
||||
}
|
||||
|
||||
// Maps make this
|
||||
key = fmt.Sprintf("%s", strings.Join(parts[:i], "."))
|
||||
if attr, ok := r.Primary.Attributes[key]; ok {
|
||||
return attr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MISSING:
|
||||
return "", fmt.Errorf(
|
||||
"Resource '%s' does not have attribute '%s' "+
|
||||
"for variable '%s'",
|
||||
id,
|
||||
v.Field,
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
func (c *walkContext) computeResourceMultiVariable(
|
||||
v *config.ResourceVariable) (string, error) {
|
||||
c.Context.sl.RLock()
|
||||
defer c.Context.sl.RUnlock()
|
||||
|
||||
// Get the information about this resource variable, and verify
|
||||
// that it exists and such.
|
||||
module, cr, err := c.resourceVariableInfo(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Get the count so we know how many to iterate over
|
||||
count, err := cr.Count()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"Error reading %s count: %s",
|
||||
v.ResourceId(),
|
||||
err)
|
||||
}
|
||||
|
||||
// If we have no module in the state yet or count, return empty
|
||||
if module == nil || len(module.Resources) == 0 || count == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var values []string
|
||||
for i := 0; i < count; i++ {
|
||||
id := fmt.Sprintf("%s.%d", v.ResourceId(), i)
|
||||
|
||||
// If we're dealing with only a single resource, then the
|
||||
// ID doesn't have a trailing index.
|
||||
if count == 1 {
|
||||
id = v.ResourceId()
|
||||
}
|
||||
|
||||
r, ok := module.Resources[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Primary == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
attr, ok := r.Primary.Attributes[v.Field]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
values = append(values, attr)
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return "", fmt.Errorf(
|
||||
"Resource '%s' does not have attribute '%s' "+
|
||||
"for variable '%s'",
|
||||
v.ResourceId(),
|
||||
v.Field,
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
return strings.Join(values, config.InterpSplitDelim), nil
|
||||
}
|
||||
|
||||
func (c *walkContext) resourceVariableInfo(
|
||||
v *config.ResourceVariable) (*ModuleState, *config.Resource, error) {
|
||||
// Get the module tree that contains our current path. This is
|
||||
// either the current module (path is empty) or a child.
|
||||
var modTree *module.Tree
|
||||
childPath := c.Path[1:len(c.Path)]
|
||||
if len(childPath) == 0 {
|
||||
modTree = c.Context.module
|
||||
} else {
|
||||
modTree = c.Context.module.Child(childPath)
|
||||
}
|
||||
|
||||
// Get the resource from the configuration so we can verify
|
||||
// that the resource is in the configuration and so we can access
|
||||
// the configuration if we need to.
|
||||
var cr *config.Resource
|
||||
for _, r := range modTree.Config().Resources {
|
||||
if r.Id() == v.ResourceId() {
|
||||
cr = r
|
||||
break
|
||||
}
|
||||
}
|
||||
if cr == nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"Resource '%s' not found for variable '%s'",
|
||||
v.ResourceId(),
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
// Get the relevant module
|
||||
module := c.Context.state.ModuleByPath(c.Path)
|
||||
return module, cr, nil
|
||||
}
|
||||
|
||||
type walkInputMeta struct {
|
||||
sync.Mutex
|
||||
|
||||
|
|
|
@ -0,0 +1,423 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
// Interpolater is the structure responsible for determining the values
|
||||
// for interpolations such as `aws_instance.foo.bar`.
|
||||
type Interpolater struct {
|
||||
Operation walkOperation
|
||||
Module *module.Tree
|
||||
State *State
|
||||
StateLock *sync.RWMutex
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
// InterpolationScope is the current scope of execution. This is required
|
||||
// since some variables which are interpolated are dependent on what we're
|
||||
// operating on and where we are.
|
||||
type InterpolationScope struct {
|
||||
Path []string
|
||||
Resource *Resource
|
||||
}
|
||||
|
||||
// Values returns the values for all the variables in the given map.
|
||||
func (i *Interpolater) Values(
|
||||
scope *InterpolationScope,
|
||||
vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) {
|
||||
result := make(map[string]ast.Variable, len(vars))
|
||||
for n, rawV := range vars {
|
||||
var err error
|
||||
switch v := rawV.(type) {
|
||||
case *config.CountVariable:
|
||||
err = i.valueCountVar(scope, n, v, result)
|
||||
case *config.ModuleVariable:
|
||||
err = i.valueModuleVar(scope, n, v, result)
|
||||
case *config.PathVariable:
|
||||
err = i.valuePathVar(scope, n, v, result)
|
||||
case *config.ResourceVariable:
|
||||
err = i.valueResourceVar(scope, n, v, result)
|
||||
case *config.UserVariable:
|
||||
err = i.valueUserVar(scope, n, v, result)
|
||||
default:
|
||||
err = fmt.Errorf("%s: unknown variable type: %T", n, rawV)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (i *Interpolater) valueCountVar(
|
||||
scope *InterpolationScope,
|
||||
n string,
|
||||
v *config.CountVariable,
|
||||
result map[string]ast.Variable) error {
|
||||
switch v.Type {
|
||||
case config.CountValueIndex:
|
||||
result[n] = ast.Variable{
|
||||
Value: scope.Resource.CountIndex,
|
||||
Type: ast.TypeInt,
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s: unknown count type: %#v", n, v.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Interpolater) valueModuleVar(
|
||||
scope *InterpolationScope,
|
||||
n string,
|
||||
v *config.ModuleVariable,
|
||||
result map[string]ast.Variable) error {
|
||||
// If we're computing all dynamic fields, then module vars count
|
||||
// and we mark it as computed.
|
||||
if i.Operation == walkValidate {
|
||||
result[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build the path to the child module we want
|
||||
path := make([]string, len(scope.Path), len(scope.Path)+1)
|
||||
copy(path, scope.Path)
|
||||
path = append(path, v.Name)
|
||||
|
||||
// Grab the lock so that if other interpolations are running or
|
||||
// state is being modified, we'll be safe.
|
||||
i.StateLock.RLock()
|
||||
defer i.StateLock.RUnlock()
|
||||
|
||||
// Get the module where we're looking for the value
|
||||
var value string
|
||||
mod := i.State.ModuleByPath(path)
|
||||
if mod == nil {
|
||||
// If the module doesn't exist, then we can return an empty string.
|
||||
// This happens usually only in Refresh() when we haven't populated
|
||||
// a state. During validation, we semantically verify that all
|
||||
// modules reference other modules, and graph ordering should
|
||||
// ensure that the module is in the state, so if we reach this
|
||||
// point otherwise it really is a panic.
|
||||
value = config.UnknownVariableValue
|
||||
} else {
|
||||
// Get the value from the outputs
|
||||
var ok bool
|
||||
value, ok = mod.Outputs[v.Field]
|
||||
if !ok {
|
||||
// Same reasons as the comment above.
|
||||
value = config.UnknownVariableValue
|
||||
}
|
||||
}
|
||||
|
||||
result[n] = ast.Variable{
|
||||
Value: value,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Interpolater) valuePathVar(
|
||||
scope *InterpolationScope,
|
||||
n string,
|
||||
v *config.PathVariable,
|
||||
result map[string]ast.Variable) error {
|
||||
switch v.Type {
|
||||
case config.PathValueCwd:
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Couldn't get cwd for var %s: %s",
|
||||
v.FullKey(), err)
|
||||
}
|
||||
|
||||
result[n] = ast.Variable{
|
||||
Value: wd,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case config.PathValueModule:
|
||||
if t := i.Module.Child(scope.Path[1:]); t != nil {
|
||||
result[n] = ast.Variable{
|
||||
Value: t.Config().Dir,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
case config.PathValueRoot:
|
||||
result[n] = ast.Variable{
|
||||
Value: i.Module.Config().Dir,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%s: unknown path type: %#v", n, v.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (i *Interpolater) valueResourceVar(
|
||||
scope *InterpolationScope,
|
||||
n string,
|
||||
v *config.ResourceVariable,
|
||||
result map[string]ast.Variable) error {
|
||||
// If we're computing all dynamic fields, then module vars count
|
||||
// and we mark it as computed.
|
||||
if i.Operation == walkValidate {
|
||||
result[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var attr string
|
||||
var err error
|
||||
if v.Multi && v.Index == -1 {
|
||||
attr, err = i.computeResourceMultiVariable(scope, v)
|
||||
} else {
|
||||
attr, err = i.computeResourceVariable(scope, v)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result[n] = ast.Variable{
|
||||
Value: attr,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Interpolater) valueUserVar(
|
||||
scope *InterpolationScope,
|
||||
n string,
|
||||
v *config.UserVariable,
|
||||
result map[string]ast.Variable) error {
|
||||
val, ok := i.Variables[v.Name]
|
||||
if ok {
|
||||
result[n] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := result[n]; !ok && i.Operation == walkValidate {
|
||||
result[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Look up if we have any variables with this prefix because
|
||||
// those are map overrides. Include those.
|
||||
for k, val := range i.Variables {
|
||||
if strings.HasPrefix(k, v.Name+".") {
|
||||
result["var."+k] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Interpolater) computeResourceVariable(
|
||||
scope *InterpolationScope,
|
||||
v *config.ResourceVariable) (string, error) {
|
||||
id := v.ResourceId()
|
||||
if v.Multi {
|
||||
id = fmt.Sprintf("%s.%d", id, v.Index)
|
||||
}
|
||||
|
||||
i.StateLock.RLock()
|
||||
defer i.StateLock.RUnlock()
|
||||
|
||||
// Get the information about this resource variable, and verify
|
||||
// that it exists and such.
|
||||
module, _, err := i.resourceVariableInfo(scope, v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If we have no module in the state yet or count, return empty
|
||||
if module == nil || len(module.Resources) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get the resource out from the state. We know the state exists
|
||||
// at this point and if there is a state, we expect there to be a
|
||||
// resource with the given name.
|
||||
r, ok := module.Resources[id]
|
||||
if !ok && v.Multi && v.Index == 0 {
|
||||
r, ok = module.Resources[v.ResourceId()]
|
||||
}
|
||||
if !ok {
|
||||
r = nil
|
||||
}
|
||||
if r == nil {
|
||||
return "", fmt.Errorf(
|
||||
"Resource '%s' not found for variable '%s'",
|
||||
id,
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
if r.Primary == nil {
|
||||
goto MISSING
|
||||
}
|
||||
|
||||
if attr, ok := r.Primary.Attributes[v.Field]; ok {
|
||||
return attr, nil
|
||||
}
|
||||
|
||||
// At apply time, we can't do the "maybe has it" check below
|
||||
// that we need for plans since parent elements might be computed.
|
||||
// Therefore, it is an error and we're missing the key.
|
||||
//
|
||||
// TODO: test by creating a state and configuration that is referencing
|
||||
// a non-existent variable "foo.bar" where the state only has "foo"
|
||||
// and verify plan works, but apply doesn't.
|
||||
if i.Operation == walkApply {
|
||||
goto MISSING
|
||||
}
|
||||
|
||||
// We didn't find the exact field, so lets separate the dots
|
||||
// and see if anything along the way is a computed set. i.e. if
|
||||
// we have "foo.0.bar" as the field, check to see if "foo" is
|
||||
// a computed list. If so, then the whole thing is computed.
|
||||
if parts := strings.Split(v.Field, "."); len(parts) > 1 {
|
||||
for i := 1; i < len(parts); i++ {
|
||||
// Lists and sets make this
|
||||
key := fmt.Sprintf("%s.#", strings.Join(parts[:i], "."))
|
||||
if attr, ok := r.Primary.Attributes[key]; ok {
|
||||
return attr, nil
|
||||
}
|
||||
|
||||
// Maps make this
|
||||
key = fmt.Sprintf("%s", strings.Join(parts[:i], "."))
|
||||
if attr, ok := r.Primary.Attributes[key]; ok {
|
||||
return attr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MISSING:
|
||||
return "", fmt.Errorf(
|
||||
"Resource '%s' does not have attribute '%s' "+
|
||||
"for variable '%s'",
|
||||
id,
|
||||
v.Field,
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
func (i *Interpolater) computeResourceMultiVariable(
|
||||
scope *InterpolationScope,
|
||||
v *config.ResourceVariable) (string, error) {
|
||||
i.StateLock.RLock()
|
||||
defer i.StateLock.RUnlock()
|
||||
|
||||
// Get the information about this resource variable, and verify
|
||||
// that it exists and such.
|
||||
module, cr, err := i.resourceVariableInfo(scope, v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Get the count so we know how many to iterate over
|
||||
count, err := cr.Count()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"Error reading %s count: %s",
|
||||
v.ResourceId(),
|
||||
err)
|
||||
}
|
||||
|
||||
// If we have no module in the state yet or count, return empty
|
||||
if module == nil || len(module.Resources) == 0 || count == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var values []string
|
||||
for i := 0; i < count; i++ {
|
||||
id := fmt.Sprintf("%s.%d", v.ResourceId(), i)
|
||||
|
||||
// If we're dealing with only a single resource, then the
|
||||
// ID doesn't have a trailing index.
|
||||
if count == 1 {
|
||||
id = v.ResourceId()
|
||||
}
|
||||
|
||||
r, ok := module.Resources[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Primary == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
attr, ok := r.Primary.Attributes[v.Field]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
values = append(values, attr)
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return "", fmt.Errorf(
|
||||
"Resource '%s' does not have attribute '%s' "+
|
||||
"for variable '%s'",
|
||||
v.ResourceId(),
|
||||
v.Field,
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
return strings.Join(values, config.InterpSplitDelim), nil
|
||||
}
|
||||
|
||||
func (i *Interpolater) resourceVariableInfo(
|
||||
scope *InterpolationScope,
|
||||
v *config.ResourceVariable) (*ModuleState, *config.Resource, error) {
|
||||
// Get the module tree that contains our current path. This is
|
||||
// either the current module (path is empty) or a child.
|
||||
modTree := i.Module
|
||||
if len(scope.Path) > 1 {
|
||||
modTree = i.Module.Child(scope.Path[1:])
|
||||
}
|
||||
|
||||
// Get the resource from the configuration so we can verify
|
||||
// that the resource is in the configuration and so we can access
|
||||
// the configuration if we need to.
|
||||
var cr *config.Resource
|
||||
for _, r := range modTree.Config().Resources {
|
||||
if r.Id() == v.ResourceId() {
|
||||
cr = r
|
||||
break
|
||||
}
|
||||
}
|
||||
if cr == nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"Resource '%s' not found for variable '%s'",
|
||||
v.ResourceId(),
|
||||
v.FullKey())
|
||||
}
|
||||
|
||||
// Get the relevant module
|
||||
module := i.State.ModuleByPath(scope.Path)
|
||||
return module, cr, nil
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
func TestInterpolater_countIndex(t *testing.T) {
|
||||
i := &Interpolater{}
|
||||
|
||||
scope := &InterpolationScope{
|
||||
Path: rootModulePath,
|
||||
Resource: &Resource{CountIndex: 42},
|
||||
}
|
||||
|
||||
testInterpolate(t, i, scope, "count.index", ast.Variable{
|
||||
Value: 42,
|
||||
Type: ast.TypeInt,
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolater_moduleVariable(t *testing.T) {
|
||||
lock := new(sync.RWMutex)
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&ModuleState{
|
||||
Path: []string{RootModuleName, "child"},
|
||||
Outputs: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
i := &Interpolater{
|
||||
State: state,
|
||||
StateLock: lock,
|
||||
}
|
||||
|
||||
scope := &InterpolationScope{
|
||||
Path: rootModulePath,
|
||||
}
|
||||
|
||||
testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
|
||||
Value: "bar",
|
||||
Type: ast.TypeString,
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolater_pathCwd(t *testing.T) {
|
||||
i := &Interpolater{}
|
||||
scope := &InterpolationScope{}
|
||||
|
||||
expected, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testInterpolate(t, i, scope, "path.cwd", ast.Variable{
|
||||
Value: expected,
|
||||
Type: ast.TypeString,
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolater_pathModule(t *testing.T) {
|
||||
mod := testModule(t, "interpolate-path-module")
|
||||
i := &Interpolater{
|
||||
Module: mod,
|
||||
}
|
||||
scope := &InterpolationScope{
|
||||
Path: []string{RootModuleName, "child"},
|
||||
}
|
||||
|
||||
path := mod.Child([]string{"child"}).Config().Dir
|
||||
testInterpolate(t, i, scope, "path.module", ast.Variable{
|
||||
Value: path,
|
||||
Type: ast.TypeString,
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolater_pathRoot(t *testing.T) {
|
||||
mod := testModule(t, "interpolate-path-module")
|
||||
i := &Interpolater{
|
||||
Module: mod,
|
||||
}
|
||||
scope := &InterpolationScope{
|
||||
Path: []string{RootModuleName, "child"},
|
||||
}
|
||||
|
||||
path := mod.Config().Dir
|
||||
testInterpolate(t, i, scope, "path.root", ast.Variable{
|
||||
Value: path,
|
||||
Type: ast.TypeString,
|
||||
})
|
||||
}
|
||||
|
||||
func testInterpolate(
|
||||
t *testing.T, i *Interpolater,
|
||||
scope *InterpolationScope,
|
||||
n string, expectedVar ast.Variable) {
|
||||
v, err := config.NewInterpolatedVariable(n)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
|
||||
"foo": v,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := map[string]ast.Variable{
|
||||
"foo": expectedVar,
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue