Merge pull request #22846 from hashicorp/jbardin/evaluate-resource
Always evaluate resources in their entirety
This commit is contained in:
commit
1ee851f256
|
@ -290,7 +290,7 @@ func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Tra
|
||||||
// of the resource, but we don't have enough context here to decide
|
// of the resource, but we don't have enough context here to decide
|
||||||
// so we'll let the caller resolve that ambiguity.
|
// so we'll let the caller resolve that ambiguity.
|
||||||
return &Reference{
|
return &Reference{
|
||||||
Subject: resourceInstAddr,
|
Subject: resourceAddr,
|
||||||
SourceRange: tfdiags.SourceRangeFromHCL(rng),
|
SourceRange: tfdiags.SourceRangeFromHCL(rng),
|
||||||
}, diags
|
}, diags
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,12 +114,10 @@ func TestParseRef(t *testing.T) {
|
||||||
{
|
{
|
||||||
`data.external.foo`,
|
`data.external.foo`,
|
||||||
&Reference{
|
&Reference{
|
||||||
Subject: ResourceInstance{
|
Subject: Resource{
|
||||||
Resource: Resource{
|
Mode: DataResourceMode,
|
||||||
Mode: DataResourceMode,
|
Type: "external",
|
||||||
Type: "external",
|
Name: "foo",
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
SourceRange: tfdiags.SourceRange{
|
SourceRange: tfdiags.SourceRange{
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
@ -592,12 +590,10 @@ func TestParseRef(t *testing.T) {
|
||||||
{
|
{
|
||||||
`boop_instance.foo`,
|
`boop_instance.foo`,
|
||||||
&Reference{
|
&Reference{
|
||||||
Subject: ResourceInstance{
|
Subject: Resource{
|
||||||
Resource: Resource{
|
Mode: ManagedResourceMode,
|
||||||
Mode: ManagedResourceMode,
|
Type: "boop_instance",
|
||||||
Type: "boop_instance",
|
Name: "foo",
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
SourceRange: tfdiags.SourceRange{
|
SourceRange: tfdiags.SourceRange{
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
|
|
@ -75,28 +75,27 @@ func (d analysisData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceR
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d analysisData) GetResourceInstance(instAddr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d analysisData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
log.Printf("[TRACE] configupgrade: Determining type for %s", instAddr)
|
log.Printf("[TRACE] configupgrade: Determining type for %s", addr)
|
||||||
addr := instAddr.Resource
|
|
||||||
|
|
||||||
// Our analysis pass should've found a suitable schema for every resource
|
// Our analysis pass should've found a suitable schema for every resource
|
||||||
// type in the module.
|
// type in the module.
|
||||||
providerType, ok := d.an.ResourceProviderType[addr]
|
providerType, ok := d.an.ResourceProviderType[addr]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Should not be possible, since analysis visits every resource block.
|
// Should not be possible, since analysis visits every resource block.
|
||||||
log.Printf("[TRACE] configupgrade: analysis.GetResourceInstance doesn't have a provider type for %s", addr)
|
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider type for %s", addr)
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
providerSchema, ok := d.an.ProviderSchemas[providerType]
|
providerSchema, ok := d.an.ProviderSchemas[providerType]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Should not be possible, since analysis loads schema for every provider.
|
// Should not be possible, since analysis loads schema for every provider.
|
||||||
log.Printf("[TRACE] configupgrade: analysis.GetResourceInstance doesn't have a provider schema for for %q", providerType)
|
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider schema for for %q", providerType)
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
schema, _ := providerSchema.SchemaForResourceAddr(addr)
|
schema, _ := providerSchema.SchemaForResourceAddr(addr)
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
// Should not be possible, since analysis loads schema for every provider.
|
// Should not be possible, since analysis loads schema for every provider.
|
||||||
log.Printf("[TRACE] configupgrade: analysis.GetResourceInstance doesn't have a schema for for %s", addr)
|
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a schema for for %s", addr)
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,19 +105,11 @@ func (d analysisData) GetResourceInstance(instAddr addrs.ResourceInstance, rng t
|
||||||
// return a list or a single object type depending on whether count is
|
// return a list or a single object type depending on whether count is
|
||||||
// set and whether an instance key is given in the address.
|
// set and whether an instance key is given in the address.
|
||||||
if d.an.ResourceHasCount[addr] {
|
if d.an.ResourceHasCount[addr] {
|
||||||
if instAddr.Key == addrs.NoKey {
|
log.Printf("[TRACE] configupgrade: %s refers to counted instance, so result is a list of %#v", addr, objTy)
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to counted instance without a key, so result is a list of %#v", instAddr, objTy)
|
return cty.UnknownVal(cty.List(objTy)), nil
|
||||||
return cty.UnknownVal(cty.List(objTy)), nil
|
|
||||||
}
|
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to counted instance with a key, so result is single object", instAddr)
|
|
||||||
return cty.UnknownVal(objTy), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if instAddr.Key != addrs.NoKey {
|
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance, so result is single object", addr)
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance with a key, which is invalid", instAddr)
|
|
||||||
return cty.DynamicVal, nil
|
|
||||||
}
|
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance without a key, so result is single object", instAddr)
|
|
||||||
return cty.UnknownVal(objTy), nil
|
return cty.UnknownVal(objTy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ type Data interface {
|
||||||
|
|
||||||
GetCountAttr(addrs.CountAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
GetCountAttr(addrs.CountAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||||
GetForEachAttr(addrs.ForEachAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
GetForEachAttr(addrs.ForEachAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||||
GetResourceInstance(addrs.ResourceInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
GetResource(addrs.Resource, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||||
GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||||
GetModuleInstance(addrs.ModuleCallInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
GetModuleInstance(addrs.ModuleCallInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||||
GetModuleInstanceOutput(addrs.ModuleCallOutput, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
GetModuleInstanceOutput(addrs.ModuleCallOutput, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||||
|
|
|
@ -7,14 +7,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type dataForTests struct {
|
type dataForTests struct {
|
||||||
CountAttrs map[string]cty.Value
|
CountAttrs map[string]cty.Value
|
||||||
ForEachAttrs map[string]cty.Value
|
ForEachAttrs map[string]cty.Value
|
||||||
ResourceInstances map[string]cty.Value
|
Resources map[string]cty.Value
|
||||||
LocalValues map[string]cty.Value
|
LocalValues map[string]cty.Value
|
||||||
Modules map[string]cty.Value
|
Modules map[string]cty.Value
|
||||||
PathAttrs map[string]cty.Value
|
PathAttrs map[string]cty.Value
|
||||||
TerraformAttrs map[string]cty.Value
|
TerraformAttrs map[string]cty.Value
|
||||||
InputVariables map[string]cty.Value
|
InputVariables map[string]cty.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Data = &dataForTests{}
|
var _ Data = &dataForTests{}
|
||||||
|
@ -31,8 +31,8 @@ func (d *dataForTests) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.Source
|
||||||
return d.ForEachAttrs[addr.Name], nil
|
return d.ForEachAttrs[addr.Name], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataForTests) GetResourceInstance(addr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d *dataForTests) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
return d.ResourceInstances[addr.String()], nil
|
return d.Resources[addr.String()], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataForTests) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d *dataForTests) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
|
|
94
lang/eval.go
94
lang/eval.go
|
@ -194,8 +194,8 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||||
// it, since that allows us to gather a full set of any errors and
|
// it, since that allows us to gather a full set of any errors and
|
||||||
// warnings, but once we've gathered all the data we'll then skip anything
|
// warnings, but once we've gathered all the data we'll then skip anything
|
||||||
// that's redundant in the process of populating our values map.
|
// that's redundant in the process of populating our values map.
|
||||||
dataResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{}
|
dataResources := map[string]map[string]cty.Value{}
|
||||||
managedResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{}
|
managedResources := map[string]map[string]cty.Value{}
|
||||||
wholeModules := map[string]map[addrs.InstanceKey]cty.Value{}
|
wholeModules := map[string]map[addrs.InstanceKey]cty.Value{}
|
||||||
moduleOutputs := map[string]map[addrs.InstanceKey]map[string]cty.Value{}
|
moduleOutputs := map[string]map[addrs.InstanceKey]map[string]cty.Value{}
|
||||||
inputVariables := map[string]cty.Value{}
|
inputVariables := map[string]cty.Value{}
|
||||||
|
@ -208,7 +208,6 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||||
|
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
rng := ref.SourceRange
|
rng := ref.SourceRange
|
||||||
isSelf := false
|
|
||||||
|
|
||||||
rawSubj := ref.Subject
|
rawSubj := ref.Subject
|
||||||
if rawSubj == addrs.Self {
|
if rawSubj == addrs.Self {
|
||||||
|
@ -226,45 +225,60 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Treat "self" as an alias for the configured self address.
|
if selfAddr == addrs.Self {
|
||||||
rawSubj = selfAddr
|
|
||||||
isSelf = true
|
|
||||||
|
|
||||||
if rawSubj == addrs.Self {
|
|
||||||
// Programming error: the self address cannot alias itself.
|
// Programming error: the self address cannot alias itself.
|
||||||
panic("scope SelfAddr attempting to alias itself")
|
panic("scope SelfAddr attempting to alias itself")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// self can only be used within a resource instance
|
||||||
|
subj := selfAddr.(addrs.ResourceInstance)
|
||||||
|
|
||||||
|
val, valDiags := normalizeRefValue(s.Data.GetResource(subj.ContainingResource(), rng))
|
||||||
|
|
||||||
|
diags = diags.Append(valDiags)
|
||||||
|
|
||||||
|
// Self is an exception in that it must always resolve to a
|
||||||
|
// particular instance. We will still insert the full resource into
|
||||||
|
// the context below.
|
||||||
|
switch k := subj.Key.(type) {
|
||||||
|
case addrs.IntKey:
|
||||||
|
self = val.Index(cty.NumberIntVal(int64(k)))
|
||||||
|
case addrs.StringKey:
|
||||||
|
self = val.Index(cty.StringVal(string(k)))
|
||||||
|
default:
|
||||||
|
self = val
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type switch must cover all of the "Referenceable" implementations
|
// This type switch must cover all of the "Referenceable" implementations
|
||||||
// in package addrs.
|
// in package addrs, however we are removing the possibility of
|
||||||
switch subj := rawSubj.(type) {
|
// ResourceInstance beforehand.
|
||||||
|
if addr, ok := rawSubj.(addrs.ResourceInstance); ok {
|
||||||
|
rawSubj = addr.ContainingResource()
|
||||||
|
}
|
||||||
|
|
||||||
case addrs.ResourceInstance:
|
switch subj := rawSubj.(type) {
|
||||||
var into map[string]map[string]map[addrs.InstanceKey]cty.Value
|
case addrs.Resource:
|
||||||
switch subj.Resource.Mode {
|
var into map[string]map[string]cty.Value
|
||||||
|
switch subj.Mode {
|
||||||
case addrs.ManagedResourceMode:
|
case addrs.ManagedResourceMode:
|
||||||
into = managedResources
|
into = managedResources
|
||||||
case addrs.DataResourceMode:
|
case addrs.DataResourceMode:
|
||||||
into = dataResources
|
into = dataResources
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unsupported ResourceMode %s", subj.Resource.Mode))
|
panic(fmt.Errorf("unsupported ResourceMode %s", subj.Mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetResource(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
|
|
||||||
r := subj.Resource
|
r := subj
|
||||||
if into[r.Type] == nil {
|
if into[r.Type] == nil {
|
||||||
into[r.Type] = make(map[string]map[addrs.InstanceKey]cty.Value)
|
into[r.Type] = make(map[string]cty.Value)
|
||||||
}
|
|
||||||
if into[r.Type][r.Name] == nil {
|
|
||||||
into[r.Type][r.Name] = make(map[addrs.InstanceKey]cty.Value)
|
|
||||||
}
|
|
||||||
into[r.Type][r.Name][subj.Key] = val
|
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
}
|
||||||
|
into[r.Type][r.Name] = val
|
||||||
|
|
||||||
case addrs.ModuleCallInstance:
|
case addrs.ModuleCallInstance:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng))
|
||||||
|
@ -274,9 +288,6 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||||
wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value)
|
wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value)
|
||||||
}
|
}
|
||||||
wholeModules[subj.Call.Name][subj.Key] = val
|
wholeModules[subj.Call.Name][subj.Key] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.ModuleCallOutput:
|
case addrs.ModuleCallOutput:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng))
|
||||||
|
@ -291,57 +302,36 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||||
moduleOutputs[callName][callKey] = make(map[string]cty.Value)
|
moduleOutputs[callName][callKey] = make(map[string]cty.Value)
|
||||||
}
|
}
|
||||||
moduleOutputs[callName][callKey][subj.Name] = val
|
moduleOutputs[callName][callKey][subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.InputVariable:
|
case addrs.InputVariable:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
inputVariables[subj.Name] = val
|
inputVariables[subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.LocalValue:
|
case addrs.LocalValue:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
localValues[subj.Name] = val
|
localValues[subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.PathAttr:
|
case addrs.PathAttr:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
pathAttrs[subj.Name] = val
|
pathAttrs[subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.TerraformAttr:
|
case addrs.TerraformAttr:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
terraformAttrs[subj.Name] = val
|
terraformAttrs[subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.CountAttr:
|
case addrs.CountAttr:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
countAttrs[subj.Name] = val
|
countAttrs[subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
case addrs.ForEachAttr:
|
case addrs.ForEachAttr:
|
||||||
val, valDiags := normalizeRefValue(s.Data.GetForEachAttr(subj, rng))
|
val, valDiags := normalizeRefValue(s.Data.GetForEachAttr(subj, rng))
|
||||||
diags = diags.Append(valDiags)
|
diags = diags.Append(valDiags)
|
||||||
forEachAttrs[subj.Name] = val
|
forEachAttrs[subj.Name] = val
|
||||||
if isSelf {
|
|
||||||
self = val
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Should never happen
|
// Should never happen
|
||||||
|
@ -367,13 +357,9 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||||
return ctx, diags
|
return ctx, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildResourceObjects(resources map[string]map[string]map[addrs.InstanceKey]cty.Value) map[string]cty.Value {
|
func buildResourceObjects(resources map[string]map[string]cty.Value) map[string]cty.Value {
|
||||||
vals := make(map[string]cty.Value)
|
vals := make(map[string]cty.Value)
|
||||||
for typeName, names := range resources {
|
for typeName, nameVals := range resources {
|
||||||
nameVals := make(map[string]cty.Value)
|
|
||||||
for name, keys := range names {
|
|
||||||
nameVals[name] = buildInstanceObjects(keys)
|
|
||||||
}
|
|
||||||
vals[typeName] = cty.ObjectVal(nameVals)
|
vals[typeName] = cty.ObjectVal(nameVals)
|
||||||
}
|
}
|
||||||
return vals
|
return vals
|
||||||
|
|
|
@ -24,7 +24,7 @@ func TestScopeEvalContext(t *testing.T) {
|
||||||
"key": cty.StringVal("a"),
|
"key": cty.StringVal("a"),
|
||||||
"value": cty.NumberIntVal(1),
|
"value": cty.NumberIntVal(1),
|
||||||
},
|
},
|
||||||
ResourceInstances: map[string]cty.Value{
|
Resources: map[string]cty.Value{
|
||||||
"null_resource.foo": cty.ObjectVal(map[string]cty.Value{
|
"null_resource.foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
"attr": cty.StringVal("bar"),
|
"attr": cty.StringVal("bar"),
|
||||||
}),
|
}),
|
||||||
|
@ -39,6 +39,14 @@ func TestScopeEvalContext(t *testing.T) {
|
||||||
"attr": cty.StringVal("multi1"),
|
"attr": cty.StringVal("multi1"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"null_resource.each": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"each0": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("each0"),
|
||||||
|
}),
|
||||||
|
"each1": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("each1"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
"null_resource.multi[1]": cty.ObjectVal(map[string]cty.Value{
|
"null_resource.multi[1]": cty.ObjectVal(map[string]cty.Value{
|
||||||
"attr": cty.StringVal("multi1"),
|
"attr": cty.StringVal("multi1"),
|
||||||
}),
|
}),
|
||||||
|
@ -139,11 +147,14 @@ func TestScopeEvalContext(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// at this level, all instance references return the entire resource
|
||||||
`null_resource.multi[1]`,
|
`null_resource.multi[1]`,
|
||||||
map[string]cty.Value{
|
map[string]cty.Value{
|
||||||
"null_resource": cty.ObjectVal(map[string]cty.Value{
|
"null_resource": cty.ObjectVal(map[string]cty.Value{
|
||||||
"multi": cty.TupleVal([]cty.Value{
|
"multi": cty.TupleVal([]cty.Value{
|
||||||
cty.DynamicVal,
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("multi0"),
|
||||||
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"attr": cty.StringVal("multi1"),
|
"attr": cty.StringVal("multi1"),
|
||||||
}),
|
}),
|
||||||
|
@ -151,6 +162,22 @@ func TestScopeEvalContext(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// at this level, all instance references return the entire resource
|
||||||
|
`null_resource.each["each1"]`,
|
||||||
|
map[string]cty.Value{
|
||||||
|
"null_resource": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"each": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"each0": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("each0"),
|
||||||
|
}),
|
||||||
|
"each1": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("each1"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`foo(null_resource.multi, null_resource.multi[1])`,
|
`foo(null_resource.multi, null_resource.multi[1])`,
|
||||||
map[string]cty.Value{
|
map[string]cty.Value{
|
||||||
|
@ -210,17 +237,6 @@ func TestScopeEvalContext(t *testing.T) {
|
||||||
{
|
{
|
||||||
`self.baz`,
|
`self.baz`,
|
||||||
map[string]cty.Value{
|
map[string]cty.Value{
|
||||||
// In the test function below we set "SelfAddr" to be
|
|
||||||
// one of the resources in our dataset, causing it to get
|
|
||||||
// expanded here and then copied into "self".
|
|
||||||
"null_resource": cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"multi": cty.TupleVal([]cty.Value{
|
|
||||||
cty.DynamicVal,
|
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"attr": cty.StringVal("multi1"),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
"self": cty.ObjectVal(map[string]cty.Value{
|
"self": cty.ObjectVal(map[string]cty.Value{
|
||||||
"attr": cty.StringVal("multi1"),
|
"attr": cty.StringVal("multi1"),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -10573,3 +10573,44 @@ func TestContext2Apply_issue19908(t *testing.T) {
|
||||||
t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got)
|
t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_invalidIndexRef(t *testing.T) {
|
||||||
|
p := testProvider("test")
|
||||||
|
p.GetSchemaReturn = &ProviderSchema{
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_instance": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"value": {Type: cty.String, Optional: true, Computed: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
|
||||||
|
m := testModule(t, "apply-invalid-index")
|
||||||
|
c := testContext2(t, &ContextOpts{
|
||||||
|
Config: m,
|
||||||
|
ProviderResolver: providers.ResolverFixed(
|
||||||
|
map[string]providers.Factory{
|
||||||
|
"test": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
diags := c.Validate()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
wantErr := `The given key does not identify an element in this collection value`
|
||||||
|
_, diags = c.Plan()
|
||||||
|
|
||||||
|
if !diags.HasErrors() {
|
||||||
|
t.Fatalf("plan succeeded; want error")
|
||||||
|
}
|
||||||
|
gotErr := diags.Err().Error()
|
||||||
|
|
||||||
|
if !strings.Contains(gotErr, wantErr) {
|
||||||
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErr, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,13 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !configVal.IsWhollyKnown() {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"configuration for %s still contains unknown values during apply (this is a bug in Terraform; please report it!)",
|
||||||
|
absAddr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr.Absolute(ctx.Path()), change.Action)
|
log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr.Absolute(ctx.Path()), change.Action)
|
||||||
resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
|
resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
|
||||||
TypeName: n.Addr.Resource.Type,
|
TypeName: n.Addr.Resource.Type,
|
||||||
|
|
|
@ -2,7 +2,6 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -507,14 +506,8 @@ func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.Sourc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Although we are giving a ResourceInstance address here, if it has
|
|
||||||
// a key of addrs.NoKey then it might actually be a request for all of
|
|
||||||
// the instances of a particular resource. The reference resolver can't
|
|
||||||
// resolve the ambiguity itself, so we must do it in here.
|
|
||||||
|
|
||||||
// First we'll consult the configuration to see if an resource of this
|
// First we'll consult the configuration to see if an resource of this
|
||||||
// name is declared at all.
|
// name is declared at all.
|
||||||
moduleAddr := d.ModulePath
|
moduleAddr := d.ModulePath
|
||||||
|
@ -525,36 +518,21 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r
|
||||||
panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr))
|
panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr))
|
||||||
}
|
}
|
||||||
|
|
||||||
config := moduleConfig.Module.ResourceByAddr(addr.ContainingResource())
|
config := moduleConfig.Module.ResourceByAddr(addr)
|
||||||
if config == nil {
|
if config == nil {
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: `Reference to undeclared resource`,
|
Summary: `Reference to undeclared resource`,
|
||||||
Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Resource.Type, addr.Resource.Name, moduleDisplayAddr(moduleAddr)),
|
Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Type, addr.Name, moduleDisplayAddr(moduleAddr)),
|
||||||
Subject: rng.ToHCL().Ptr(),
|
Subject: rng.ToHCL().Ptr(),
|
||||||
})
|
})
|
||||||
return cty.DynamicVal, diags
|
return cty.DynamicVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// First we'll find the state for the resource as a whole, and decide
|
rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath))
|
||||||
// from there whether we're going to interpret the given address as a
|
|
||||||
// resource or a resource instance address.
|
|
||||||
rs := d.Evaluator.State.Resource(addr.ContainingResource().Absolute(d.ModulePath))
|
|
||||||
|
|
||||||
if rs == nil {
|
if rs == nil {
|
||||||
schema := d.getResourceSchema(addr.ContainingResource(), config.ProviderConfigAddr().Absolute(d.ModulePath))
|
// we must return DynamicVal so that both interpretations
|
||||||
|
|
||||||
// If it doesn't exist at all then we can't reliably determine whether
|
|
||||||
// single-instance or whole-resource interpretation was intended, but
|
|
||||||
// we can decide this partially...
|
|
||||||
if addr.Key != addrs.NoKey {
|
|
||||||
// If there's an instance key then the user must be intending
|
|
||||||
// single-instance interpretation, and so we can return a
|
|
||||||
// properly-typed unknown value to help with type checking.
|
|
||||||
return cty.UnknownVal(schema.ImpliedType()), diags
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise we must return DynamicVal so that both interpretations
|
|
||||||
// can proceed without generating errors, and we'll deal with this
|
// can proceed without generating errors, and we'll deal with this
|
||||||
// in a later step where more information is gathered.
|
// in a later step where more information is gathered.
|
||||||
// (In practice we should only end up here during the validate walk,
|
// (In practice we should only end up here during the validate walk,
|
||||||
|
@ -569,136 +547,14 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r
|
||||||
return cty.DynamicVal, diags
|
return cty.DynamicVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
schema := d.getResourceSchema(addr.ContainingResource(), rs.ProviderConfig)
|
return d.getResourceInstancesAll(addr, rng, config, rs, rs.ProviderConfig)
|
||||||
|
|
||||||
// If we are able to automatically convert to the "right" type of instance
|
|
||||||
// key for this each mode then we'll do so, to match with how we generally
|
|
||||||
// treat values elsewhere in the language. This allows code below to
|
|
||||||
// assume that any possible conversions have already been dealt with and
|
|
||||||
// just worry about validation.
|
|
||||||
key := d.coerceInstanceKey(addr.Key, rs.EachMode)
|
|
||||||
|
|
||||||
multi := false
|
|
||||||
|
|
||||||
switch rs.EachMode {
|
|
||||||
case states.NoEach:
|
|
||||||
if key != addrs.NoKey {
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid resource index",
|
|
||||||
Detail: fmt.Sprintf("Resource %s does not have either \"count\" or \"for_each\" set, so it cannot be indexed.", addr.ContainingResource()),
|
|
||||||
Subject: rng.ToHCL().Ptr(),
|
|
||||||
})
|
|
||||||
return cty.DynamicVal, diags
|
|
||||||
}
|
|
||||||
case states.EachList:
|
|
||||||
multi = key == addrs.NoKey
|
|
||||||
if _, ok := addr.Key.(addrs.IntKey); !multi && !ok {
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid resource index",
|
|
||||||
Detail: fmt.Sprintf("Resource %s must be indexed with a number value.", addr.ContainingResource()),
|
|
||||||
Subject: rng.ToHCL().Ptr(),
|
|
||||||
})
|
|
||||||
return cty.DynamicVal, diags
|
|
||||||
}
|
|
||||||
case states.EachMap:
|
|
||||||
multi = key == addrs.NoKey
|
|
||||||
if _, ok := addr.Key.(addrs.StringKey); !multi && !ok {
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid resource index",
|
|
||||||
Detail: fmt.Sprintf("Resource %s must be indexed with a string value.", addr.ContainingResource()),
|
|
||||||
Subject: rng.ToHCL().Ptr(),
|
|
||||||
})
|
|
||||||
return cty.DynamicVal, diags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !multi {
|
|
||||||
log.Printf("[TRACE] GetResourceInstance: %s is a single instance", addr)
|
|
||||||
is := rs.Instance(key)
|
|
||||||
if is == nil {
|
|
||||||
return cty.UnknownVal(schema.ImpliedType()), diags
|
|
||||||
}
|
|
||||||
return d.getResourceInstanceSingle(addr, rng, is, config, rs.ProviderConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[TRACE] GetResourceInstance: %s has multiple keyed instances", addr)
|
|
||||||
return d.getResourceInstancesAll(addr.ContainingResource(), rng, config, rs, rs.ProviderConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInstance, rng tfdiags.SourceRange, is *states.ResourceInstance, config *configs.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
|
|
||||||
schema := d.getResourceSchema(addr.ContainingResource(), providerAddr)
|
|
||||||
if schema == nil {
|
|
||||||
// This shouldn't happen, since validation before we get here should've
|
|
||||||
// taken care of it, but we'll show a reasonable error message anyway.
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: `Missing resource type schema`,
|
|
||||||
Detail: fmt.Sprintf("No schema is available for %s in %s. This is a bug in Terraform and should be reported.", addr, providerAddr),
|
|
||||||
Subject: rng.ToHCL().Ptr(),
|
|
||||||
})
|
|
||||||
return cty.DynamicVal, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
ty := schema.ImpliedType()
|
|
||||||
if is == nil || is.Current == nil {
|
|
||||||
// Assume we're dealing with an instance that hasn't been created yet.
|
|
||||||
return cty.UnknownVal(ty), diags
|
|
||||||
}
|
|
||||||
|
|
||||||
if is.Current.Status == states.ObjectPlanned {
|
|
||||||
// If there's a pending change for this instance in our plan, we'll prefer
|
|
||||||
// that. This is important because the state can't represent unknown values
|
|
||||||
// and so its data is inaccurate when changes are pending.
|
|
||||||
if change := d.Evaluator.Changes.GetResourceInstanceChange(addr.Absolute(d.ModulePath), states.CurrentGen); change != nil {
|
|
||||||
val, err := change.After.Decode(ty)
|
|
||||||
if err != nil {
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid resource instance data in plan",
|
|
||||||
Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", addr.Absolute(d.ModulePath), err),
|
|
||||||
Subject: &config.DeclRange,
|
|
||||||
})
|
|
||||||
return cty.UnknownVal(ty), diags
|
|
||||||
}
|
|
||||||
return val, diags
|
|
||||||
} else {
|
|
||||||
// If the object is in planned status then we should not
|
|
||||||
// get here, since we should've found a pending value
|
|
||||||
// in the plan above instead.
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Missing pending object in plan",
|
|
||||||
Detail: fmt.Sprintf("Instance %s is marked as having a change pending but that change is not recorded in the plan. This is a bug in Terraform; please report it.", addr),
|
|
||||||
Subject: &config.DeclRange,
|
|
||||||
})
|
|
||||||
return cty.UnknownVal(ty), diags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ios, err := is.Current.Decode(ty)
|
|
||||||
if err != nil {
|
|
||||||
// This shouldn't happen, since by the time we get here
|
|
||||||
// we should've upgraded the state data already.
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid resource instance data in state",
|
|
||||||
Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", addr.Absolute(d.ModulePath), err),
|
|
||||||
Subject: &config.DeclRange,
|
|
||||||
})
|
|
||||||
return cty.UnknownVal(ty), diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return ios.Value, diags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng tfdiags.SourceRange, config *configs.Resource, rs *states.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
|
func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng tfdiags.SourceRange, config *configs.Resource, rs *states.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
instAddr := addrs.ResourceInstance{Resource: addr, Key: addrs.NoKey}
|
||||||
|
|
||||||
schema := d.getResourceSchema(addr, providerAddr)
|
schema := d.getResourceSchema(addr, providerAddr)
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
// This shouldn't happen, since validation before we get here should've
|
// This shouldn't happen, since validation before we get here should've
|
||||||
|
@ -713,6 +569,58 @@ func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng t
|
||||||
}
|
}
|
||||||
|
|
||||||
switch rs.EachMode {
|
switch rs.EachMode {
|
||||||
|
case states.NoEach:
|
||||||
|
ty := schema.ImpliedType()
|
||||||
|
is := rs.Instances[addrs.NoKey]
|
||||||
|
if is == nil || is.Current == nil {
|
||||||
|
// Assume we're dealing with an instance that hasn't been created yet.
|
||||||
|
return cty.UnknownVal(ty), diags
|
||||||
|
}
|
||||||
|
|
||||||
|
if is.Current.Status == states.ObjectPlanned {
|
||||||
|
// If there's a pending change for this instance in our plan, we'll prefer
|
||||||
|
// that. This is important because the state can't represent unknown values
|
||||||
|
// and so its data is inaccurate when changes are pending.
|
||||||
|
if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr.Absolute(d.ModulePath), states.CurrentGen); change != nil {
|
||||||
|
val, err := change.After.Decode(ty)
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid resource instance data in plan",
|
||||||
|
Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", addr.Absolute(d.ModulePath), err),
|
||||||
|
Subject: &config.DeclRange,
|
||||||
|
})
|
||||||
|
return cty.UnknownVal(ty), diags
|
||||||
|
}
|
||||||
|
return val, diags
|
||||||
|
} else {
|
||||||
|
// If the object is in planned status then we should not
|
||||||
|
// get here, since we should've found a pending value
|
||||||
|
// in the plan above instead.
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Missing pending object in plan",
|
||||||
|
Detail: fmt.Sprintf("Instance %s is marked as having a change pending but that change is not recorded in the plan. This is a bug in Terraform; please report it.", addr),
|
||||||
|
Subject: &config.DeclRange,
|
||||||
|
})
|
||||||
|
return cty.UnknownVal(ty), diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ios, err := is.Current.Decode(ty)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't happen, since by the time we get here
|
||||||
|
// we should've upgraded the state data already.
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid resource instance data in state",
|
||||||
|
Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", addr.Absolute(d.ModulePath), err),
|
||||||
|
Subject: &config.DeclRange,
|
||||||
|
})
|
||||||
|
return cty.UnknownVal(ty), diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return ios.Value, diags
|
||||||
|
|
||||||
case states.EachList:
|
case states.EachList:
|
||||||
// We need to infer the length of our resulting tuple by searching
|
// We need to infer the length of our resulting tuple by searching
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "test_instance" "a" {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "b" {
|
||||||
|
value = test_instance.a[0].value
|
||||||
|
}
|
Loading…
Reference in New Issue