package terraform import ( "fmt" "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" "github.com/hashicorp/terraform-plugin-sdk/internal/configs" "github.com/hashicorp/terraform-plugin-sdk/internal/dag" "github.com/hashicorp/terraform-plugin-sdk/internal/lang" ) // NodeApplyableOutput represents an output that is "applyable": // it is ready to be applied. type NodeApplyableOutput struct { Addr addrs.AbsOutputValue Config *configs.Output // Config is the output in the config } var ( _ GraphNodeSubPath = (*NodeApplyableOutput)(nil) _ RemovableIfNotTargeted = (*NodeApplyableOutput)(nil) _ GraphNodeTargetDownstream = (*NodeApplyableOutput)(nil) _ GraphNodeReferenceable = (*NodeApplyableOutput)(nil) _ GraphNodeReferencer = (*NodeApplyableOutput)(nil) _ GraphNodeReferenceOutside = (*NodeApplyableOutput)(nil) _ GraphNodeEvalable = (*NodeApplyableOutput)(nil) _ dag.GraphNodeDotter = (*NodeApplyableOutput)(nil) ) func (n *NodeApplyableOutput) Name() string { return n.Addr.String() } // GraphNodeSubPath func (n *NodeApplyableOutput) Path() addrs.ModuleInstance { return n.Addr.Module } // RemovableIfNotTargeted func (n *NodeApplyableOutput) RemoveIfNotTargeted() bool { // We need to add this so that this node will be removed if // it isn't targeted or a dependency of a target. return true } // GraphNodeTargetDownstream func (n *NodeApplyableOutput) TargetDownstream(targetedDeps, untargetedDeps *dag.Set) bool { // If any of the direct dependencies of an output are targeted then // the output must always be targeted as well, so its value will always // be up-to-date at the completion of an apply walk. return true } func referenceOutsideForOutput(addr addrs.AbsOutputValue) (selfPath, referencePath addrs.ModuleInstance) { // Output values have their expressions resolved in the context of the // module where they are defined. referencePath = addr.Module // ...but they are referenced in the context of their calling module. selfPath = addr.Module.Parent() return // uses named return values } // GraphNodeReferenceOutside implementation func (n *NodeApplyableOutput) ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance) { return referenceOutsideForOutput(n.Addr) } func referenceableAddrsForOutput(addr addrs.AbsOutputValue) []addrs.Referenceable { // An output in the root module can't be referenced at all. if addr.Module.IsRoot() { return nil } // Otherwise, we can be referenced via a reference to our output name // on the parent module's call, or via a reference to the entire call. // e.g. module.foo.bar or just module.foo . // Note that our ReferenceOutside method causes these addresses to be // relative to the calling module, not the module where the output // was declared. _, outp := addr.ModuleCallOutput() _, call := addr.Module.CallInstance() return []addrs.Referenceable{outp, call} } // GraphNodeReferenceable func (n *NodeApplyableOutput) ReferenceableAddrs() []addrs.Referenceable { return referenceableAddrsForOutput(n.Addr) } func referencesForOutput(c *configs.Output) []*addrs.Reference { impRefs, _ := lang.ReferencesInExpr(c.Expr) expRefs, _ := lang.References(c.DependsOn) l := len(impRefs) + len(expRefs) if l == 0 { return nil } refs := make([]*addrs.Reference, 0, l) refs = append(refs, impRefs...) refs = append(refs, expRefs...) return refs } // GraphNodeReferencer func (n *NodeApplyableOutput) References() []*addrs.Reference { return appendResourceDestroyReferences(referencesForOutput(n.Config)) } // GraphNodeEvalable func (n *NodeApplyableOutput) EvalTree() EvalNode { return &EvalSequence{ Nodes: []EvalNode{ &EvalOpFilter{ Ops: []walkOperation{walkRefresh, walkPlan, walkApply, walkValidate, walkDestroy, walkPlanDestroy}, Node: &EvalWriteOutput{ Addr: n.Addr.OutputValue, Sensitive: n.Config.Sensitive, Expr: n.Config.Expr, }, }, }, } } // dag.GraphNodeDotter impl. func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { return &dag.DotNode{ Name: name, Attrs: map[string]string{ "label": n.Name(), "shape": "note", }, } } // NodeDestroyableOutput represents an output that is "destroybale": // its application will remove the output from the state. type NodeDestroyableOutput struct { Addr addrs.AbsOutputValue Config *configs.Output // Config is the output in the config } var ( _ GraphNodeSubPath = (*NodeDestroyableOutput)(nil) _ RemovableIfNotTargeted = (*NodeDestroyableOutput)(nil) _ GraphNodeTargetDownstream = (*NodeDestroyableOutput)(nil) _ GraphNodeReferencer = (*NodeDestroyableOutput)(nil) _ GraphNodeEvalable = (*NodeDestroyableOutput)(nil) _ dag.GraphNodeDotter = (*NodeDestroyableOutput)(nil) ) func (n *NodeDestroyableOutput) Name() string { return fmt.Sprintf("%s (destroy)", n.Addr.String()) } // GraphNodeSubPath func (n *NodeDestroyableOutput) Path() addrs.ModuleInstance { return n.Addr.Module } // RemovableIfNotTargeted func (n *NodeDestroyableOutput) RemoveIfNotTargeted() bool { // We need to add this so that this node will be removed if // it isn't targeted or a dependency of a target. return true } // This will keep the destroy node in the graph if its corresponding output // node is also in the destroy graph. func (n *NodeDestroyableOutput) TargetDownstream(targetedDeps, untargetedDeps *dag.Set) bool { return true } // GraphNodeReferencer func (n *NodeDestroyableOutput) References() []*addrs.Reference { return referencesForOutput(n.Config) } // GraphNodeEvalable func (n *NodeDestroyableOutput) EvalTree() EvalNode { return &EvalDeleteOutput{ Addr: n.Addr.OutputValue, } } // dag.GraphNodeDotter impl. func (n *NodeDestroyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { return &dag.DotNode{ Name: name, Attrs: map[string]string{ "label": n.Name(), "shape": "note", }, } }