mirror of https://github.com/hashicorp/terraform
Action Validate Graph (#37514)
* action validation preparation: add NodeAbstractAction I decided to (loosely) follow the NodeResourceAbstract pattern so that, in the next commit, I can configure the validate walk to create a NodeValidatableAction vs the default nodeExpandActionDeclaration. I am putting this in a separate commit for easier review, to show that this did not impact the current implementation.pull/37530/head
parent
266cd93c2f
commit
4fadc32a9e
@ -0,0 +1,112 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/dag"
|
||||
"github.com/hashicorp/terraform/internal/lang/langrefs"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
)
|
||||
|
||||
// NodeAbstractAction represents an action that has no associated
|
||||
// operations.
|
||||
type NodeAbstractAction struct {
|
||||
Addr addrs.ConfigAction
|
||||
Config configs.Action
|
||||
|
||||
// The fields below will be automatically set using the Attach interfaces if
|
||||
// you're running those transforms, but also can be explicitly set if you
|
||||
// already have that information.
|
||||
|
||||
// The address of the provider this action will use
|
||||
ResolvedProvider addrs.AbsProviderConfig
|
||||
Schema *providers.ActionSchema
|
||||
}
|
||||
|
||||
var (
|
||||
_ GraphNodeReferenceable = (*NodeAbstractAction)(nil)
|
||||
_ GraphNodeReferencer = (*NodeAbstractAction)(nil)
|
||||
_ GraphNodeConfigAction = (*NodeAbstractAction)(nil)
|
||||
_ GraphNodeAttachActionSchema = (*NodeAbstractAction)(nil)
|
||||
_ GraphNodeProviderConsumer = (*NodeAbstractAction)(nil)
|
||||
)
|
||||
|
||||
func (n NodeAbstractAction) Name() string {
|
||||
return n.Addr.String()
|
||||
}
|
||||
|
||||
// ConcreteActionNodeFunc is a callback type used to convert an
|
||||
// abstract action to a concrete one of some type.
|
||||
type ConcreteActionNodeFunc func(*NodeAbstractAction) dag.Vertex
|
||||
|
||||
// I'm not sure why my ConcreteActionNodeFUnction kept being nil in tests, but
|
||||
// this is much more robust. If it isn't a validate walk, we need
|
||||
// nodeExpandActionDeclaration.
|
||||
func DefaultConcreteActionNodeFunc(a *NodeAbstractAction) dag.Vertex {
|
||||
return &nodeExpandActionDeclaration{
|
||||
NodeAbstractAction: a,
|
||||
}
|
||||
}
|
||||
|
||||
// GraphNodeConfigAction
|
||||
func (n NodeAbstractAction) ActionAddr() addrs.ConfigAction {
|
||||
return n.Addr
|
||||
}
|
||||
|
||||
func (n NodeAbstractAction) ModulePath() addrs.Module {
|
||||
return n.Addr.Module
|
||||
}
|
||||
|
||||
func (n *NodeAbstractAction) ReferenceableAddrs() []addrs.Referenceable {
|
||||
return []addrs.Referenceable{n.Addr.Action}
|
||||
}
|
||||
|
||||
func (n *NodeAbstractAction) References() []*addrs.Reference {
|
||||
var result []*addrs.Reference
|
||||
c := n.Config
|
||||
|
||||
refs, _ := langrefs.ReferencesInExpr(addrs.ParseRef, c.Count)
|
||||
result = append(result, refs...)
|
||||
refs, _ = langrefs.ReferencesInExpr(addrs.ParseRef, c.ForEach)
|
||||
result = append(result, refs...)
|
||||
|
||||
if n.Schema != nil {
|
||||
refs, _ = langrefs.ReferencesInBlock(addrs.ParseRef, c.Config, n.Schema.ConfigSchema)
|
||||
result = append(result, refs...)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *NodeAbstractAction) AttachActionSchema(schema *providers.ActionSchema) {
|
||||
n.Schema = schema
|
||||
}
|
||||
|
||||
func (n *NodeAbstractAction) ProvidedBy() (addrs.ProviderConfig, bool) {
|
||||
// If the resolvedProvider is set, use that
|
||||
if n.ResolvedProvider.Provider.Type != "" {
|
||||
return n.ResolvedProvider, true
|
||||
}
|
||||
|
||||
// otherwise refer back to the config
|
||||
relAddr := n.Config.ProviderConfigAddr()
|
||||
return addrs.LocalProviderConfig{
|
||||
LocalName: relAddr.LocalName,
|
||||
Alias: relAddr.Alias,
|
||||
}, false
|
||||
}
|
||||
|
||||
func (n *NodeAbstractAction) Provider() addrs.Provider {
|
||||
if n.Config.Provider.Type != "" {
|
||||
return n.Config.Provider
|
||||
}
|
||||
|
||||
return addrs.ImpliedProviderForUnqualifiedType(n.Addr.Action.ImpliedProvider())
|
||||
}
|
||||
|
||||
func (n *NodeAbstractAction) SetProvider(p addrs.AbsProviderConfig) {
|
||||
n.ResolvedProvider = p
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// NodeValidatableAction represents an action that is used for validation only.
|
||||
type NodeValidatableAction struct {
|
||||
*NodeAbstractAction
|
||||
}
|
||||
|
||||
var (
|
||||
_ GraphNodeModuleInstance = (*NodeValidatableAction)(nil)
|
||||
_ GraphNodeExecutable = (*NodeValidatableAction)(nil)
|
||||
_ GraphNodeReferenceable = (*NodeValidatableAction)(nil)
|
||||
_ GraphNodeReferencer = (*NodeValidatableAction)(nil)
|
||||
_ GraphNodeConfigAction = (*NodeValidatableAction)(nil)
|
||||
_ GraphNodeAttachActionSchema = (*NodeValidatableAction)(nil)
|
||||
)
|
||||
|
||||
func (n *NodeValidatableAction) Path() addrs.ModuleInstance {
|
||||
// There is no expansion during validation, so we evaluate everything as
|
||||
// single module instances.
|
||||
return n.Addr.Module.UnkeyedInstanceShim()
|
||||
}
|
||||
|
||||
func (n *NodeValidatableAction) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
|
||||
diags = diags.Append(err)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
keyData := EvalDataForNoInstanceKey
|
||||
|
||||
switch {
|
||||
case n.Config.Count != nil:
|
||||
// If the config block has count, we'll evaluate with an unknown
|
||||
// number as count.index so we can still type check even though
|
||||
// we won't expand count until the plan phase.
|
||||
keyData = InstanceKeyEvalData{
|
||||
CountIndex: cty.UnknownVal(cty.Number),
|
||||
}
|
||||
|
||||
// Basic type-checking of the count argument. More complete validation
|
||||
// of this will happen when we DynamicExpand during the plan walk.
|
||||
_, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx)
|
||||
diags = diags.Append(countDiags)
|
||||
|
||||
case n.Config.ForEach != nil:
|
||||
keyData = InstanceKeyEvalData{
|
||||
EachKey: cty.UnknownVal(cty.String),
|
||||
EachValue: cty.UnknownVal(cty.DynamicPseudoType),
|
||||
}
|
||||
|
||||
// Evaluate the for_each expression here so we can expose the diagnostics
|
||||
forEachDiags := newForEachEvaluator(n.Config.ForEach, ctx, false).ValidateActionValue()
|
||||
diags = diags.Append(forEachDiags)
|
||||
}
|
||||
|
||||
schema := providerSchema.SchemaForActionType(n.Config.Type)
|
||||
if schema.ConfigSchema == nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid action type",
|
||||
Detail: fmt.Sprintf("The provider %s does not support action type %q.", n.Provider().ForDisplay(), n.Config.Type),
|
||||
Subject: &n.Config.TypeRange,
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
// We currently only support unlinked actions, so we send a diagnostic for other types
|
||||
if n.Schema.Lifecycle != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Lifecycle actions are not supported",
|
||||
Detail: "This version of Terraform does not support lifecycle actions",
|
||||
Subject: n.Config.DeclRange.Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
if n.Schema.Linked != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Linked actions are not supported",
|
||||
Detail: "This version of Terraform does not support linked actions",
|
||||
Subject: n.Config.DeclRange.Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
var configVal cty.Value
|
||||
var valDiags tfdiags.Diagnostics
|
||||
if n.Config.Config != nil {
|
||||
configVal, _, valDiags = ctx.EvaluateBlock(n.Config.Config, schema.ConfigSchema, nil, keyData)
|
||||
diags = diags.Append(valDiags)
|
||||
if valDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
} else {
|
||||
configVal = cty.NullVal(n.Schema.ConfigSchema.ImpliedType())
|
||||
}
|
||||
|
||||
// Use unmarked value for validate request
|
||||
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
||||
log.Printf("[TRACE] Validating config for %q", n.Addr)
|
||||
req := providers.ValidateActionConfigRequest{
|
||||
TypeName: n.Config.Type,
|
||||
Config: unmarkedConfigVal,
|
||||
}
|
||||
|
||||
resp := provider.ValidateActionConfig(req)
|
||||
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
|
||||
|
||||
return diags
|
||||
}
|
||||
Loading…
Reference in new issue