core: Check that resource exists in config before evaluating it

We were previously doing this for all of the reference types except this
one. Now we do it for resources and resource instances too, which both
allows us to produce a proper error message when one is missing (rather
than returning an unknown value) and allows us to properly handle the
case where there are no instances yet present in the state (e.g. because
we're in the validate walk) but "count" isn't set, and so a single
unknown value is expected rather than an empty tuple.
This commit is contained in:
Martin Atkins 2018-05-11 17:39:32 -07:00
parent 0c4c7c5a5f
commit b54342460c
1 changed files with 40 additions and 3 deletions

View File

@ -421,6 +421,27 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r
// 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 output of this
// name is declared at all.
moduleAddr := d.ModulePath
moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
if moduleConfig == nil {
// should never happen, since we can't be evaluating in a module
// that wasn't mentioned in configuration.
panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr))
}
config := moduleConfig.Module.ResourceByAddr(addr.ContainingResource())
if config == nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Reference to undeclared resource`,
Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Resource.Type, addr.Resource.Name, moduleAddr),
Subject: rng.ToHCL().Ptr(),
})
return cty.DynamicVal, diags
}
// We need to shim our address to the legacy form still used in the state structs.
addrKey := NewLegacyResourceInstanceAddress(addr.Absolute(d.ModulePath)).stateId()
@ -466,7 +487,7 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r
return d.getResourceInstancePending(addr, rng)
}
return d.getResourceInstancesAll(addr.ContainingResource(), rng, ms)
return d.getResourceInstancesAll(addr.ContainingResource(), config, ms)
}
func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInstance, rng tfdiags.SourceRange, is *InstanceState, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
@ -488,8 +509,13 @@ func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInsta
return cty.DynamicVal, diags
}
flatmapVal := is.Attributes
ty := schema.ImpliedType()
if is == nil {
// Assume we're dealing with an instance that hasn't been created yet.
return cty.UnknownVal(ty), diags
}
flatmapVal := is.Attributes
val, err := hcl2shim.HCL2ValueFromFlatmap(flatmapVal, ty)
if err != nil {
// A value in the flatmap value could not be conformed to the schema
@ -505,8 +531,10 @@ func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInsta
return val, diags
}
func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng tfdiags.SourceRange, ms *ModuleState) (cty.Value, tfdiags.Diagnostics) {
func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, config *configs.Resource, ms *ModuleState) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rng := tfdiags.SourceRangeFromHCL(config.DeclRange)
hasCount := config.Count != nil
// Currently the only multi-instance construct we support is "count", which
// ensures that all of the instances will have integer keys, and so we
@ -564,6 +592,15 @@ func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng t
instanceVals[key] = val
}
if length == 0 && !hasCount {
// If we have nothing at all and the configuration lacks a count
// argument then we'll assume that we're dealing with a resource that
// is pending creation (e.g. during the validate walk) and that it
// will eventually have only one unkeyed instance.
providerAddr := config.ProviderConfigAddr().Absolute(d.ModulePath)
return d.getResourceInstanceSingle(addr.Instance(addrs.NoKey), rng, nil, providerAddr)
}
// TODO: In future, when for_each is implemented, we'll need to decide here
// whether to return a tuple value or an object value. However, by that
// time we should've revised the state structs so we can see unambigously