|
|
|
|
@ -8,6 +8,7 @@ import (
|
|
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
|
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
|
|
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
|
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
|
|
|
testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
|
|
|
|
|
@ -23,8 +24,10 @@ func TestContext2Apply_actions(t *testing.T) {
|
|
|
|
|
prevRunState *states.State
|
|
|
|
|
events []providers.InvokeActionEvent
|
|
|
|
|
callingInvokeReturnsDiagnostics tfdiags.Diagnostics
|
|
|
|
|
planOpts *PlanOpts
|
|
|
|
|
|
|
|
|
|
expectInvokeActionCalled bool
|
|
|
|
|
expectInvokeActionCalls []providers.InvokeActionRequest
|
|
|
|
|
|
|
|
|
|
expectDiagnostics tfdiags.Diagnostics
|
|
|
|
|
}{
|
|
|
|
|
@ -207,6 +210,110 @@ resource "test_object" "a" {
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"action with configuration": {
|
|
|
|
|
module: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test_object" "a" {
|
|
|
|
|
name = "foo"
|
|
|
|
|
}
|
|
|
|
|
action "test_unlinked" "hello" {
|
|
|
|
|
config {
|
|
|
|
|
attr = resource.test_object.a.name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
resource "test_object" "b" {
|
|
|
|
|
lifecycle {
|
|
|
|
|
action_trigger {
|
|
|
|
|
events = [before_create]
|
|
|
|
|
actions = [action.test_unlinked.hello]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
expectInvokeActionCalled: true,
|
|
|
|
|
expectInvokeActionCalls: []providers.InvokeActionRequest{{
|
|
|
|
|
ActionType: "test_unlinked",
|
|
|
|
|
PlannedActionData: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"attr": cty.StringVal("foo"),
|
|
|
|
|
}),
|
|
|
|
|
}},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Providers can handle unknown values in the configuration
|
|
|
|
|
"action with unknown configuration": {
|
|
|
|
|
module: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
variable "unknown_value" {
|
|
|
|
|
type = string
|
|
|
|
|
}
|
|
|
|
|
action "test_unlinked" "hello" {
|
|
|
|
|
config {
|
|
|
|
|
attr = var.unknown_value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
resource "test_object" "b" {
|
|
|
|
|
lifecycle {
|
|
|
|
|
action_trigger {
|
|
|
|
|
events = [before_create]
|
|
|
|
|
actions = [action.test_unlinked.hello]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
planOpts: SimplePlanOpts(plans.NormalMode, InputValues{
|
|
|
|
|
"unknown_value": &InputValue{
|
|
|
|
|
Value: cty.UnknownVal(cty.String),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
expectInvokeActionCalled: true,
|
|
|
|
|
expectInvokeActionCalls: []providers.InvokeActionRequest{{
|
|
|
|
|
ActionType: "test_unlinked",
|
|
|
|
|
PlannedActionData: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"attr": cty.UnknownVal(cty.String),
|
|
|
|
|
}),
|
|
|
|
|
}},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"action with secrets in configuration": {
|
|
|
|
|
module: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
variable "secret_value" {
|
|
|
|
|
type = string
|
|
|
|
|
sensitive = true
|
|
|
|
|
}
|
|
|
|
|
action "test_unlinked" "hello" {
|
|
|
|
|
config {
|
|
|
|
|
attr = var.secret_value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
resource "test_object" "b" {
|
|
|
|
|
lifecycle {
|
|
|
|
|
action_trigger {
|
|
|
|
|
events = [before_create]
|
|
|
|
|
actions = [action.test_unlinked.hello]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
planOpts: SimplePlanOpts(plans.NormalMode, InputValues{
|
|
|
|
|
"secret_value": &InputValue{
|
|
|
|
|
Value: cty.StringVal("psst, I'm secret").Mark(marks.Sensitive), // Not sure if we need the mark here, but it doesn't hurt
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
expectInvokeActionCalled: true,
|
|
|
|
|
expectInvokeActionCalls: []providers.InvokeActionRequest{{
|
|
|
|
|
ActionType: "test_unlinked",
|
|
|
|
|
PlannedActionData: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"attr": cty.StringVal("psst, I'm secret"),
|
|
|
|
|
}),
|
|
|
|
|
}},
|
|
|
|
|
},
|
|
|
|
|
} {
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
|
m := testModuleInline(t, tc.module)
|
|
|
|
|
@ -286,7 +393,12 @@ resource "test_object" "a" {
|
|
|
|
|
diags := ctx.Validate(m, &ValidateOpts{})
|
|
|
|
|
tfdiags.AssertNoDiagnostics(t, diags)
|
|
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, tc.prevRunState, SimplePlanOpts(plans.NormalMode, InputValues{}))
|
|
|
|
|
planOpts := SimplePlanOpts(plans.NormalMode, InputValues{})
|
|
|
|
|
if tc.planOpts != nil {
|
|
|
|
|
planOpts = tc.planOpts
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, tc.prevRunState, planOpts)
|
|
|
|
|
tfdiags.AssertNoDiagnostics(t, diags)
|
|
|
|
|
|
|
|
|
|
_, diags = ctx.Apply(plan, m, nil)
|
|
|
|
|
@ -299,6 +411,19 @@ resource "test_object" "a" {
|
|
|
|
|
if tc.expectInvokeActionCalled && len(invokeActionCalls) == 0 {
|
|
|
|
|
t.Fatalf("expected invoke action to be called, but it was not")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(tc.expectInvokeActionCalls) > 0 && len(invokeActionCalls) != len(tc.expectInvokeActionCalls) {
|
|
|
|
|
t.Fatalf("expected %d invoke action calls, got %d", len(tc.expectInvokeActionCalls), len(invokeActionCalls))
|
|
|
|
|
}
|
|
|
|
|
for i, expectedCall := range tc.expectInvokeActionCalls {
|
|
|
|
|
actualCall := invokeActionCalls[i]
|
|
|
|
|
if actualCall.ActionType != expectedCall.ActionType {
|
|
|
|
|
t.Fatalf("expected invoke action call %d ActionType to be %s, got %s", i, expectedCall.ActionType, actualCall.ActionType)
|
|
|
|
|
}
|
|
|
|
|
if !actualCall.PlannedActionData.RawEquals(expectedCall.PlannedActionData) {
|
|
|
|
|
t.Fatalf("expected invoke action call %d PlannedActionData to be %s, got %s", i, expectedCall.PlannedActionData, actualCall.PlannedActionData)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|