Add integration test for stacks action invocation via lifecycle trigger

pull/38245/head
Mutahhir Hayat 3 months ago committed by Daniel Schmidt
parent 946918220c
commit cb3dfa615f

@ -301,6 +301,9 @@ func (l *Loader) AddRaw(rawMsg *anypb.Any) error {
DeferredReason: deferredReason,
})
case *tfstackdata1.PlanActionInvocationPlanned:
// TODO: Implemented in a future apply-related PR.
default:
// Should not get here, because a stack plan can only be loaded by
// the same version of Terraform that created it, and the above

@ -1053,3 +1053,139 @@ func mustPlanDynamicValue(t *testing.T, v cty.Value) *tfstackdata1.DynamicValue
}
return tfstackdata1.Terraform1ToStackDataDynamicValue(ret)
}
func TestPlanning_ActionInvocationLifecycle(t *testing.T) {
// This integration test verifies that action invocations with lifecycle
// triggers are correctly planned and included in the PlannedChange objects.
cfg := testStackConfig(t, "planning", "action_lifecycle")
componentInstAddr := stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "web",
},
},
}
actionInstAddr := addrs.AbsActionInstance{
Module: addrs.RootModuleInstance,
Action: addrs.ActionInstance{
Action: addrs.Action{
Type: "test_action",
Name: "notify",
},
Key: addrs.NoKey,
},
}
providerAddr := addrs.NewBuiltInProvider("test")
providerInstAddr := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: providerAddr,
}
resourceTypeSchema := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
Required: true,
},
},
}
actionTypeSchema := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"message": {
Type: cty.String,
Required: true,
},
},
}
main := NewForPlanning(cfg, stackstate.NewState(), PlanOpts{
PlanningMode: plans.NormalMode,
PlanTimestamp: time.Now().UTC(),
ProviderFactories: ProviderFactories{
addrs.NewBuiltInProvider("test"): func() (providers.Interface, error) {
return &providerTesting.MockProvider{
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
Provider: providers.Schema{
Body: &configschema.Block{},
},
ResourceTypes: map[string]providers.Schema{
"test_resource": {
Body: resourceTypeSchema,
},
},
Actions: map[string]providers.ActionSchema{
"test_action": {
ConfigSchema: actionTypeSchema,
},
},
},
ConfigureProviderFn: func(cpr providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
return providers.ConfigureProviderResponse{}
},
PlanResourceChangeFn: func(prcr providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
return providers.PlanResourceChangeResponse{
PlannedState: prcr.ProposedNewState,
}
},
}, nil
},
},
})
outp, outpTest := testPlanOutput(t)
main.PlanAll(context.Background(), outp)
plan, diags := outpTest.Close(t)
assertNoDiagnostics(t, diags)
cmpPlan := plan.GetComponent(componentInstAddr)
if cmpPlan == nil {
t.Fatalf("no plan for %s", componentInstAddr)
}
// Verify that we have planned changes for action invocations
plannedChanges := outpTest.PlannedChanges()
var foundActionChange *stackplan.PlannedChangeActionInvocationInstancePlanned
for _, pc := range plannedChanges {
if actionChange, ok := pc.(*stackplan.PlannedChangeActionInvocationInstancePlanned); ok {
foundActionChange = actionChange
break
}
}
if foundActionChange == nil {
t.Fatalf("no action invocation planned change found; got %d changes", len(plannedChanges))
}
// Verify the action invocation details
if got, want := foundActionChange.ActionInvocationAddr.Component.String(), componentInstAddr.String(); got != want {
t.Errorf("wrong component instance\ngot: %s\nwant: %s", got, want)
}
if got, want := foundActionChange.ActionInvocationAddr.Item.String(), actionInstAddr.String(); got != want {
t.Errorf("wrong action instance\ngot: %s\nwant: %s", got, want)
}
if got, want := foundActionChange.ProviderConfigAddr.String(), providerInstAddr.String(); got != want {
t.Errorf("wrong provider config addr\ngot: %s\nwant: %s", got, want)
}
// Verify the invocation has the correct trigger type
if foundActionChange.Invocation == nil {
t.Fatal("invocation is nil")
}
if _, ok := foundActionChange.Invocation.ActionTrigger.(*plans.LifecycleActionTrigger); !ok {
t.Errorf("wrong action trigger type\ngot: %T\nwant: *plans.LifecycleActionTrigger", foundActionChange.Invocation.ActionTrigger)
}
// Verify we can convert to proto successfully
protoChange, err := foundActionChange.PlannedChangeProto()
if err != nil {
t.Fatalf("failed to convert to proto: %s", err)
}
if protoChange == nil {
t.Fatal("proto change is nil")
}
if len(protoChange.Descriptions) == 0 {
t.Error("expected at least one description in proto change")
}
}

@ -0,0 +1,24 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
required_providers {
test = {
source = "terraform.io/builtin/test"
}
}
provider "test" "main" {
}
component "web" {
source = "./module_web"
providers = {
test = provider.test.main
}
}
output "result" {
type = string
value = component.web.result
}

@ -0,0 +1,31 @@
terraform {
required_providers {
test = {
source = "terraform.io/builtin/test"
configuration_aliases = [ test ]
}
}
}
action "test_action" "notify" {
config {
message = "resource created"
}
}
resource "test_resource" "main" {
value = "example"
lifecycle {
action_trigger {
events = [after_create]
actions = [action.test_action.notify]
}
}
}
output "result" {
value = test_resource.main.value
}
Loading…
Cancel
Save