Skip to content

Commit 6d2177d

Browse files
Backport of actions: return an error if config is omitted but the schema has required attrs into v1.14 (#37643)
* backport of commit 2084154 * backport of commit 8a889aa --------- Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
1 parent 085e0bd commit 6d2177d

File tree

3 files changed

+135
-66
lines changed

3 files changed

+135
-66
lines changed

internal/terraform/context_validate_test.go

Lines changed: 117 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3575,60 +3575,114 @@ func TestContext2Validate_queryList(t *testing.T) {
35753575
// Action Validation is largely exercised in context_plan_actions_test.go
35763576
func TestContext2Validate_action(t *testing.T) {
35773577
tests := map[string]struct {
3578-
config string
3579-
wantErr string
3578+
config string
3579+
wantErr string
3580+
expectValidateCalled bool
35803581
}{
35813582
"valid config": {
35823583
`
3583-
terraform {
3584-
required_providers {
3585-
test = {
3586-
source = "hashicorp/test"
3587-
version = "1.0.0"
3584+
terraform {
3585+
required_providers {
3586+
test = {
3587+
source = "hashicorp/test"
3588+
version = "1.0.0"
3589+
}
3590+
}
3591+
}
3592+
action "test_register" "foo" {
3593+
config {
3594+
host = "cmdb.snot"
3595+
}
3596+
}
3597+
resource "test_instance" "foo" {
3598+
lifecycle {
3599+
action_trigger {
3600+
events = [after_create]
3601+
actions = [action.test_register.foo]
3602+
}
3603+
}
3604+
}
3605+
`,
3606+
"",
3607+
true,
3608+
},
3609+
"validly null config": { // this doesn't seem likely, but let's make sure nothing panics.
3610+
`
3611+
terraform {
3612+
required_providers {
3613+
test = {
3614+
source = "hashicorp/test"
3615+
version = "1.0.0"
3616+
}
3617+
}
35883618
}
3589-
}
3590-
}
3591-
action "test_register" "foo" {
3592-
config {
3593-
host = "cmdb.snot"
3594-
}
3595-
}
3596-
resource "test_instance" "foo" {
3597-
lifecycle {
3598-
action_trigger {
3599-
events = [after_create]
3600-
actions = [action.test_register.foo]
3601-
}
3602-
}
3603-
}
3604-
`,
3619+
action "test_other" "foo" {
3620+
}
3621+
resource "test_instance" "foo" {
3622+
lifecycle {
3623+
action_trigger {
3624+
events = [after_create]
3625+
actions = [action.test_other.foo]
3626+
}
3627+
}
3628+
}
3629+
`,
36053630
"",
3631+
true,
36063632
},
36073633
"missing required config": {
36083634
`
3609-
terraform {
3610-
required_providers {
3611-
test = {
3612-
source = "hashicorp/test"
3613-
version = "1.0.0"
3635+
terraform {
3636+
required_providers {
3637+
test = {
3638+
source = "hashicorp/test"
3639+
version = "1.0.0"
3640+
}
3641+
}
36143642
}
3615-
}
3616-
}
3617-
action "test_register" "foo" {
3618-
config {}
3619-
}
3620-
resource "test_instance" "foo" {
3621-
lifecycle {
3622-
action_trigger {
3623-
events = [after_create]
3624-
actions = [action.test_register.foo]
3625-
}
3626-
}
3627-
}
3628-
`,
3629-
"host is null",
3643+
action "test_register" "foo" {
3644+
config {}
3645+
}
3646+
resource "test_instance" "foo" {
3647+
lifecycle {
3648+
action_trigger {
3649+
events = [after_create]
3650+
actions = [action.test_register.foo]
3651+
}
3652+
}
3653+
}
3654+
`,
3655+
"Missing required argument: The argument \"host\" is required, but no definition was found.",
3656+
false,
3657+
},
3658+
"invalid required config (provider validation)": {
3659+
`
3660+
terraform {
3661+
required_providers {
3662+
test = {
3663+
source = "hashicorp/test"
3664+
version = "1.0.0"
3665+
}
3666+
}
3667+
}
3668+
action "test_register" "foo" {
3669+
config {
3670+
host = "invalid"
3671+
}
3672+
}
3673+
resource "test_instance" "foo" {
3674+
lifecycle {
3675+
action_trigger {
3676+
events = [after_create]
3677+
actions = [action.test_register.foo]
3678+
}
3679+
}
3680+
}
3681+
`,
3682+
"Invalid value for required argument \"host\" because I said so",
3683+
true,
36303684
},
3631-
"invalid nil config config": {
3685+
"invalid nil config": {
36323686
`
36333687
terraform {
36343688
required_providers {
@@ -3649,7 +3703,8 @@ resource "test_instance" "foo" {
36493703
}
36503704
}
36513705
`,
3652-
"config is null",
3706+
"Missing required argument: The argument \"host\" is required, but was not set.",
3707+
false,
36533708
},
36543709
}
36553710

@@ -3669,6 +3724,14 @@ resource "test_instance" "foo" {
36693724
},
36703725
Actions: map[string]*providers.ActionSchema{
36713726
"test_register": {
3727+
ConfigSchema: &configschema.Block{
3728+
Attributes: map[string]*configschema.Attribute{
3729+
"host": {Type: cty.String, Required: true},
3730+
"output": {Type: cty.String, Computed: true, Optional: true},
3731+
},
3732+
},
3733+
},
3734+
"test_other": {
36723735
ConfigSchema: &configschema.Block{
36733736
Attributes: map[string]*configschema.Attribute{
36743737
"host": {Type: cty.String, Optional: true},
@@ -3678,12 +3741,8 @@ resource "test_instance" "foo" {
36783741
},
36793742
})
36803743
p.ValidateActionConfigFn = func(r providers.ValidateActionConfigRequest) (resp providers.ValidateActionConfigResponse) {
3681-
if r.Config.IsNull() {
3682-
resp.Diagnostics = resp.Diagnostics.Append(errors.New("config is null"))
3683-
return
3684-
}
3685-
if r.Config.GetAttr("host").IsNull() {
3686-
resp.Diagnostics = resp.Diagnostics.Append(errors.New("host is null"))
3744+
if r.Config.GetAttr("host") == cty.StringVal("invalid") {
3745+
resp.Diagnostics = resp.Diagnostics.Append(errors.New("Invalid value for required argument \"host\" because I said so"))
36873746
}
36883747
return
36893748
}
@@ -3695,10 +3754,6 @@ resource "test_instance" "foo" {
36953754
})
36963755

36973756
diags := ctx.Validate(m, nil)
3698-
if !p.ValidateActionConfigCalled {
3699-
t.Fatal("ValidateAction RPC was not called")
3700-
}
3701-
37023757
if test.wantErr != "" {
37033758
if !diags.HasErrors() {
37043759
t.Errorf("unexpected success\nwant errors: %s", test.wantErr)
@@ -3710,6 +3765,13 @@ resource "test_instance" "foo" {
37103765
t.Errorf("unexpected error\ngot: %s", diags.Err().Error())
37113766
}
37123767
}
3768+
if p.ValidateActionConfigCalled != test.expectValidateCalled {
3769+
if test.expectValidateCalled {
3770+
t.Fatal("provider Validate RPC was expected, but not called")
3771+
} else {
3772+
t.Fatal("unexpected Validate RCP call")
3773+
}
3774+
}
37133775
})
37143776
}
37153777
}

internal/terraform/node_action_abstract.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ func (n NodeAbstractAction) Name() string {
4444
// abstract action to a concrete one of some type.
4545
type ConcreteActionNodeFunc func(*NodeAbstractAction) dag.Vertex
4646

47-
// I'm not sure why my ConcreteActionNodeFUnction kept being nil in tests, but
48-
// this is much more robust. If it isn't a validate walk, we need
49-
// nodeExpandActionDeclaration.
47+
// DefaultConcreteActionNodeFunc is the default ConcreteActionNodeFunc used by
48+
// everything except validate.
5049
func DefaultConcreteActionNodeFunc(a *NodeAbstractAction) dag.Vertex {
5150
return &nodeExpandActionDeclaration{
5251
NodeAbstractAction: a,

internal/terraform/node_action_validate.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,24 @@ func (n *NodeValidatableAction) Execute(ctx EvalContext, _ walkOperation) tfdiag
8282
return diags
8383
}
8484

85-
var configVal cty.Value
86-
var valDiags tfdiags.Diagnostics
87-
if n.Config.Config != nil {
88-
configVal, _, valDiags = ctx.EvaluateBlock(n.Config.Config, schema.ConfigSchema, nil, keyData)
89-
diags = diags.Append(valDiags)
90-
if valDiags.HasErrors() {
85+
config := n.Config.Config
86+
if n.Config.Config == nil {
87+
config = hcl.EmptyBody()
88+
}
89+
90+
configVal, _, valDiags := ctx.EvaluateBlock(config, schema.ConfigSchema, nil, keyData)
91+
if valDiags.HasErrors() {
92+
// If there was no config block at all, we'll add a Context range to the returned diagnostic
93+
if n.Config.Config == nil {
94+
for _, diag := range valDiags.ToHCL() {
95+
diag.Context = &n.Config.DeclRange
96+
diags = diags.Append(diag)
97+
}
98+
return diags
99+
} else {
100+
diags = diags.Append(valDiags)
91101
return diags
92102
}
93-
} else {
94-
configVal = cty.NullVal(n.Schema.ConfigSchema.ImpliedType())
95103
}
96104

97105
// Use unmarked value for validate request

0 commit comments

Comments
 (0)