opentofu/terraform/node_resource_validate.go
Mitchell Hashimoto cf46e1c3e0
terraform: don't validate computed values in validate
This disables the computed value check for `count` during the validation
pass. This enables partial support for #3888 or #1497: as long as the
value is non-computed during the plan, complex values will work in
counts.

**Notably, this allows data source values to be present in counts!**

The "count" value can be disabled during validation safely because we
can treat it as if any field that uses `count.index` is computed for
validation. We then validate a single instance (as if `count = 1`) just
to make sure all required fields are set.
2017-01-27 21:15:43 -08:00

147 lines
3.3 KiB
Go

package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// NodeValidatableResource represents a resource that is used for validation
// only.
type NodeValidatableResource struct {
*NodeAbstractCountResource
}
// GraphNodeEvalable
func (n *NodeValidatableResource) EvalTree() EvalNode {
// Ensure we're validating
c := n.NodeAbstractCountResource
c.Validate = true
return c.EvalTree()
}
// GraphNodeDynamicExpandable
func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// Grab the state which we read
state, lock := ctx.State()
lock.RLock()
defer lock.RUnlock()
// Expand the resource count which must be available by now from EvalTree
count := 1
if n.Config.RawCount.Value() != unknownValue() {
var err error
count, err = n.Config.Count()
if err != nil {
return nil, err
}
}
// The concrete resource factory we'll use
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
// Add the config and state since we don't do that via transforms
a.Config = n.Config
return &NodeValidatableResourceInstance{
NodeAbstractResource: a,
}
}
// Start creating the steps
steps := []GraphTransformer{
// Expand the count.
&ResourceCountTransformer{
Concrete: concreteResource,
Count: count,
Addr: n.ResourceAddr(),
},
// Attach the state
&AttachStateTransformer{State: state},
// Targeting
&TargetsTransformer{ParsedTargets: 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: "NodeValidatableResource",
}
return b.Build(ctx.Path())
}
// This represents a _single_ resource instance to validate.
type NodeValidatableResourceInstance struct {
*NodeAbstractResource
}
// GraphNodeEvalable
func (n *NodeValidatableResourceInstance) EvalTree() EvalNode {
addr := n.NodeAbstractResource.Addr
// Build the resource for eval
resource := &Resource{
Name: addr.Name,
Type: addr.Type,
CountIndex: addr.Index,
}
if resource.CountIndex < 0 {
resource.CountIndex = 0
}
// Declare a bunch of variables that are used for state during
// evaluation. Most of this are written to by-address below.
var config *ResourceConfig
var provider ResourceProvider
seq := &EvalSequence{
Nodes: []EvalNode{
&EvalValidateResourceSelfRef{
Addr: &addr,
Config: &n.Config.RawConfig,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalValidateResource{
Provider: &provider,
Config: &config,
ResourceName: n.Config.Name,
ResourceType: n.Config.Type,
ResourceMode: n.Config.Mode,
},
},
}
// Validate all the provisioners
for _, p := range n.Config.Provisioners {
var provisioner ResourceProvisioner
seq.Nodes = append(seq.Nodes, &EvalGetProvisioner{
Name: p.Type,
Output: &provisioner,
}, &EvalInterpolate{
Config: p.RawConfig.Copy(),
Resource: resource,
Output: &config,
}, &EvalValidateProvisioner{
Provisioner: &provisioner,
Config: &config,
})
}
return seq
}