import (
"fmt"
- "github.com/hashicorp/terraform/config"
+ "github.com/hashicorp/terraform/plans"
+ "github.com/hashicorp/terraform/providers"
+ "github.com/hashicorp/terraform/states"
+
+ "github.com/hashicorp/terraform/addrs"
+ "github.com/zclconf/go-cty/cty"
)
// NodePlannableResourceInstance represents a _single_ resource
// instance that is plannable. This means this represents a single
// count index, for example.
type NodePlannableResourceInstance struct {
- *NodeAbstractResource
+ *NodeAbstractResourceInstance
+ ForceCreateBeforeDestroy bool
}
+var (
+ _ GraphNodeSubPath = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeReferenceable = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeReferencer = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeResource = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil)
+ _ GraphNodeEvalable = (*NodePlannableResourceInstance)(nil)
+)
+
// GraphNodeEvalable
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
- addr := n.NodeAbstractResource.Addr
-
- // stateId is the ID to put into the state
- stateId := addr.stateId()
+ addr := n.ResourceInstanceAddr()
- // Build the instance info. More of this will be populated during eval
- info := &InstanceInfo{
- Id: stateId,
- Type: addr.Type,
- ModulePath: normalizeModulePath(addr.Path),
- }
-
- // Build the resource for eval
- resource := &Resource{
- Name: addr.Name,
- Type: addr.Type,
- CountIndex: addr.Index,
- }
- if resource.CountIndex < 0 {
- resource.CountIndex = 0
- }
+ // State still uses legacy-style internal ids, so we need to shim to get
+ // a suitable key to use.
+ stateId := NewLegacyResourceInstanceAddress(addr).stateId()
// Determine the dependencies for the state.
stateDeps := n.StateReferences()
// Eval info is different depending on what kind of resource this is
- switch n.Config.Mode {
- case config.ManagedResourceMode:
- return n.evalTreeManagedResource(
- stateId, info, resource, stateDeps,
- )
- case config.DataResourceMode:
- return n.evalTreeDataResource(
- stateId, info, resource, stateDeps)
+ switch addr.Resource.Resource.Mode {
+ case addrs.ManagedResourceMode:
+ return n.evalTreeManagedResource(addr, stateId, stateDeps)
+ case addrs.DataResourceMode:
+ return n.evalTreeDataResource(addr, stateId, stateDeps)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
}
}
-func (n *NodePlannableResourceInstance) evalTreeDataResource(
- stateId string, info *InstanceInfo,
- resource *Resource, stateDeps []string) EvalNode {
- var provider ResourceProvider
- var config *ResourceConfig
- var diff *InstanceDiff
- var state *InstanceState
+func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []addrs.Referenceable) EvalNode {
+ config := n.Config
+ var provider providers.Interface
+ var providerSchema *ProviderSchema
+ var change *plans.ResourceInstanceChange
+ var state *states.ResourceInstanceObject
+ var configVal cty.Value
return &EvalSequence{
Nodes: []EvalNode{
- &EvalReadState{
- Name: stateId,
- Output: &state,
+ &EvalGetProvider{
+ Addr: n.ResolvedProvider,
+ Output: &provider,
+ Schema: &providerSchema,
},
- // We need to re-interpolate the config here because some
- // of the attributes may have become computed during
- // earlier planning, due to other resources having
- // "requires new resource" diffs.
- &EvalInterpolate{
- Config: n.Config.RawConfig.Copy(),
- Resource: resource,
- Output: &config,
+ &EvalReadState{
+ Addr: addr.Resource,
+ Provider: &provider,
+ ProviderSchema: &providerSchema,
+
+ Output: &state,
},
+ // If we already have a non-planned state then we already dealt
+ // with this during the refresh walk and so we have nothing to do
+ // here.
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
- computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0
-
- // If the configuration is complete and we
- // already have a state then we don't need to
- // do any further work during apply, because we
- // already populated the state during refresh.
- if !computed && state != nil {
- return true, EvalEarlyExitError{}
+ depChanges := false
+
+ // Check and see if any of our dependencies have changes.
+ changes := ctx.Changes()
+ for _, d := range n.StateReferences() {
+ ri, ok := d.(addrs.ResourceInstance)
+ if !ok {
+ continue
+ }
+ change := changes.GetResourceInstanceChange(ri.Absolute(ctx.Path()), states.CurrentGen)
+ if change != nil && change.Action != plans.NoOp {
+ depChanges = true
+ break
+ }
}
+ refreshed := state != nil && state.Status != states.ObjectPlanned
+
+ // If there are no dependency changes, and it's not a forced
+ // read because we there was no Refresh, then we don't need
+ // to re-read. If any dependencies have changes, it means
+ // our config may also have changes and we need to Read the
+ // data source again.
+ if !depChanges && refreshed {
+ return false, EvalEarlyExitError{}
+ }
return true, nil
},
Then: EvalNoop{},
},
- &EvalGetProvider{
- Name: n.ResolvedProvider,
- Output: &provider,
+ &EvalValidateSelfRef{
+ Addr: addr.Resource,
+ Config: config.Config,
+ ProviderSchema: &providerSchema,
},
- &EvalReadDataDiff{
- Info: info,
- Config: &config,
- Provider: &provider,
- Output: &diff,
- OutputState: &state,
+ &EvalReadData{
+ Addr: addr.Resource,
+ Config: n.Config,
+ Dependencies: n.StateReferences(),
+ Provider: &provider,
+ ProviderAddr: n.ResolvedProvider,
+ ProviderSchema: &providerSchema,
+ ForcePlanRead: true, // _always_ produce a Read change, even if the config seems ready
+ OutputChange: &change,
+ OutputValue: &configVal,
+ OutputState: &state,
},
&EvalWriteState{
- Name: stateId,
- ResourceType: n.Config.Type,
- Provider: n.ResolvedProvider,
- Dependencies: stateDeps,
- State: &state,
+ Addr: addr.Resource,
+ ProviderAddr: n.ResolvedProvider,
+ ProviderSchema: &providerSchema,
+ State: &state,
},
&EvalWriteDiff{
- Name: stateId,
- Diff: &diff,
+ Addr: addr.Resource,
+ ProviderSchema: &providerSchema,
+ Change: &change,
},
},
}
}
-func (n *NodePlannableResourceInstance) evalTreeManagedResource(
- stateId string, info *InstanceInfo,
- resource *Resource, stateDeps []string) EvalNode {
- // Declare a bunch of variables that are used for state during
- // evaluation. Most of this are written to by-address below.
- var provider ResourceProvider
- var diff *InstanceDiff
- var state *InstanceState
- var resourceConfig *ResourceConfig
+func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []addrs.Referenceable) EvalNode {
+ config := n.Config
+ var provider providers.Interface
+ var providerSchema *ProviderSchema
+ var change *plans.ResourceInstanceChange
+ var state *states.ResourceInstanceObject
return &EvalSequence{
Nodes: []EvalNode{
- &EvalInterpolate{
- Config: n.Config.RawConfig.Copy(),
- Resource: resource,
- Output: &resourceConfig,
- },
&EvalGetProvider{
- Name: n.ResolvedProvider,
+ Addr: n.ResolvedProvider,
Output: &provider,
+ Schema: &providerSchema,
},
- // Re-run validation to catch any errors we missed, e.g. type
- // mismatches on computed values.
- &EvalValidateResource{
- Provider: &provider,
- Config: &resourceConfig,
- ResourceName: n.Config.Name,
- ResourceType: n.Config.Type,
- ResourceMode: n.Config.Mode,
- IgnoreWarnings: true,
- },
+
&EvalReadState{
- Name: stateId,
+ Addr: addr.Resource,
+ Provider: &provider,
+ ProviderSchema: &providerSchema,
+
Output: &state,
},
+
+ &EvalValidateSelfRef{
+ Addr: addr.Resource,
+ Config: config.Config,
+ ProviderSchema: &providerSchema,
+ },
+
&EvalDiff{
- Name: stateId,
- Info: info,
- Config: &resourceConfig,
- Resource: n.Config,
- Provider: &provider,
- State: &state,
- OutputDiff: &diff,
- OutputState: &state,
+ Addr: addr.Resource,
+ Config: n.Config,
+ CreateBeforeDestroy: n.ForceCreateBeforeDestroy,
+ Provider: &provider,
+ ProviderAddr: n.ResolvedProvider,
+ ProviderSchema: &providerSchema,
+ State: &state,
+ OutputChange: &change,
+ OutputState: &state,
},
&EvalCheckPreventDestroy{
- Resource: n.Config,
- Diff: &diff,
+ Addr: addr.Resource,
+ Config: n.Config,
+ Change: &change,
},
&EvalWriteState{
- Name: stateId,
- ResourceType: n.Config.Type,
- Provider: n.ResolvedProvider,
- Dependencies: stateDeps,
- State: &state,
+ Addr: addr.Resource,
+ ProviderAddr: n.ResolvedProvider,
+ State: &state,
+ ProviderSchema: &providerSchema,
},
&EvalWriteDiff{
- Name: stateId,
- Diff: &diff,
+ Addr: addr.Resource,
+ ProviderSchema: &providerSchema,
+ Change: &change,
},
},
}