Merge pull request #22846 from hashicorp/jbardin/evaluate-resource

Always evaluate resources in their entirety
This commit is contained in:
James Bardin 2019-10-08 07:57:15 -04:00 committed by GitHub
commit 1ee851f256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 212 additions and 260 deletions

View File

@ -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
} }

View File

@ -114,13 +114,11 @@ 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},
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
@ -592,13 +590,11 @@ 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},
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},

View File

@ -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
} }

View File

@ -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)

View File

@ -9,7 +9,7 @@ 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
@ -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) {

View File

@ -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

View File

@ -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"),
}), }),

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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,69 +547,15 @@ 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 { func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng tfdiags.SourceRange, config *configs.Resource, rs *states.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
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 var diags tfdiags.Diagnostics
schema := d.getResourceSchema(addr.ContainingResource(), providerAddr) instAddr := addrs.ResourceInstance{Resource: addr, Key: addrs.NoKey}
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
// taken care of it, but we'll show a reasonable error message anyway. // taken care of it, but we'll show a reasonable error message anyway.
@ -644,7 +568,10 @@ func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInsta
return cty.DynamicVal, diags return cty.DynamicVal, diags
} }
switch rs.EachMode {
case states.NoEach:
ty := schema.ImpliedType() ty := schema.ImpliedType()
is := rs.Instances[addrs.NoKey]
if is == nil || is.Current == nil { if is == nil || is.Current == nil {
// Assume we're dealing with an instance that hasn't been created yet. // Assume we're dealing with an instance that hasn't been created yet.
return cty.UnknownVal(ty), diags return cty.UnknownVal(ty), diags
@ -654,7 +581,7 @@ func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInsta
// If there's a pending change for this instance in our plan, we'll prefer // 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 // that. This is important because the state can't represent unknown values
// and so its data is inaccurate when changes are pending. // and so its data is inaccurate when changes are pending.
if change := d.Evaluator.Changes.GetResourceInstanceChange(addr.Absolute(d.ModulePath), states.CurrentGen); change != nil { if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr.Absolute(d.ModulePath), states.CurrentGen); change != nil {
val, err := change.After.Decode(ty) val, err := change.After.Decode(ty)
if err != nil { if err != nil {
diags = diags.Append(&hcl.Diagnostic{ diags = diags.Append(&hcl.Diagnostic{
@ -694,25 +621,6 @@ func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInsta
} }
return ios.Value, 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) {
var diags tfdiags.Diagnostics
schema := d.getResourceSchema(addr, 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
}
switch rs.EachMode {
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

View File

@ -0,0 +1,7 @@
resource "test_instance" "a" {
count = 0
}
resource "test_instance" "b" {
value = test_instance.a[0].value
}