mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
783a07d9e8
Go 1.19's "fmt" has some awareness of the new doc comment formatting conventions and adjusts the presentation of the source comments to make it clearer how godoc would interpret them. Therefore this commit includes various updates made by "go fmt" to acheve that. In line with our usual convention that we make stylistic/grammar/spelling tweaks typically only when we're "in the area" changing something else anyway, I also took this opportunity to review most of the comments that this updated to see if there were any other opportunities to improve them.
499 lines
17 KiB
Go
499 lines
17 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/dag"
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// ConcreteResourceNodeFunc is a callback type used to convert an
|
|
// abstract resource to a concrete one of some type.
|
|
type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex
|
|
|
|
// GraphNodeConfigResource is implemented by any nodes that represent a resource.
|
|
// The type of operation cannot be assumed, only that this node represents
|
|
// the given resource.
|
|
type GraphNodeConfigResource interface {
|
|
ResourceAddr() addrs.ConfigResource
|
|
}
|
|
|
|
// ConcreteResourceInstanceNodeFunc is a callback type used to convert an
|
|
// abstract resource instance to a concrete one of some type.
|
|
type ConcreteResourceInstanceNodeFunc func(*NodeAbstractResourceInstance) dag.Vertex
|
|
|
|
// GraphNodeResourceInstance is implemented by any nodes that represent
|
|
// a resource instance. A single resource may have multiple instances if,
|
|
// for example, the "count" or "for_each" argument is used for it in
|
|
// configuration.
|
|
type GraphNodeResourceInstance interface {
|
|
ResourceInstanceAddr() addrs.AbsResourceInstance
|
|
|
|
// StateDependencies returns any inter-resource dependencies that are
|
|
// stored in the state.
|
|
StateDependencies() []addrs.ConfigResource
|
|
}
|
|
|
|
// NodeAbstractResource represents a resource that has no associated
|
|
// operations. It registers all the interfaces for a resource that common
|
|
// across multiple operation types.
|
|
type NodeAbstractResource struct {
|
|
Addr addrs.ConfigResource
|
|
|
|
// The fields below will be automatically set using the Attach
|
|
// interfaces if you're running those transforms, but also be explicitly
|
|
// set if you already have that information.
|
|
|
|
Schema *configschema.Block // Schema for processing the configuration body
|
|
SchemaVersion uint64 // Schema version of "Schema", as decided by the provider
|
|
Config *configs.Resource // Config is the resource in the config
|
|
|
|
// ProviderMetas is the provider_meta configs for the module this resource belongs to
|
|
ProviderMetas map[addrs.Provider]*configs.ProviderMeta
|
|
|
|
ProvisionerSchemas map[string]*configschema.Block
|
|
|
|
// Set from GraphNodeTargetable
|
|
Targets []addrs.Targetable
|
|
|
|
// Set from AttachDataResourceDependsOn
|
|
dependsOn []addrs.ConfigResource
|
|
forceDependsOn bool
|
|
|
|
// The address of the provider this resource will use
|
|
ResolvedProvider addrs.AbsProviderConfig
|
|
|
|
// This resource may expand into instances which need to be imported.
|
|
importTargets []*ImportTarget
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeReferenceable = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeReferencer = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeProviderConsumer = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeProvisionerConsumer = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeConfigResource = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeAttachResourceSchema = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeAttachProvisionerSchema = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResource)(nil)
|
|
_ GraphNodeTargetable = (*NodeAbstractResource)(nil)
|
|
_ graphNodeAttachDataResourceDependsOn = (*NodeAbstractResource)(nil)
|
|
_ dag.GraphNodeDotter = (*NodeAbstractResource)(nil)
|
|
)
|
|
|
|
// NewNodeAbstractResource creates an abstract resource graph node for
|
|
// the given absolute resource address.
|
|
func NewNodeAbstractResource(addr addrs.ConfigResource) *NodeAbstractResource {
|
|
return &NodeAbstractResource{
|
|
Addr: addr,
|
|
}
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeModuleInstance = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeReferenceable = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeReferencer = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeProviderConsumer = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeProvisionerConsumer = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeConfigResource = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeResourceInstance = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeAttachResourceState = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeAttachResourceSchema = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResourceInstance)(nil)
|
|
_ GraphNodeTargetable = (*NodeAbstractResourceInstance)(nil)
|
|
_ dag.GraphNodeDotter = (*NodeAbstractResourceInstance)(nil)
|
|
)
|
|
|
|
func (n *NodeAbstractResource) Name() string {
|
|
return n.ResourceAddr().String()
|
|
}
|
|
|
|
// GraphNodeModulePath
|
|
func (n *NodeAbstractResource) ModulePath() addrs.Module {
|
|
return n.Addr.Module
|
|
}
|
|
|
|
// GraphNodeReferenceable
|
|
func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
|
|
return []addrs.Referenceable{n.Addr.Resource}
|
|
}
|
|
|
|
func (n *NodeAbstractResource) Import(addr *ImportTarget) {
|
|
|
|
}
|
|
|
|
// GraphNodeReferencer
|
|
func (n *NodeAbstractResource) References() []*addrs.Reference {
|
|
// If we have a config then we prefer to use that.
|
|
if c := n.Config; c != nil {
|
|
var result []*addrs.Reference
|
|
|
|
result = append(result, n.DependsOn()...)
|
|
|
|
if n.Schema == nil {
|
|
// Should never happen, but we'll log if it does so that we can
|
|
// see this easily when debugging.
|
|
log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name())
|
|
}
|
|
|
|
refs, _ := lang.ReferencesInExpr(c.Count)
|
|
result = append(result, refs...)
|
|
refs, _ = lang.ReferencesInExpr(c.ForEach)
|
|
result = append(result, refs...)
|
|
|
|
for _, expr := range c.TriggersReplacement {
|
|
refs, _ = lang.ReferencesInExpr(expr)
|
|
result = append(result, refs...)
|
|
}
|
|
|
|
// ReferencesInBlock() requires a schema
|
|
if n.Schema != nil {
|
|
refs, _ = lang.ReferencesInBlock(c.Config, n.Schema)
|
|
result = append(result, refs...)
|
|
}
|
|
|
|
if c.Managed != nil {
|
|
if c.Managed.Connection != nil {
|
|
refs, _ = lang.ReferencesInBlock(c.Managed.Connection.Config, connectionBlockSupersetSchema)
|
|
result = append(result, refs...)
|
|
}
|
|
|
|
for _, p := range c.Managed.Provisioners {
|
|
if p.When != configs.ProvisionerWhenCreate {
|
|
continue
|
|
}
|
|
if p.Connection != nil {
|
|
refs, _ = lang.ReferencesInBlock(p.Connection.Config, connectionBlockSupersetSchema)
|
|
result = append(result, refs...)
|
|
}
|
|
|
|
schema := n.ProvisionerSchemas[p.Type]
|
|
if schema == nil {
|
|
log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name())
|
|
}
|
|
refs, _ = lang.ReferencesInBlock(p.Config, schema)
|
|
result = append(result, refs...)
|
|
}
|
|
}
|
|
|
|
for _, check := range c.Preconditions {
|
|
refs, _ := lang.ReferencesInExpr(check.Condition)
|
|
result = append(result, refs...)
|
|
refs, _ = lang.ReferencesInExpr(check.ErrorMessage)
|
|
result = append(result, refs...)
|
|
}
|
|
for _, check := range c.Postconditions {
|
|
refs, _ := lang.ReferencesInExpr(check.Condition)
|
|
result = append(result, refs...)
|
|
refs, _ = lang.ReferencesInExpr(check.ErrorMessage)
|
|
result = append(result, refs...)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Otherwise, we have no references.
|
|
return nil
|
|
}
|
|
|
|
func (n *NodeAbstractResource) DependsOn() []*addrs.Reference {
|
|
var result []*addrs.Reference
|
|
if c := n.Config; c != nil {
|
|
|
|
for _, traversal := range c.DependsOn {
|
|
ref, diags := addrs.ParseRef(traversal)
|
|
if diags.HasErrors() {
|
|
// We ignore this here, because this isn't a suitable place to return
|
|
// errors. This situation should be caught and rejected during
|
|
// validation.
|
|
log.Printf("[ERROR] Can't parse %#v from depends_on as reference: %s", traversal, diags.Err())
|
|
continue
|
|
}
|
|
|
|
result = append(result, ref)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) {
|
|
n.ResolvedProvider = p
|
|
}
|
|
|
|
// GraphNodeProviderConsumer
|
|
func (n *NodeAbstractResource) ProvidedBy() (addrs.ProviderConfig, bool) {
|
|
// If we have a config we prefer that above all else
|
|
if n.Config != nil {
|
|
relAddr := n.Config.ProviderConfigAddr()
|
|
return addrs.LocalProviderConfig{
|
|
LocalName: relAddr.LocalName,
|
|
Alias: relAddr.Alias,
|
|
}, false
|
|
}
|
|
|
|
// No provider configuration found; return a default address
|
|
return addrs.AbsProviderConfig{
|
|
Provider: n.Provider(),
|
|
Module: n.ModulePath(),
|
|
}, false
|
|
}
|
|
|
|
// GraphNodeProviderConsumer
|
|
func (n *NodeAbstractResource) Provider() addrs.Provider {
|
|
if n.Config != nil {
|
|
return n.Config.Provider
|
|
}
|
|
return addrs.ImpliedProviderForUnqualifiedType(n.Addr.Resource.ImpliedProvider())
|
|
}
|
|
|
|
// GraphNodeProvisionerConsumer
|
|
func (n *NodeAbstractResource) ProvisionedBy() []string {
|
|
// If we have no configuration, then we have no provisioners
|
|
if n.Config == nil || n.Config.Managed == nil {
|
|
return nil
|
|
}
|
|
|
|
// Build the list of provisioners we need based on the configuration.
|
|
// It is okay to have duplicates here.
|
|
result := make([]string, len(n.Config.Managed.Provisioners))
|
|
for i, p := range n.Config.Managed.Provisioners {
|
|
result[i] = p.Type
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GraphNodeProvisionerConsumer
|
|
func (n *NodeAbstractResource) AttachProvisionerSchema(name string, schema *configschema.Block) {
|
|
if n.ProvisionerSchemas == nil {
|
|
n.ProvisionerSchemas = make(map[string]*configschema.Block)
|
|
}
|
|
n.ProvisionerSchemas[name] = schema
|
|
}
|
|
|
|
// GraphNodeResource
|
|
func (n *NodeAbstractResource) ResourceAddr() addrs.ConfigResource {
|
|
return n.Addr
|
|
}
|
|
|
|
// GraphNodeTargetable
|
|
func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) {
|
|
n.Targets = targets
|
|
}
|
|
|
|
// graphNodeAttachDataResourceDependsOn
|
|
func (n *NodeAbstractResource) AttachDataResourceDependsOn(deps []addrs.ConfigResource, force bool) {
|
|
n.dependsOn = deps
|
|
n.forceDependsOn = force
|
|
}
|
|
|
|
// GraphNodeAttachResourceConfig
|
|
func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource) {
|
|
n.Config = c
|
|
}
|
|
|
|
// GraphNodeAttachResourceSchema impl
|
|
func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block, version uint64) {
|
|
n.Schema = schema
|
|
n.SchemaVersion = version
|
|
}
|
|
|
|
// GraphNodeAttachProviderMetaConfigs impl
|
|
func (n *NodeAbstractResource) AttachProviderMetaConfigs(c map[addrs.Provider]*configs.ProviderMeta) {
|
|
n.ProviderMetas = c
|
|
}
|
|
|
|
// GraphNodeDotter impl.
|
|
func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
|
|
return &dag.DotNode{
|
|
Name: name,
|
|
Attrs: map[string]string{
|
|
"label": n.Name(),
|
|
"shape": "box",
|
|
},
|
|
}
|
|
}
|
|
|
|
// writeResourceState ensures that a suitable resource-level state record is
|
|
// present in the state, if that's required for the "each mode" of that
|
|
// resource.
|
|
//
|
|
// This is important primarily for the situation where count = 0, since this
|
|
// eval is the only change we get to set the resource "each mode" to list
|
|
// in that case, allowing expression evaluation to see it as a zero-element list
|
|
// rather than as not set at all.
|
|
func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.AbsResource) (diags tfdiags.Diagnostics) {
|
|
state := ctx.State()
|
|
|
|
// We'll record our expansion decision in the shared "expander" object
|
|
// so that later operations (i.e. DynamicExpand and expression evaluation)
|
|
// can refer to it. Since this node represents the abstract module, we need
|
|
// to expand the module here to create all resources.
|
|
expander := ctx.InstanceExpander()
|
|
|
|
switch {
|
|
case n.Config.Count != nil:
|
|
count, countDiags := evaluateCountExpression(n.Config.Count, ctx)
|
|
diags = diags.Append(countDiags)
|
|
if countDiags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
state.SetResourceProvider(addr, n.ResolvedProvider)
|
|
expander.SetResourceCount(addr.Module, n.Addr.Resource, count)
|
|
|
|
case n.Config.ForEach != nil:
|
|
forEach, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx)
|
|
diags = diags.Append(forEachDiags)
|
|
if forEachDiags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
// This method takes care of all of the business logic of updating this
|
|
// while ensuring that any existing instances are preserved, etc.
|
|
state.SetResourceProvider(addr, n.ResolvedProvider)
|
|
expander.SetResourceForEach(addr.Module, n.Addr.Resource, forEach)
|
|
|
|
default:
|
|
state.SetResourceProvider(addr, n.ResolvedProvider)
|
|
expander.SetResourceSingle(addr.Module, n.Addr.Resource)
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
// readResourceInstanceState reads the current object for a specific instance in
|
|
// the state.
|
|
func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return nil, diags
|
|
}
|
|
|
|
log.Printf("[TRACE] readResourceInstanceState: reading state for %s", addr)
|
|
|
|
src := ctx.State().ResourceInstanceObject(addr, states.CurrentGen)
|
|
if src == nil {
|
|
// Presumably we only have deposed objects, then.
|
|
log.Printf("[TRACE] readResourceInstanceState: no state present for %s", addr)
|
|
return nil, nil
|
|
}
|
|
|
|
schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource())
|
|
if schema == nil {
|
|
// Shouldn't happen since we should've failed long ago if no schema is present
|
|
return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr))
|
|
}
|
|
src, upgradeDiags := upgradeResourceState(addr, provider, src, schema, currentVersion)
|
|
if n.Config != nil {
|
|
upgradeDiags = upgradeDiags.InConfigBody(n.Config.Config, addr.String())
|
|
}
|
|
diags = diags.Append(upgradeDiags)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
obj, err := src.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
}
|
|
|
|
return obj, diags
|
|
}
|
|
|
|
// readResourceInstanceStateDeposed reads the deposed object for a specific
|
|
// instance in the state.
|
|
func (n *NodeAbstractResource) readResourceInstanceStateDeposed(ctx EvalContext, addr addrs.AbsResourceInstance, key states.DeposedKey) (*states.ResourceInstanceObject, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return nil, diags
|
|
}
|
|
|
|
if key == states.NotDeposed {
|
|
return nil, diags.Append(fmt.Errorf("readResourceInstanceStateDeposed used with no instance key; this is a bug in Terraform and should be reported"))
|
|
}
|
|
|
|
log.Printf("[TRACE] readResourceInstanceStateDeposed: reading state for %s deposed object %s", addr, key)
|
|
|
|
src := ctx.State().ResourceInstanceObject(addr, key)
|
|
if src == nil {
|
|
// Presumably we only have deposed objects, then.
|
|
log.Printf("[TRACE] readResourceInstanceStateDeposed: no state present for %s deposed object %s", addr, key)
|
|
return nil, diags
|
|
}
|
|
|
|
schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource())
|
|
if schema == nil {
|
|
// Shouldn't happen since we should've failed long ago if no schema is present
|
|
return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr))
|
|
|
|
}
|
|
|
|
src, upgradeDiags := upgradeResourceState(addr, provider, src, schema, currentVersion)
|
|
if n.Config != nil {
|
|
upgradeDiags = upgradeDiags.InConfigBody(n.Config.Config, addr.String())
|
|
}
|
|
diags = diags.Append(upgradeDiags)
|
|
if diags.HasErrors() {
|
|
// Note that we don't have any channel to return warnings here. We'll
|
|
// accept that for now since warnings during a schema upgrade would
|
|
// be pretty weird anyway, since this operation is supposed to seem
|
|
// invisible to the user.
|
|
return nil, diags
|
|
}
|
|
|
|
obj, err := src.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
}
|
|
|
|
return obj, diags
|
|
}
|
|
|
|
// graphNodesAreResourceInstancesInDifferentInstancesOfSameModule is an
|
|
// annoyingly-task-specific helper function that returns true if and only if
|
|
// the following conditions hold:
|
|
// - Both of the given vertices represent specific resource instances, as
|
|
// opposed to unexpanded resources or any other non-resource-related object.
|
|
// - The module instance addresses for both of the resource instances belong
|
|
// to the same static module.
|
|
// - The module instance addresses for both of the resource instances are
|
|
// not equal, indicating that they belong to different instances of the
|
|
// same module.
|
|
//
|
|
// This result can be used as a way to compensate for the effects of
|
|
// conservative analysis passes in our graph builders which make their
|
|
// decisions based only on unexpanded addresses, often so that they can behave
|
|
// correctly for interactions between expanded and not-yet-expanded objects.
|
|
//
|
|
// Callers of this helper function will typically skip adding an edge between
|
|
// the two given nodes if this function returns true.
|
|
func graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(a, b dag.Vertex) bool {
|
|
aRI, aOK := a.(GraphNodeResourceInstance)
|
|
bRI, bOK := b.(GraphNodeResourceInstance)
|
|
if !(aOK && bOK) {
|
|
return false
|
|
}
|
|
aModInst := aRI.ResourceInstanceAddr().Module
|
|
bModInst := bRI.ResourceInstanceAddr().Module
|
|
aMod := aModInst.Module()
|
|
bMod := bModInst.Module()
|
|
if !aMod.Equal(bMod) {
|
|
return false
|
|
}
|
|
return !aModInst.Equal(bModInst)
|
|
}
|