@@ -15,13 +15,15 @@ import (
1515
1616 "github.com/hashicorp/terraform/internal/addrs"
1717 "github.com/hashicorp/terraform/internal/configs"
18+ "github.com/hashicorp/terraform/internal/configs/configschema"
1819 "github.com/hashicorp/terraform/internal/genconfig"
1920 "github.com/hashicorp/terraform/internal/instances"
2021 "github.com/hashicorp/terraform/internal/lang/ephemeral"
2122 "github.com/hashicorp/terraform/internal/moduletest/mocking"
2223 "github.com/hashicorp/terraform/internal/plans"
2324 "github.com/hashicorp/terraform/internal/plans/deferring"
2425 "github.com/hashicorp/terraform/internal/providers"
26+ "github.com/hashicorp/terraform/internal/provisioners"
2527 "github.com/hashicorp/terraform/internal/states"
2628 "github.com/hashicorp/terraform/internal/tfdiags"
2729)
@@ -347,6 +349,16 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
347349 repData .EachValue = cty .DynamicVal
348350 }
349351
352+ // Even if we don't run the provisioners during plan we need to make sure they are valid
353+ if n .Config != nil && n .Config .Managed != nil {
354+ for _ , p := range n .Config .Managed .Provisioners {
355+ diags = diags .Append (n .validateProvisioner (ctx , p , n .Config .Managed .Connection , repData ))
356+ if diags .HasErrors () {
357+ return diags
358+ }
359+ }
360+ }
361+
350362 diags = diags .Append (n .replaceTriggered (ctx , repData ))
351363 if diags .HasErrors () {
352364 return diags
@@ -638,6 +650,11 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
638650 diags = diags .Append (configDiags )
639651 return nil , deferred , diags
640652 }
653+ diags = diags .Append (ctx .Deprecations ().ValidateAsConfig (configVal , n .ModulePath ()).InConfigBody (n .Config .Config , absAddr .String ()))
654+ if diags .HasErrors () {
655+ return nil , deferred , diags
656+ }
657+
641658 configVal , _ = configVal .UnmarkDeep ()
642659
643660 // Let's pretend we're reading the value as a data source so we
@@ -1009,6 +1026,75 @@ func (n *NodePlannableResourceInstance) generateResourceConfig(ctx EvalContext,
10091026 return genconfig .ExtractLegacyConfigFromState (schema .Body , state ), diags
10101027}
10111028
1029+ func (n * NodePlannableResourceInstance ) validateProvisioner (ctx EvalContext , p * configs.Provisioner , baseConn * configs.Connection , repData instances.RepetitionData ) tfdiags.Diagnostics {
1030+ var diags tfdiags.Diagnostics
1031+ provisioner , err := ctx .Provisioner (p .Type )
1032+ if err != nil {
1033+ diags = diags .Append (err )
1034+ return diags
1035+ }
1036+
1037+ if provisioner == nil {
1038+ return diags .Append (fmt .Errorf ("provisioner %s not initialized" , p .Type ))
1039+ }
1040+ provisionerSchema , err := ctx .ProvisionerSchema (p .Type )
1041+ if err != nil {
1042+ return diags .Append (fmt .Errorf ("failed to read schema for provisioner %s: %s" , p .Type , err ))
1043+ }
1044+ if provisionerSchema == nil {
1045+ return diags .Append (fmt .Errorf ("provisioner %s has no schema" , p .Type ))
1046+ }
1047+
1048+ // Validate the provisioner's own config first
1049+ configVal , _ , configDiags := n .evaluateBlock (ctx , p .Config , provisionerSchema , repData )
1050+ diags = diags .Append (configDiags )
1051+
1052+ if configVal == cty .NilVal {
1053+ // Should never happen for a well-behaved EvaluateBlock implementation
1054+ return diags .Append (fmt .Errorf ("EvaluateBlock returned nil value" ))
1055+ }
1056+
1057+ // Use unmarked value for validate request
1058+ unmarkedConfigVal , _ := configVal .UnmarkDeep ()
1059+ req := provisioners.ValidateProvisionerConfigRequest {
1060+ Config : unmarkedConfigVal ,
1061+ }
1062+
1063+ resp := provisioner .ValidateProvisionerConfig (req )
1064+ diags = diags .Append (resp .Diagnostics )
1065+
1066+ if p .Connection != nil {
1067+ // We can't comprehensively validate the connection config since its
1068+ // final structure is decided by the communicator and we can't instantiate
1069+ // that until we have a complete instance state. However, we *can* catch
1070+ // configuration keys that are not valid for *any* communicator, catching
1071+ // typos early rather than waiting until we actually try to run one of
1072+ // the resource's provisioners.
1073+
1074+ cfg := p .Connection .Config
1075+ if baseConn != nil {
1076+ // Merge the local config into the base connection config, if we
1077+ // both specified.
1078+ cfg = configs .MergeBodies (baseConn .Config , cfg )
1079+ }
1080+
1081+ _ , _ , connDiags := n .evaluateBlock (ctx , cfg , connectionBlockSupersetSchema , repData )
1082+ diags = diags .Append (connDiags )
1083+ } else if baseConn != nil {
1084+ // Just validate the baseConn directly.
1085+ _ , _ , connDiags := n .evaluateBlock (ctx , baseConn .Config , connectionBlockSupersetSchema , repData )
1086+ diags = diags .Append (connDiags )
1087+
1088+ }
1089+ return diags
1090+ }
1091+
1092+ func (n * NodePlannableResourceInstance ) evaluateBlock (ctx EvalContext , body hcl.Body , schema * configschema.Block , keyData instances.RepetitionData ) (cty.Value , hcl.Body , tfdiags.Diagnostics ) {
1093+ val , hclBody , diags := ctx .EvaluateBlock (body , schema , n .Addr .Resource , keyData )
1094+ diags = diags .Append (ctx .Deprecations ().ValidateAsConfig (val , n .ModulePath ()).InConfigBody (body , n .Addr .String ()))
1095+ return val , hclBody , diags
1096+ }
1097+
10121098// mergeDeps returns the union of 2 sets of dependencies
10131099func mergeDeps (a , b []addrs.ConfigResource ) []addrs.ConfigResource {
10141100 switch {
0 commit comments