2023-05-02 11:33:06 -04:00
// Copyright IBM Corp. 2014, 2026
2023-08-10 18:43:27 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-05-02 11:33:06 -04:00
2021-02-23 10:16:09 -05:00
package views
import (
2024-10-30 08:44:02 -04:00
"errors"
2021-02-23 10:16:09 -05:00
"fmt"
2022-04-20 15:01:17 -04:00
"sync"
2021-02-23 10:16:09 -05:00
"testing"
2025-08-18 10:39:49 -04:00
"testing/synctest"
2021-02-23 10:16:09 -05:00
"time"
2024-07-17 07:10:11 -04:00
"github.com/zclconf/go-cty/cty"
2021-05-17 15:00:50 -04:00
"github.com/hashicorp/terraform/internal/addrs"
2025-08-13 10:06:34 -04:00
"github.com/hashicorp/terraform/internal/configs"
2021-05-17 15:33:17 -04:00
"github.com/hashicorp/terraform/internal/plans"
2021-02-23 10:16:09 -05:00
"github.com/hashicorp/terraform/internal/terminal"
2021-05-17 15:46:19 -04:00
"github.com/hashicorp/terraform/internal/terraform"
2021-02-23 10:16:09 -05:00
)
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
func testJSONHookResourceID ( addr addrs . AbsResourceInstance ) terraform . HookResourceIdentity {
return terraform . HookResourceIdentity {
Addr : addr ,
ProviderAddr : addrs . Provider {
Type : "test" ,
Namespace : "hashicorp" ,
Hostname : "example.com" ,
} ,
}
}
2025-08-29 09:43:34 -04:00
func testJSONLifecycleHook ( actionAddr addrs . AbsActionInstance , triggeringResourceAddr addrs . AbsResourceInstance , actionTriggerIndex int , actionsListIndex int ) terraform . HookActionIdentity {
2025-07-18 12:36:08 -04:00
return terraform . HookActionIdentity {
2025-08-13 10:06:34 -04:00
Addr : actionAddr ,
2026-02-23 15:12:12 -05:00
ActionTrigger : & plans . ResourceActionTrigger {
2025-08-13 10:06:34 -04:00
TriggeringResourceAddr : triggeringResourceAddr ,
ActionTriggerBlockIndex : actionTriggerIndex ,
ActionsListIndex : actionsListIndex ,
ActionTriggerEvent : configs . AfterCreate ,
} ,
2025-07-02 11:19:40 -04:00
}
}
2025-08-29 09:43:34 -04:00
func testJSONInvokeHook ( actionAddr addrs . AbsActionInstance ) terraform . HookActionIdentity {
return terraform . HookActionIdentity {
Addr : actionAddr ,
ActionTrigger : new ( plans . InvokeActionTrigger ) ,
}
}
2021-02-23 10:16:09 -05:00
// Test a sequence of hooks associated with creating a resource
func TestJSONHook_create ( t * testing . T ) {
streams , done := terminal . StreamsForTesting ( t )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
2022-04-20 15:01:17 -04:00
var nowMu sync . Mutex
2021-02-23 10:16:09 -05:00
now := time . Now ( )
2022-04-20 15:01:17 -04:00
hook . timeNow = func ( ) time . Time {
nowMu . Lock ( )
defer nowMu . Unlock ( )
return now
}
2021-02-23 10:16:09 -05:00
after := make ( chan time . Time , 1 )
hook . timeAfter = func ( time . Duration ) <- chan time . Time { return after }
addr := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
priorState := cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"bar" : cty . List ( cty . String ) ,
} ) )
plannedNewState := cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "test" ) ,
"bar" : cty . ListVal ( [ ] cty . Value {
cty . StringVal ( "baz" ) ,
} ) ,
} )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err := hook . PreApply ( testJSONHookResourceID ( addr ) , addrs . NotDeposed , plans . Create , priorState , plannedNewState )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err = hook . PreProvisionInstanceStep ( testJSONHookResourceID ( addr ) , "local-exec" )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
hook . ProvisionOutput ( testJSONHookResourceID ( addr ) , "local-exec" , ` Executing: ["/bin/sh" "-c" "touch /etc/motd"] ` )
2021-02-23 10:16:09 -05:00
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err = hook . PostProvisionInstanceStep ( testJSONHookResourceID ( addr ) , "local-exec" , nil )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
// Travel 10s into the future, notify the progress goroutine, and sleep
// briefly to allow it to execute
2022-04-20 15:01:17 -04:00
nowMu . Lock ( )
2021-02-23 10:16:09 -05:00
now = now . Add ( 10 * time . Second )
after <- now
2022-04-20 15:01:17 -04:00
nowMu . Unlock ( )
2024-07-17 07:10:11 -04:00
time . Sleep ( 10 * time . Millisecond )
2021-02-23 10:16:09 -05:00
// Travel 10s into the future, notify the progress goroutine, and sleep
// briefly to allow it to execute
2022-04-20 15:01:17 -04:00
nowMu . Lock ( )
2021-02-23 10:16:09 -05:00
now = now . Add ( 10 * time . Second )
after <- now
2022-04-20 15:01:17 -04:00
nowMu . Unlock ( )
2024-07-17 07:10:11 -04:00
time . Sleep ( 10 * time . Millisecond )
2021-02-23 10:16:09 -05:00
// Travel 2s into the future. We have arrived!
2022-04-20 15:01:17 -04:00
nowMu . Lock ( )
2021-02-23 10:16:09 -05:00
now = now . Add ( 2 * time . Second )
2022-04-20 15:01:17 -04:00
nowMu . Unlock ( )
2021-02-23 10:16:09 -05:00
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err = hook . PostApply ( testJSONHookResourceID ( addr ) , addrs . NotDeposed , plannedNewState , nil )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
// Shut down the progress goroutine if still active
2024-10-30 08:44:02 -04:00
hook . resourceProgressMu . Lock ( )
for key , progress := range hook . resourceProgress {
2021-02-23 10:16:09 -05:00
close ( progress . done )
<- progress . heartbeatDone
2024-10-30 08:44:02 -04:00
delete ( hook . resourceProgress , key )
2021-02-23 10:16:09 -05:00
}
2024-10-30 08:44:02 -04:00
hook . resourceProgressMu . Unlock ( )
2021-02-23 10:16:09 -05:00
wantResource := map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
}
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
"@message" : "test_instance.boop: Creating..." ,
"@module" : "terraform.ui" ,
"type" : "apply_start" ,
"hook" : map [ string ] interface { } {
"action" : string ( "create" ) ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Provisioning with 'local-exec'..." ,
"@module" : "terraform.ui" ,
"type" : "provision_start" ,
"hook" : map [ string ] interface { } {
"provisioner" : "local-exec" ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : ` test_instance.boop: (local-exec): Executing: ["/bin/sh" "-c" "touch /etc/motd"] ` ,
"@module" : "terraform.ui" ,
"type" : "provision_progress" ,
"hook" : map [ string ] interface { } {
"output" : ` Executing: ["/bin/sh" "-c" "touch /etc/motd"] ` ,
"provisioner" : "local-exec" ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: (local-exec) Provisioning complete" ,
"@module" : "terraform.ui" ,
"type" : "provision_complete" ,
"hook" : map [ string ] interface { } {
"provisioner" : "local-exec" ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Still creating... [10s elapsed]" ,
"@module" : "terraform.ui" ,
"type" : "apply_progress" ,
"hook" : map [ string ] interface { } {
"action" : string ( "create" ) ,
"elapsed_seconds" : float64 ( 10 ) ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Still creating... [20s elapsed]" ,
"@module" : "terraform.ui" ,
"type" : "apply_progress" ,
"hook" : map [ string ] interface { } {
"action" : string ( "create" ) ,
"elapsed_seconds" : float64 ( 20 ) ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Creation complete after 22s [id=test]" ,
"@module" : "terraform.ui" ,
"type" : "apply_complete" ,
"hook" : map [ string ] interface { } {
"action" : string ( "create" ) ,
"elapsed_seconds" : float64 ( 22 ) ,
"id_key" : "id" ,
"id_value" : "test" ,
"resource" : wantResource ,
} ,
} ,
}
testJSONViewOutputEquals ( t , done ( t ) . Stdout ( ) , want )
}
func TestJSONHook_errors ( t * testing . T ) {
streams , done := terminal . StreamsForTesting ( t )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
addr := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
priorState := cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"bar" : cty . List ( cty . String ) ,
} ) )
plannedNewState := cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "test" ) ,
"bar" : cty . ListVal ( [ ] cty . Value {
cty . StringVal ( "baz" ) ,
} ) ,
} )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err := hook . PreApply ( testJSONHookResourceID ( addr ) , addrs . NotDeposed , plans . Delete , priorState , plannedNewState )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
provisionError := fmt . Errorf ( "provisioner didn't want to" )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err = hook . PostProvisionInstanceStep ( testJSONHookResourceID ( addr ) , "local-exec" , provisionError )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
applyError := fmt . Errorf ( "provider was sad" )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err = hook . PostApply ( testJSONHookResourceID ( addr ) , addrs . NotDeposed , plannedNewState , applyError )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
// Shut down the progress goroutine
2024-10-30 08:44:02 -04:00
hook . resourceProgressMu . Lock ( )
for key , progress := range hook . resourceProgress {
2021-02-23 10:16:09 -05:00
close ( progress . done )
<- progress . heartbeatDone
2024-10-30 08:44:02 -04:00
delete ( hook . resourceProgress , key )
2021-02-23 10:16:09 -05:00
}
2024-10-30 08:44:02 -04:00
hook . resourceProgressMu . Unlock ( )
2021-02-23 10:16:09 -05:00
wantResource := map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
}
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
"@message" : "test_instance.boop: Destroying..." ,
"@module" : "terraform.ui" ,
"type" : "apply_start" ,
"hook" : map [ string ] interface { } {
"action" : string ( "delete" ) ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: (local-exec) Provisioning errored" ,
"@module" : "terraform.ui" ,
"type" : "provision_errored" ,
"hook" : map [ string ] interface { } {
"provisioner" : "local-exec" ,
"resource" : wantResource ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Destruction errored after 0s" ,
"@module" : "terraform.ui" ,
"type" : "apply_errored" ,
"hook" : map [ string ] interface { } {
"action" : string ( "delete" ) ,
"elapsed_seconds" : float64 ( 0 ) ,
"resource" : wantResource ,
} ,
} ,
}
testJSONViewOutputEquals ( t , done ( t ) . Stdout ( ) , want )
}
func TestJSONHook_refresh ( t * testing . T ) {
streams , done := terminal . StreamsForTesting ( t )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
addr := addrs . Resource {
Mode : addrs . DataResourceMode ,
Type : "test_data_source" ,
Name : "beep" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
state := cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "honk" ) ,
"bar" : cty . ListVal ( [ ] cty . Value {
cty . StringVal ( "baz" ) ,
} ) ,
} )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err := hook . PreRefresh ( testJSONHookResourceID ( addr ) , addrs . NotDeposed , state )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
Refactor terraform.Hook to use a resource-identifying wrapper struct
The terraform.Hook interface lets other areas of code perform streaming
reactions to various events, generally in the service of some UI somewhere.
Nearly all of the methods on this interface take an `addrs.AbsResourceInstance`
as their first argument, to identify the resource that's being operated on.
However, that addrs struct doesn't necessarily contain everything you might want
in order to uniquely and usefully identify a resource. It has the module
instance and resource instance addresses, but it lacks the provider source
address, which can affect how the consuming UI should display the resource's
events. (For example, Terraform Cloud wants reliable info about who maintains a
given provider, what cloud provider it operates on, and where to find its
documentation.)
Instead of polluting `addrs.AbsResourceInstance` with extra information that
isn't relevant to other call sites, let's change the first argument of each Hook
method to be a wrapper struct defined in the package that owns the Hook
interface, and add the provider address to that wrapper as a sibling of the
resource address. This causes a big noisy commit today, but should streamline
future updates to the UI-facing "identity" of a resource; existing callers can
ignore any new fields they're uninterested in, or exploit new info as needed.
Other than making new information available for future edits to Hook
implementing types, this commit should have no effect on existing behavior.
2024-02-27 20:42:17 -05:00
action , err = hook . PostRefresh ( testJSONHookResourceID ( addr ) , addrs . NotDeposed , state , state )
2021-02-23 10:16:09 -05:00
testHookReturnValues ( t , action , err )
wantResource := map [ string ] interface { } {
"addr" : string ( "data.test_data_source.beep" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "data.test_data_source.beep" ) ,
"resource_key" : nil ,
"resource_name" : string ( "beep" ) ,
"resource_type" : string ( "test_data_source" ) ,
}
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
"@message" : "data.test_data_source.beep: Refreshing state... [id=honk]" ,
"@module" : "terraform.ui" ,
"type" : "refresh_start" ,
"hook" : map [ string ] interface { } {
"resource" : wantResource ,
"id_key" : "id" ,
"id_value" : "honk" ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "data.test_data_source.beep: Refresh complete [id=honk]" ,
"@module" : "terraform.ui" ,
"type" : "refresh_complete" ,
"hook" : map [ string ] interface { } {
"resource" : wantResource ,
"id_key" : "id" ,
"id_value" : "honk" ,
} ,
} ,
}
testJSONViewOutputEquals ( t , done ( t ) . Stdout ( ) , want )
}
2024-10-30 08:44:02 -04:00
func TestJSONHook_EphemeralOp ( t * testing . T ) {
streams , done := terminal . StreamsForTesting ( t )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
addr := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
action , err := hook . PreEphemeralOp ( testJSONHookResourceID ( addr ) , plans . Open )
testHookReturnValues ( t , action , err )
action , err = hook . PostEphemeralOp ( testJSONHookResourceID ( addr ) , plans . Open , nil )
testHookReturnValues ( t , action , err )
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
"@message" : "test_instance.boop: Opening..." ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_start" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Opening complete after 0s" ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_complete" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"elapsed_seconds" : float64 ( 0 ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
} ,
} ,
}
testJSONViewOutputEquals ( t , done ( t ) . Stdout ( ) , want )
}
func TestJSONHook_EphemeralOp_progress ( t * testing . T ) {
2025-08-18 10:39:49 -04:00
syncTest , streams , done := streamableSyncTest ( t )
syncTest ( t , func ( t * testing . T ) {
start := time . Now ( )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
hook . periodicUiTimer = 1 * time . Second
t . Log ( time . Since ( start ) )
addr := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
action , err := hook . PreEphemeralOp ( testJSONHookResourceID ( addr ) , plans . Open )
testHookReturnValues ( t , action , err )
time . Sleep ( 2005 * time . Millisecond )
action , err = hook . PostEphemeralOp ( testJSONHookResourceID ( addr ) , plans . Open , nil )
testHookReturnValues ( t , action , err )
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
"@message" : "test_instance.boop: Opening..." ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_start" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
2024-10-30 08:44:02 -04:00
} ,
} ,
2025-08-18 10:39:49 -04:00
{
"@level" : "info" ,
"@message" : "test_instance.boop: Still opening... [1s elapsed]" ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_progress" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"elapsed_seconds" : float64 ( 1 ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
2024-10-30 08:44:02 -04:00
} ,
} ,
2025-08-18 10:39:49 -04:00
{
"@level" : "info" ,
"@message" : "test_instance.boop: Still opening... [2s elapsed]" ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_progress" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"elapsed_seconds" : float64 ( 2 ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
2024-10-30 08:44:02 -04:00
} ,
} ,
2025-08-18 10:39:49 -04:00
{
"@level" : "info" ,
"@message" : "test_instance.boop: Opening complete after 2s" ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_complete" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"elapsed_seconds" : float64 ( 2 ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
2024-10-30 08:44:02 -04:00
} ,
} ,
2025-08-18 10:39:49 -04:00
}
stdout := done ( t ) . Stdout ( )
testJSONViewOutputEquals ( t , stdout , want )
} )
2024-10-30 08:44:02 -04:00
}
func TestJSONHook_EphemeralOp_error ( t * testing . T ) {
streams , done := terminal . StreamsForTesting ( t )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
addr := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
action , err := hook . PreEphemeralOp ( testJSONHookResourceID ( addr ) , plans . Open )
testHookReturnValues ( t , action , err )
action , err = hook . PostEphemeralOp ( testJSONHookResourceID ( addr ) , plans . Open , errors . New ( "test error" ) )
testHookReturnValues ( t , action , err )
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
"@message" : "test_instance.boop: Opening..." ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_start" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "test_instance.boop: Opening errored after 0s" ,
"@module" : "terraform.ui" ,
"type" : "ephemeral_op_errored" ,
"hook" : map [ string ] interface { } {
"action" : string ( "open" ) ,
"elapsed_seconds" : float64 ( 0 ) ,
"resource" : map [ string ] interface { } {
"addr" : string ( "test_instance.boop" ) ,
"implied_provider" : string ( "test" ) ,
"module" : string ( "" ) ,
"resource" : string ( "test_instance.boop" ) ,
"resource_key" : nil ,
"resource_name" : string ( "boop" ) ,
"resource_type" : string ( "test_instance" ) ,
} ,
} ,
} ,
}
testJSONViewOutputEquals ( t , done ( t ) . Stdout ( ) , want )
}
2025-07-02 11:19:40 -04:00
func TestJSONHook_actions ( t * testing . T ) {
streams , done := terminal . StreamsForTesting ( t )
hook := newJSONHook ( NewJSONView ( NewView ( streams ) ) )
actionA := addrs . AbsActionInstance {
Module : addrs . RootModuleInstance ,
Action : addrs . Action {
Type : "aws_lambda_invocation" ,
Name : "notify_slack" ,
} . Instance ( addrs . IntKey ( 42 ) ) ,
}
resourceA := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance )
subModule := addrs . RootModuleInstance . Child ( "childMod" , addrs . StringKey ( "infra" ) )
actionB := addrs . AbsActionInstance {
Module : subModule ,
Action : addrs . Action {
Type : "ansible_playbook" ,
Name : "webserver" ,
} . Instance ( addrs . NoKey ) ,
}
resourceB := addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "test_instance" ,
Name : "boop" ,
} . Instance ( addrs . NoKey ) . Absolute ( subModule )
2025-08-29 09:43:34 -04:00
actionC := addrs . AbsActionInstance {
Module : addrs . RootModuleInstance ,
Action : addrs . Action {
Type : "aws_lambda_invocation" ,
Name : "invoke_me" ,
} . Instance ( addrs . IntKey ( 42 ) ) ,
}
actionAHookId := testJSONLifecycleHook ( actionA , resourceA , 0 , 1 )
actionBHookId := testJSONLifecycleHook ( actionB , resourceB , 2 , 3 )
actionCHookId := testJSONInvokeHook ( actionC )
2025-07-18 12:36:08 -04:00
action , err := hook . StartAction ( actionAHookId )
2025-07-02 11:19:40 -04:00
testHookReturnValues ( t , action , err )
2025-07-18 12:36:08 -04:00
action , err = hook . ProgressAction ( actionAHookId , "Hello world from the lambda function" )
2025-07-02 11:19:40 -04:00
testHookReturnValues ( t , action , err )
2025-07-18 12:36:08 -04:00
action , err = hook . StartAction ( actionBHookId )
2025-07-02 11:19:40 -04:00
testHookReturnValues ( t , action , err )
2025-07-18 12:36:08 -04:00
action , err = hook . ProgressAction ( actionBHookId , "TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]" )
2025-07-02 11:19:40 -04:00
testHookReturnValues ( t , action , err )
2025-07-18 12:36:08 -04:00
action , err = hook . CompleteAction ( actionBHookId , nil )
2025-07-02 11:19:40 -04:00
testHookReturnValues ( t , action , err )
2025-07-18 12:36:08 -04:00
action , err = hook . CompleteAction ( actionAHookId , errors . New ( "lambda terminated with exit code 1" ) )
2025-07-02 11:19:40 -04:00
testHookReturnValues ( t , action , err )
2025-08-29 09:43:34 -04:00
action , err = hook . StartAction ( actionCHookId )
testHookReturnValues ( t , action , err )
action , err = hook . ProgressAction ( actionCHookId , "Hello world from the invoked action" )
testHookReturnValues ( t , action , err )
action , err = hook . CompleteAction ( actionCHookId , nil )
testHookReturnValues ( t , action , err )
2025-07-02 11:19:40 -04:00
want := [ ] map [ string ] interface { } {
{
"@level" : "info" ,
2025-08-29 09:43:34 -04:00
"@message" : "test_instance.boop.trigger[0]: Action started: action.aws_lambda_invocation.notify_slack[42]" ,
2025-07-02 11:19:40 -04:00
"@module" : "terraform.ui" ,
"type" : "action_start" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "action.aws_lambda_invocation.notify_slack[42]" ,
"module" : "" ,
"implied_provider" : "aws" ,
2025-08-29 09:43:34 -04:00
"action" : "action.aws_lambda_invocation.notify_slack[42]" ,
"action_key" : float64 ( 42 ) ,
"action_name" : "notify_slack" ,
"action_type" : "aws_lambda_invocation" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"lifecycle" : map [ string ] interface { } {
"actions_index" : float64 ( 1 ) ,
"resource" : map [ string ] interface { } {
"addr" : "test_instance.boop" ,
"implied_provider" : "test" ,
"module" : "" ,
"resource" : "test_instance.boop" ,
"resource_key" : nil ,
"resource_name" : "boop" ,
"resource_type" : "test_instance" ,
} ,
"trigger_event" : "AfterCreate" ,
"trigger_index" : float64 ( 0 ) ,
2025-07-02 11:19:40 -04:00
} ,
} ,
} ,
{
"@level" : "info" ,
2025-07-18 12:36:08 -04:00
"@message" : "test_instance.boop (0): action.aws_lambda_invocation.notify_slack[42] - Hello world from the lambda function" ,
2025-07-02 11:19:40 -04:00
"@module" : "terraform.ui" ,
"type" : "action_progress" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "action.aws_lambda_invocation.notify_slack[42]" ,
"module" : "" ,
"implied_provider" : "aws" ,
2025-08-29 09:43:34 -04:00
"action" : "action.aws_lambda_invocation.notify_slack[42]" ,
"action_key" : float64 ( 42 ) ,
"action_name" : "notify_slack" ,
"action_type" : "aws_lambda_invocation" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"message" : "Hello world from the lambda function" ,
"lifecycle" : map [ string ] interface { } {
"actions_index" : float64 ( 1 ) ,
"resource" : map [ string ] interface { } {
"addr" : "test_instance.boop" ,
"implied_provider" : "test" ,
"module" : "" ,
"resource" : "test_instance.boop" ,
"resource_key" : nil ,
"resource_name" : "boop" ,
"resource_type" : "test_instance" ,
} ,
"trigger_event" : "AfterCreate" ,
"trigger_index" : float64 ( 0 ) ,
2025-07-02 11:19:40 -04:00
} ,
} ,
} ,
{
"@level" : "info" ,
2025-08-29 09:43:34 -04:00
"@message" : "module.childMod[\"infra\"].test_instance.boop.trigger[2]: Action started: module.childMod[\"infra\"].action.ansible_playbook.webserver" ,
2025-07-02 11:19:40 -04:00
"@module" : "terraform.ui" ,
"type" : "action_start" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "module.childMod[\"infra\"].action.ansible_playbook.webserver" ,
"module" : "module.childMod[\"infra\"]" ,
"implied_provider" : "ansible" ,
2025-08-29 09:43:34 -04:00
"action" : "action.ansible_playbook.webserver" ,
"action_key" : nil ,
"action_name" : "webserver" ,
"action_type" : "ansible_playbook" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"lifecycle" : map [ string ] interface { } {
"actions_index" : float64 ( 3 ) ,
"resource" : map [ string ] interface { } {
"addr" : "module.childMod[\"infra\"].test_instance.boop" ,
"implied_provider" : "test" ,
"module" : "module.childMod[\"infra\"]" ,
"resource" : "test_instance.boop" ,
"resource_key" : nil ,
"resource_name" : "boop" ,
"resource_type" : "test_instance" ,
} ,
"trigger_event" : "AfterCreate" ,
"trigger_index" : float64 ( 2 ) ,
2025-07-02 11:19:40 -04:00
} ,
} ,
} ,
{
"@level" : "info" ,
2025-07-18 12:36:08 -04:00
"@message" : "module.childMod[\"infra\"].test_instance.boop (2): module.childMod[\"infra\"].action.ansible_playbook.webserver - TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]" ,
2025-07-02 11:19:40 -04:00
"@module" : "terraform.ui" ,
"type" : "action_progress" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "module.childMod[\"infra\"].action.ansible_playbook.webserver" ,
"module" : "module.childMod[\"infra\"]" ,
"implied_provider" : "ansible" ,
2025-08-29 09:43:34 -04:00
"action" : "action.ansible_playbook.webserver" ,
"action_key" : nil ,
"action_name" : "webserver" ,
"action_type" : "ansible_playbook" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"message" : "TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]" ,
"lifecycle" : map [ string ] interface { } {
"actions_index" : float64 ( 3 ) ,
"resource" : map [ string ] interface { } {
"addr" : "module.childMod[\"infra\"].test_instance.boop" ,
"implied_provider" : "test" ,
"module" : "module.childMod[\"infra\"]" ,
"resource" : "test_instance.boop" ,
"resource_key" : nil ,
"resource_name" : "boop" ,
"resource_type" : "test_instance" ,
} ,
"trigger_event" : "AfterCreate" ,
"trigger_index" : float64 ( 2 ) ,
2025-07-02 11:19:40 -04:00
} ,
} ,
} ,
{
"@level" : "info" ,
2025-08-29 09:43:34 -04:00
"@message" : "module.childMod[\"infra\"].test_instance.boop (2): Action complete: module.childMod[\"infra\"].action.ansible_playbook.webserver" ,
2025-07-02 11:19:40 -04:00
"@module" : "terraform.ui" ,
"type" : "action_complete" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "module.childMod[\"infra\"].action.ansible_playbook.webserver" ,
"module" : "module.childMod[\"infra\"]" ,
"implied_provider" : "ansible" ,
2025-08-29 09:43:34 -04:00
"action" : "action.ansible_playbook.webserver" ,
"action_key" : nil ,
"action_name" : "webserver" ,
"action_type" : "ansible_playbook" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"lifecycle" : map [ string ] interface { } {
"actions_index" : float64 ( 3 ) ,
"resource" : map [ string ] interface { } {
"addr" : "module.childMod[\"infra\"].test_instance.boop" ,
"implied_provider" : "test" ,
"module" : "module.childMod[\"infra\"]" ,
"resource" : "test_instance.boop" ,
"resource_key" : nil ,
"resource_name" : "boop" ,
"resource_type" : "test_instance" ,
} ,
"trigger_event" : "AfterCreate" ,
"trigger_index" : float64 ( 2 ) ,
2025-07-02 11:19:40 -04:00
} ,
} ,
} ,
{
"@level" : "info" ,
2025-08-29 09:43:34 -04:00
"@message" : "test_instance.boop (0): Action errored: action.aws_lambda_invocation.notify_slack[42] - lambda terminated with exit code 1" ,
2025-07-02 11:19:40 -04:00
"@module" : "terraform.ui" ,
"type" : "action_errored" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "action.aws_lambda_invocation.notify_slack[42]" ,
"module" : "" ,
"implied_provider" : "aws" ,
2025-08-29 09:43:34 -04:00
"action" : "action.aws_lambda_invocation.notify_slack[42]" ,
"action_key" : float64 ( 42 ) ,
"action_name" : "notify_slack" ,
"action_type" : "aws_lambda_invocation" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"error" : "lambda terminated with exit code 1" ,
"lifecycle" : map [ string ] interface { } {
"actions_index" : float64 ( 1 ) ,
"resource" : map [ string ] interface { } {
"addr" : "test_instance.boop" ,
"implied_provider" : "test" ,
"module" : "" ,
"resource" : "test_instance.boop" ,
"resource_key" : nil ,
"resource_name" : "boop" ,
"resource_type" : "test_instance" ,
} ,
"trigger_event" : "AfterCreate" ,
"trigger_index" : float64 ( 0 ) ,
} ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "Action started: action.aws_lambda_invocation.invoke_me[42]" ,
"@module" : "terraform.ui" ,
"type" : "action_start" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "action.aws_lambda_invocation.invoke_me[42]" ,
2025-07-02 11:19:40 -04:00
"module" : "" ,
2025-08-29 09:43:34 -04:00
"implied_provider" : "aws" ,
"action" : "action.aws_lambda_invocation.invoke_me[42]" ,
"action_key" : float64 ( 42 ) ,
"action_name" : "invoke_me" ,
"action_type" : "aws_lambda_invocation" ,
} ,
"invoke" : map [ string ] interface { } { } ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "action.aws_lambda_invocation.invoke_me[42] - Hello world from the invoked action" ,
"@module" : "terraform.ui" ,
"type" : "action_progress" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "action.aws_lambda_invocation.invoke_me[42]" ,
"module" : "" ,
"implied_provider" : "aws" ,
"action" : "action.aws_lambda_invocation.invoke_me[42]" ,
"action_key" : float64 ( 42 ) ,
"action_name" : "invoke_me" ,
"action_type" : "aws_lambda_invocation" ,
} ,
"message" : "Hello world from the invoked action" ,
"invoke" : map [ string ] interface { } { } ,
} ,
} ,
{
"@level" : "info" ,
"@message" : "Action complete: action.aws_lambda_invocation.invoke_me[42]" ,
"@module" : "terraform.ui" ,
"type" : "action_complete" ,
"hook" : map [ string ] interface { } {
"action" : map [ string ] interface { } {
"addr" : "action.aws_lambda_invocation.invoke_me[42]" ,
"module" : "" ,
"implied_provider" : "aws" ,
"action" : "action.aws_lambda_invocation.invoke_me[42]" ,
"action_key" : float64 ( 42 ) ,
"action_name" : "invoke_me" ,
"action_type" : "aws_lambda_invocation" ,
2025-07-02 11:19:40 -04:00
} ,
2025-08-29 09:43:34 -04:00
"invoke" : map [ string ] interface { } { } ,
2025-07-02 11:19:40 -04:00
} ,
} ,
}
testJSONViewOutputEquals ( t , done ( t ) . Stdout ( ) , want )
}
2021-02-23 10:16:09 -05:00
func testHookReturnValues ( t * testing . T , action terraform . HookAction , err error ) {
t . Helper ( )
if err != nil {
t . Fatal ( err )
}
if action != terraform . HookActionContinue {
t . Fatalf ( "Expected hook to continue, given: %#v" , action )
}
}
2025-08-18 10:39:49 -04:00
// streamableSyncTest is a helper to ensure that the long-running streaming goroutines are started outside of the synctest bubble.
// Otherwise, the sync bubble will be unable to advance time, and the main goroutine will become infinitely paused on any time.Sleep operation.
func streamableSyncTest ( t * testing . T ) ( func ( t * testing . T , f func ( * testing . T ) ) , * terminal . Streams , func ( * testing . T ) * terminal . TestOutput ) {
streams , done := terminal . StreamsForTesting ( t )
return synctest . Test , streams , done
}