From 824986b6987c27b6f28b224cebf451d8388f85d8 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 22 May 2018 10:14:30 -0700 Subject: [PATCH] core: use correct provider config address when eval pending resources The evaluate data source was using a guessed provider configuration address from configuration in this case, but that isn't necessarily correct since the resource might actually be associated with a config inherited from a parent module. We still need to retain that fallback to config because we are sometimes asked to evaluate when state is incomplete (like in "terraform console"), but if possible we'll take the stored provider address from the state and use that, even if the resource is otherwise "pending". --- terraform/eval_state.go | 2 ++ terraform/evaluate.go | 42 +++++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/terraform/eval_state.go b/terraform/eval_state.go index 46ba5befde..36051e1e58 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -2,6 +2,7 @@ package terraform import ( "fmt" + "log" "github.com/hashicorp/terraform/addrs" ) @@ -208,6 +209,7 @@ func writeInstanceToState( rs.Type = resourceType rs.Dependencies = dependencies rs.Provider = provider + log.Printf("[TRACE] Saving state for %s, managed by %s", resourceName, provider) if err := writerFn(rs); err != nil { return nil, err diff --git a/terraform/evaluate.go b/terraform/evaluate.go index f7bcaf5d84..ffc5b70c20 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -2,6 +2,7 @@ package terraform import ( "fmt" + "log" "os" "strconv" "strings" @@ -462,10 +463,10 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r // we revise the state structs to natively support the HCL type system. rs := ms.Resources[addrKey] - // If we have an exact match for the requested instance and it has non-nil - // primary data then we'll use it directly. This is the easy path. - if rs != nil && rs.Primary != nil { - providerAddr, err := rs.ProviderAddr() + var providerAddr addrs.AbsProviderConfig + if rs != nil { + var err error + providerAddr, err = rs.ProviderAddr() if err != nil { // This indicates corruption of or tampering with the state file diags = diags.Append(&hcl.Diagnostic{ @@ -476,6 +477,20 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r }) return cty.DynamicVal, diags } + } else { + // Must assume a provider address from the config, then. + // This result is usually ignored since we'll probably end up in + // the getResourceInstancesAll path after this (if our instance + // actually has a key). However, we can also end up here in strange + // cases like "terraform console", which might be used before a + // particular resource has been created in state at all. + providerAddr = config.ProviderConfigAddr().Absolute(d.ModulePath) + } + + // If we have an exact match for the requested instance and it has non-nil + // primary data then we'll use it directly. This is the easy path. + if rs != nil && rs.Primary != nil { + log.Printf("[TRACE] GetResourceInstance: %s is a single instance", addr) return d.getResourceInstanceSingle(addr, rng, rs.Primary, providerAddr) } @@ -484,10 +499,11 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r // If we have a _keyed_ address then instead it's a single instance that // isn't evaluated yet. if addr.Key != addrs.NoKey { - return d.getResourceInstancePending(addr, rng) + log.Printf("[TRACE] GetResourceInstance: %s is pending", addr) + return d.getResourceInstancePending(addr, rng, providerAddr) } - return d.getResourceInstancesAll(addr.ContainingResource(), config, ms) + return d.getResourceInstancesAll(addr.ContainingResource(), config, ms, providerAddr) } func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInstance, rng tfdiags.SourceRange, is *InstanceState, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) { @@ -531,7 +547,7 @@ func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInsta return val, diags } -func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, config *configs.Resource, ms *ModuleState) (cty.Value, tfdiags.Diagnostics) { +func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, config *configs.Resource, ms *ModuleState, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics rng := tfdiags.SourceRangeFromHCL(config.DeclRange) hasCount := config.Count != nil @@ -574,6 +590,8 @@ func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, confi key = addrs.StringKey(keyStr) } + // In this case we'll ignore our given providerAddr, since it was + // for a single unkeyed ResourceState, not the keyed one we have now. providerAddr, err := rs.ProviderAddr() if err != nil { // This indicates corruption of or tampering with the state file @@ -597,10 +615,14 @@ func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, confi // 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) + // In this case we _do_ use the given providerAddr, since that + // is for the unkeyed instance we found in GetResourceInstance. + log.Printf("[TRACE] GetResourceInstance: %s has no instances yet", addr) return d.getResourceInstanceSingle(addr.Instance(addrs.NoKey), rng, nil, providerAddr) } + log.Printf("[TRACE] GetResourceInstance: %s has multiple keyed instances (%d)", addr, length) + // 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 @@ -628,7 +650,7 @@ func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, confi return cty.TupleVal(valsSeq), diags } -func (d *evaluationStateData) getResourceInstancePending(addr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { +func (d *evaluationStateData) getResourceInstancePending(addr addrs.ResourceInstance, rng tfdiags.SourceRange, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics // We'd ideally like to return a properly-typed unknown value here, in @@ -655,7 +677,6 @@ func (d *evaluationStateData) getResourceInstancePending(addr addrs.ResourceInst if rc == nil { return cty.DynamicVal, diags } - providerAddr := rc.ProviderConfigAddr().Absolute(d.ModulePath) schema := d.getResourceSchema(addr.ContainingResource(), providerAddr) if schema == nil { return cty.DynamicVal, diags @@ -668,6 +689,7 @@ func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAdd d.Evaluator.ProvidersLock.Lock() defer d.Evaluator.ProvidersLock.Unlock() + log.Printf("[TRACE] Need provider schema for %s", providerAddr) providerSchema := d.Evaluator.ProviderSchemas[providerAddr.String()] if providerSchema == nil { return nil