package terraform import ( "log" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" ) // nodeExpandPlannableResource handles the first layer of resource // expansion. We need this extra layer so DynamicExpand is called twice for // the resource, the first to expand the Resource for each module instance, and // the second to expand each ResourceInstance for the expanded Resources. type nodeExpandPlannableResource struct { *NodeAbstractResource // ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD // during graph construction, if dependencies require us to force this // on regardless of what the configuration says. ForceCreateBeforeDestroy *bool // skipRefresh indicates that we should skip refreshing individual instances skipRefresh bool // We attach dependencies to the Resource during refresh, since the // instances are instantiated during DynamicExpand. dependencies []addrs.ConfigResource } var ( _ GraphNodeDestroyerCBD = (*nodeExpandPlannableResource)(nil) _ GraphNodeDynamicExpandable = (*nodeExpandPlannableResource)(nil) _ GraphNodeReferenceable = (*nodeExpandPlannableResource)(nil) _ GraphNodeReferencer = (*nodeExpandPlannableResource)(nil) _ GraphNodeConfigResource = (*nodeExpandPlannableResource)(nil) _ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil) _ GraphNodeAttachDependencies = (*nodeExpandPlannableResource)(nil) _ GraphNodeTargetable = (*nodeExpandPlannableResource)(nil) ) func (n *nodeExpandPlannableResource) Name() string { return n.NodeAbstractResource.Name() + " (expand)" } // GraphNodeAttachDependencies func (n *nodeExpandPlannableResource) AttachDependencies(deps []addrs.ConfigResource) { n.dependencies = deps } // GraphNodeDestroyerCBD func (n *nodeExpandPlannableResource) CreateBeforeDestroy() bool { if n.ForceCreateBeforeDestroy != nil { return *n.ForceCreateBeforeDestroy } // If we have no config, we just assume no if n.Config == nil || n.Config.Managed == nil { return false } return n.Config.Managed.CreateBeforeDestroy } // GraphNodeDestroyerCBD func (n *nodeExpandPlannableResource) ModifyCreateBeforeDestroy(v bool) error { n.ForceCreateBeforeDestroy = &v return nil } func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { var g Graph expander := ctx.InstanceExpander() moduleInstances := expander.ExpandModule(n.Addr.Module) // Add the current expanded resource to the graph for _, module := range moduleInstances { resAddr := n.Addr.Resource.Absolute(module) g.Add(&NodePlannableResource{ NodeAbstractResource: n.NodeAbstractResource, Addr: resAddr, ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy, dependencies: n.dependencies, skipRefresh: n.skipRefresh, }) } // Lock the state while we inspect it state := ctx.State().Lock() defer ctx.State().Unlock() var orphans []*states.Resource for _, res := range state.Resources(n.Addr) { found := false for _, m := range moduleInstances { if m.Equal(res.Addr.Module) { found = true break } } // Address form state was not found in the current config if !found { orphans = append(orphans, res) } } // The concrete resource factory we'll use for orphans concreteResourceOrphan := func(a *NodeAbstractResourceInstance) *NodePlannableResourceInstanceOrphan { // Add the config and state since we don't do that via transforms a.Config = n.Config a.ResolvedProvider = n.ResolvedProvider a.Schema = n.Schema a.ProvisionerSchemas = n.ProvisionerSchemas a.ProviderMetas = n.ProviderMetas a.Dependencies = n.dependencies return &NodePlannableResourceInstanceOrphan{ NodeAbstractResourceInstance: a, } } for _, res := range orphans { for key := range res.Instances { addr := res.Addr.Instance(key) abs := NewNodeAbstractResourceInstance(addr) abs.AttachResourceState(res) n := concreteResourceOrphan(abs) g.Add(n) } } return &g, nil } // NodePlannableResource represents a resource that is "plannable": // it is ready to be planned in order to create a diff. type NodePlannableResource struct { *NodeAbstractResource Addr addrs.AbsResource // ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD // during graph construction, if dependencies require us to force this // on regardless of what the configuration says. ForceCreateBeforeDestroy *bool // skipRefresh indicates that we should skip refreshing individual instances skipRefresh bool dependencies []addrs.ConfigResource } var ( _ GraphNodeModuleInstance = (*NodePlannableResource)(nil) _ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil) _ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil) _ GraphNodeReferenceable = (*NodePlannableResource)(nil) _ GraphNodeReferencer = (*NodePlannableResource)(nil) _ GraphNodeConfigResource = (*NodePlannableResource)(nil) _ GraphNodeAttachResourceConfig = (*NodePlannableResource)(nil) ) func (n *NodePlannableResource) Path() addrs.ModuleInstance { return n.Addr.Module } func (n *NodePlannableResource) Name() string { return n.Addr.String() } // GraphNodeExecutable func (n *NodePlannableResource) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { if n.Config == nil { // Nothing to do, then. log.Printf("[TRACE] NodeApplyableResource: no configuration present for %s", n.Name()) return nil } return n.writeResourceState(ctx, n.Addr) } // GraphNodeDestroyerCBD func (n *NodePlannableResource) CreateBeforeDestroy() bool { if n.ForceCreateBeforeDestroy != nil { return *n.ForceCreateBeforeDestroy } // If we have no config, we just assume no if n.Config == nil || n.Config.Managed == nil { return false } return n.Config.Managed.CreateBeforeDestroy } // GraphNodeDestroyerCBD func (n *NodePlannableResource) ModifyCreateBeforeDestroy(v bool) error { n.ForceCreateBeforeDestroy = &v return nil } // GraphNodeDynamicExpandable func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { var diags tfdiags.Diagnostics // We need to potentially rename an instance address in the state // if we're transitioning whether "count" is set at all. fixResourceCountSetTransition(ctx, n.Addr.Config(), n.Config.Count != nil) // Our instance expander should already have been informed about the // expansion of this resource and of all of its containing modules, so // it can tell us which instance addresses we need to process. expander := ctx.InstanceExpander() instanceAddrs := expander.ExpandResource(n.ResourceAddr().Absolute(ctx.Path())) // Our graph transformers require access to the full state, so we'll // temporarily lock it while we work on this. state := ctx.State().Lock() defer ctx.State().Unlock() // The concrete resource factory we'll use concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config a.ResolvedProvider = n.ResolvedProvider a.Schema = n.Schema a.ProvisionerSchemas = n.ProvisionerSchemas a.ProviderMetas = n.ProviderMetas a.dependsOn = n.dependsOn a.Dependencies = n.dependencies return &NodePlannableResourceInstance{ NodeAbstractResourceInstance: a, // By the time we're walking, we've figured out whether we need // to force on CreateBeforeDestroy due to dependencies on other // nodes that have it. ForceCreateBeforeDestroy: n.CreateBeforeDestroy(), skipRefresh: n.skipRefresh, } } // The concrete resource factory we'll use for orphans concreteResourceOrphan := func(a *NodeAbstractResourceInstance) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config a.ResolvedProvider = n.ResolvedProvider a.Schema = n.Schema a.ProvisionerSchemas = n.ProvisionerSchemas a.ProviderMetas = n.ProviderMetas return &NodePlannableResourceInstanceOrphan{ NodeAbstractResourceInstance: a, } } // Start creating the steps steps := []GraphTransformer{ // Expand the count or for_each (if present) &ResourceCountTransformer{ Concrete: concreteResource, Schema: n.Schema, Addr: n.ResourceAddr(), InstanceAddrs: instanceAddrs, }, // Add the count/for_each orphans &OrphanResourceInstanceCountTransformer{ Concrete: concreteResourceOrphan, Addr: n.Addr, InstanceAddrs: instanceAddrs, State: state, }, // Attach the state &AttachStateTransformer{State: state}, // Targeting &TargetsTransformer{Targets: n.Targets}, // Connect references so ordering is correct &ReferenceTransformer{}, // Make sure there is a single root &RootTransformer{}, } // Build the graph b := &BasicGraphBuilder{ Steps: steps, Validate: true, Name: "NodePlannableResource", } graph, diags := b.Build(ctx.Path()) return graph, diags.ErrWithWarnings() }