opentofu/terraform/node_resource_validate.go
Martin Atkins 0e963db2c5 Detect and reject unknown attributes in "connection" blocks
Since the validation of connection blocks is delegated to the communicator
selected by "type", we were not previously doing any validation of the
attribute names in these blocks until running provisioners during apply.

Proper validation here requires us to already have the instance state,
since the final connection info is a merge of values provided in config
with values assigned automatically by the resource. However, we can do
some basic name validation to catch typos during the validation pass, even
though semantic validation and checking for missing attributes will still
wait until the provisioner is instantiated.

This fixes #6582 as much as we reasonably can.
2017-04-06 13:46:43 -07:00

159 lines
3.5 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
var connConfig *ResourceConfig
seq.Nodes = append(
seq.Nodes,
&EvalGetProvisioner{
Name: p.Type,
Output: &provisioner,
},
&EvalInterpolate{
Config: p.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalInterpolate{
Config: p.ConnInfo.Copy(),
Resource: resource,
Output: &connConfig,
},
&EvalValidateProvisioner{
Provisioner: &provisioner,
Config: &config,
ConnConfig: &connConfig,
},
)
}
return seq
}