package terraform
import (
- "fmt"
+ "log"
- "github.com/hashicorp/terraform/config"
+ "github.com/hashicorp/terraform/addrs"
+ "github.com/hashicorp/terraform/dag"
+ "github.com/hashicorp/terraform/lang"
)
// NodeApplyableResource represents a resource that is "applyable":
-// it is ready to be applied and is represented by a diff.
+// it may need to have its record in the state adjusted to match configuration.
+//
+// Unlike in the plan walk, this resource node does not DynamicExpand. Instead,
+// it should be inserted into the same graph as any instances of the nodes
+// with dependency edges ensuring that the resource is evaluated before any
+// of its instances, which will turn ensure that the whole-resource record
+// in the state is suitably prepared to receive any updates to instances.
type NodeApplyableResource struct {
*NodeAbstractResource
}
-// GraphNodeCreator
-func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
- return n.NodeAbstractResource.Addr
-}
-
-// GraphNodeReferencer, overriding NodeAbstractResource
-func (n *NodeApplyableResource) References() []string {
- result := n.NodeAbstractResource.References()
-
- // The "apply" side of a resource generally also depends on the
- // destruction of its dependencies as well. For example, if a LB
- // references a set of VMs with ${vm.foo.*.id}, then we must wait for
- // the destruction so we get the newly updated list of VMs.
- //
- // The exception here is CBD. When CBD is set, we don't do this since
- // it would create a cycle. By not creating a cycle, we require two
- // applies since the first apply the creation step will use the OLD
- // values (pre-destroy) and the second step will update.
- //
- // This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x).
- // We mimic that behavior here now and can improve upon it in the future.
- //
- // This behavior is tested in graph_build_apply_test.go to test ordering.
- cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy
- if !cbd {
- // The "apply" side of a resource always depends on the destruction
- // of all its dependencies in addition to the creation.
- for _, v := range result {
- result = append(result, v+".destroy")
- }
- }
+var (
+ _ GraphNodeResource = (*NodeApplyableResource)(nil)
+ _ GraphNodeEvalable = (*NodeApplyableResource)(nil)
+ _ GraphNodeProviderConsumer = (*NodeApplyableResource)(nil)
+ _ GraphNodeAttachResourceConfig = (*NodeApplyableResource)(nil)
+ _ GraphNodeReferencer = (*NodeApplyableResource)(nil)
+)
- return result
+func (n *NodeApplyableResource) Name() string {
+ return n.NodeAbstractResource.Name() + " (prepare state)"
}
-// GraphNodeEvalable
-func (n *NodeApplyableResource) EvalTree() EvalNode {
- addr := n.NodeAbstractResource.Addr
-
- // stateId is the ID to put into the state
- stateId := addr.stateId()
-
- // Build the instance info. More of this will be populated during eval
- info := &InstanceInfo{
- Id: stateId,
- Type: addr.Type,
+func (n *NodeApplyableResource) References() []*addrs.Reference {
+ if n.Config == nil {
+ log.Printf("[WARN] NodeApplyableResource %q: no configuration, so can't determine References", dag.VertexName(n))
+ return nil
}
- // Build the resource for eval
- resource := &Resource{
- Name: addr.Name,
- Type: addr.Type,
- CountIndex: addr.Index,
- }
- if resource.CountIndex < 0 {
- resource.CountIndex = 0
- }
+ var result []*addrs.Reference
- // Determine the dependencies for the state.
- stateDeps := n.StateReferences()
+ // Since this node type only updates resource-level metadata, we only
+ // need to worry about the parts of the configuration that affect
+ // our "each mode": the count and for_each meta-arguments.
+ refs, _ := lang.ReferencesInExpr(n.Config.Count)
+ result = append(result, refs...)
+ refs, _ = lang.ReferencesInExpr(n.Config.ForEach)
+ result = append(result, refs...)
- // 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)
- default:
- panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
- }
+ return result
}
-func (n *NodeApplyableResource) evalTreeDataResource(
- stateId string, info *InstanceInfo,
- resource *Resource, stateDeps []string) EvalNode {
- var provider ResourceProvider
- var config *ResourceConfig
- var diff *InstanceDiff
- var state *InstanceState
-
- return &EvalSequence{
- Nodes: []EvalNode{
- // Build the instance info
- &EvalInstanceInfo{
- Info: info,
- },
-
- // Get the saved diff for apply
- &EvalReadDiff{
- Name: stateId,
- Diff: &diff,
- },
-
- // Stop here if we don't actually have a diff
- &EvalIf{
- If: func(ctx EvalContext) (bool, error) {
- if diff == nil {
- return true, EvalEarlyExitError{}
- }
-
- if diff.GetAttributesLen() == 0 {
- return true, EvalEarlyExitError{}
- }
-
- return true, nil
- },
- Then: EvalNoop{},
- },
-
- // Normally we interpolate count as a preparation step before
- // a DynamicExpand, but an apply graph has pre-expanded nodes
- // and so the count would otherwise never be interpolated.
- //
- // This is redundant when there are multiple instances created
- // from the same config (count > 1) but harmless since the
- // underlying structures have mutexes to make this concurrency-safe.
- //
- // In most cases this isn't actually needed because we dealt with
- // all of the counts during the plan walk, but we do it here
- // for completeness because other code assumes that the
- // final count is always available during interpolation.
- //
- // Here we are just populating the interpolated value in-place
- // inside this RawConfig object, like we would in
- // NodeAbstractCountResource.
- &EvalInterpolate{
- Config: n.Config.RawCount,
- ContinueOnErr: true,
- },
-
- // We need to re-interpolate the config here, rather than
- // just using the diff's values directly, because we've
- // potentially learned more variable values during the
- // apply pass that weren't known when the diff was produced.
- &EvalInterpolate{
- Config: n.Config.RawConfig.Copy(),
- Resource: resource,
- Output: &config,
- },
-
- &EvalGetProvider{
- Name: n.ResolvedProvider,
- Output: &provider,
- },
-
- // Make a new diff with our newly-interpolated config.
- &EvalReadDataDiff{
- Info: info,
- Config: &config,
- Previous: &diff,
- Provider: &provider,
- Output: &diff,
- },
-
- &EvalReadDataApply{
- Info: info,
- Diff: &diff,
- Provider: &provider,
- Output: &state,
- },
-
- &EvalWriteState{
- Name: stateId,
- ResourceType: n.Config.Type,
- Provider: n.ResolvedProvider,
- Dependencies: stateDeps,
- State: &state,
- },
-
- // Clear the diff now that we've applied it, so
- // later nodes won't see a diff that's now a no-op.
- &EvalWriteDiff{
- Name: stateId,
- Diff: nil,
- },
-
- &EvalUpdateStateHook{},
- },
+// GraphNodeEvalable
+func (n *NodeApplyableResource) EvalTree() EvalNode {
+ addr := n.ResourceAddr()
+ config := n.Config
+ providerAddr := n.ResolvedProvider
+
+ if config == nil {
+ // Nothing to do, then.
+ log.Printf("[TRACE] NodeApplyableResource: no configuration present for %s", addr)
+ return &EvalNoop{}
}
-}
-
-func (n *NodeApplyableResource) 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, diffApply *InstanceDiff
- var state *InstanceState
- var resourceConfig *ResourceConfig
- var err error
- var createNew bool
- var createBeforeDestroyEnabled bool
-
- return &EvalSequence{
- Nodes: []EvalNode{
- // Build the instance info
- &EvalInstanceInfo{
- Info: info,
- },
-
- // Get the saved diff for apply
- &EvalReadDiff{
- Name: stateId,
- Diff: &diffApply,
- },
-
- // We don't want to do any destroys
- &EvalIf{
- If: func(ctx EvalContext) (bool, error) {
- if diffApply == nil {
- return true, EvalEarlyExitError{}
- }
-
- if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
- return true, EvalEarlyExitError{}
- }
-
- diffApply.SetDestroy(false)
- return true, nil
- },
- Then: EvalNoop{},
- },
-
- &EvalIf{
- If: func(ctx EvalContext) (bool, error) {
- destroy := false
- if diffApply != nil {
- destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
- }
-
- createBeforeDestroyEnabled =
- n.Config.Lifecycle.CreateBeforeDestroy &&
- destroy
-
- return createBeforeDestroyEnabled, nil
- },
- Then: &EvalDeposeState{
- Name: stateId,
- },
- },
-
- // Normally we interpolate count as a preparation step before
- // a DynamicExpand, but an apply graph has pre-expanded nodes
- // and so the count would otherwise never be interpolated.
- //
- // This is redundant when there are multiple instances created
- // from the same config (count > 1) but harmless since the
- // underlying structures have mutexes to make this concurrency-safe.
- //
- // In most cases this isn't actually needed because we dealt with
- // all of the counts during the plan walk, but we need to do this
- // in order to support interpolation of resource counts from
- // apply-time-interpolated expressions, such as those in
- // "provisioner" blocks.
- //
- // Here we are just populating the interpolated value in-place
- // inside this RawConfig object, like we would in
- // NodeAbstractCountResource.
- &EvalInterpolate{
- Config: n.Config.RawCount,
- ContinueOnErr: true,
- },
-
- &EvalInterpolate{
- Config: n.Config.RawConfig.Copy(),
- Resource: resource,
- Output: &resourceConfig,
- },
- &EvalGetProvider{
- Name: n.ResolvedProvider,
- Output: &provider,
- },
- &EvalReadState{
- Name: stateId,
- Output: &state,
- },
- // 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,
- },
- &EvalDiff{
- Info: info,
- Config: &resourceConfig,
- Resource: n.Config,
- Provider: &provider,
- Diff: &diffApply,
- State: &state,
- OutputDiff: &diffApply,
- },
-
- // Get the saved diff
- &EvalReadDiff{
- Name: stateId,
- Diff: &diff,
- },
-
- // Compare the diffs
- &EvalCompareDiff{
- Info: info,
- One: &diff,
- Two: &diffApply,
- },
-
- &EvalGetProvider{
- Name: n.ResolvedProvider,
- Output: &provider,
- },
- &EvalReadState{
- Name: stateId,
- Output: &state,
- },
- // Call pre-apply hook
- &EvalApplyPre{
- Info: info,
- State: &state,
- Diff: &diffApply,
- },
- &EvalApply{
- Info: info,
- State: &state,
- Diff: &diffApply,
- Provider: &provider,
- Output: &state,
- Error: &err,
- CreateNew: &createNew,
- },
- &EvalWriteState{
- Name: stateId,
- ResourceType: n.Config.Type,
- Provider: n.ResolvedProvider,
- Dependencies: stateDeps,
- State: &state,
- },
- &EvalApplyProvisioners{
- Info: info,
- State: &state,
- Resource: n.Config,
- InterpResource: resource,
- CreateNew: &createNew,
- Error: &err,
- When: config.ProvisionerWhenCreate,
- },
- &EvalIf{
- If: func(ctx EvalContext) (bool, error) {
- return createBeforeDestroyEnabled && err != nil, nil
- },
- Then: &EvalUndeposeState{
- Name: stateId,
- State: &state,
- },
- Else: &EvalWriteState{
- Name: stateId,
- ResourceType: n.Config.Type,
- Provider: n.ResolvedProvider,
- Dependencies: stateDeps,
- State: &state,
- },
- },
-
- // We clear the diff out here so that future nodes
- // don't see a diff that is already complete. There
- // is no longer a diff!
- &EvalWriteDiff{
- Name: stateId,
- Diff: nil,
- },
- &EvalApplyPost{
- Info: info,
- State: &state,
- Error: &err,
- },
- &EvalUpdateStateHook{},
- },
+ return &EvalWriteResourceState{
+ Addr: addr.Resource,
+ Config: config,
+ ProviderAddr: providerAddr,
}
}