diff --git a/terraform/context.go b/terraform/context.go index 7720bd8ac..925691c5a 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -229,6 +229,21 @@ func (c *Context) ShadowError() error { return c.shadowErr } +// Interpolater returns an Interpolater built on a copy of the state +// that can be used to test interpolation values. +func (c *Context) Interpolater() *Interpolater { + var varLock sync.Mutex + var stateLock sync.RWMutex + return &Interpolater{ + Operation: walkApply, + Module: c.module, + State: c.state.DeepCopy(), + StateLock: &stateLock, + VariableValues: map[string]interface{}{}, + VariableValuesLock: &varLock, + } +} + // Input asks for input to fill variables and provider configurations. // This modifies the configuration in-place, so asking for Input twice // may result in different UI output showing different current values. diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 1c024e466..cba22e466 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -46,6 +46,10 @@ type InterpolationScope struct { func (i *Interpolater) Values( scope *InterpolationScope, vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) { + if scope == nil { + scope = &InterpolationScope{} + } + result := make(map[string]ast.Variable, len(vars)) // Copy the default variables @@ -369,7 +373,12 @@ func (i *Interpolater) computeResourceVariable( // If we're requesting "count" its a special variable that we grab // directly from the config itself. if v.Field == "count" { - count, err := cr.Count() + var count int + if cr != nil { + count, err = cr.Count() + } else { + count, err = i.resourceCountMax(module, cr, v) + } if err != nil { return nil, fmt.Errorf( "Error reading %s count: %s", @@ -380,26 +389,36 @@ func (i *Interpolater) computeResourceVariable( return &ast.Variable{Type: ast.TypeInt, Value: count}, nil } - // If we have no module in the state yet or count, return empty - if module == nil || len(module.Resources) == 0 { - return nil, 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 { - goto MISSING + var r *ResourceState + if module != nil && len(module.Resources) > 0 { + var ok bool + 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 || r.Primary == nil { + if i.Operation == walkApply || i.Operation == walkPlan { + return nil, fmt.Errorf( + "Resource '%s' not found for variable '%s'", + v.ResourceId(), + v.FullKey()) + } + + // If we have no module in the state yet or count, return empty. + // NOTE(@mitchellh): I actually don't know why this is here. During + // a refactor I kept this here to maintain the same behavior, but + // I'm not sure why its here. + if module == nil || len(module.Resources) == 0 { + return nil, nil + } - if r.Primary == nil { goto MISSING } @@ -672,12 +691,6 @@ func (i *Interpolater) resourceVariableInfo( 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)