mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
925 lines
30 KiB
Go
925 lines
30 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package views
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"testing/synctest"
|
|
"time"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
|
"github.com/hashicorp/terraform/internal/terraform"
|
|
)
|
|
|
|
func testJSONHookResourceID(addr addrs.AbsResourceInstance) terraform.HookResourceIdentity {
|
|
return terraform.HookResourceIdentity{
|
|
Addr: addr,
|
|
ProviderAddr: addrs.Provider{
|
|
Type: "test",
|
|
Namespace: "hashicorp",
|
|
Hostname: "example.com",
|
|
},
|
|
}
|
|
}
|
|
|
|
func testJSONLifecycleHook(actionAddr addrs.AbsActionInstance, triggeringResourceAddr addrs.AbsResourceInstance, actionTriggerIndex int, actionsListIndex int) terraform.HookActionIdentity {
|
|
return terraform.HookActionIdentity{
|
|
Addr: actionAddr,
|
|
ActionTrigger: &plans.LifecycleActionTrigger{
|
|
TriggeringResourceAddr: triggeringResourceAddr,
|
|
ActionTriggerBlockIndex: actionTriggerIndex,
|
|
ActionsListIndex: actionsListIndex,
|
|
ActionTriggerEvent: configs.AfterCreate,
|
|
},
|
|
}
|
|
}
|
|
|
|
func testJSONInvokeHook(actionAddr addrs.AbsActionInstance) terraform.HookActionIdentity {
|
|
return terraform.HookActionIdentity{
|
|
Addr: actionAddr,
|
|
ActionTrigger: new(plans.InvokeActionTrigger),
|
|
}
|
|
}
|
|
|
|
// 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)))
|
|
|
|
var nowMu sync.Mutex
|
|
now := time.Now()
|
|
hook.timeNow = func() time.Time {
|
|
nowMu.Lock()
|
|
defer nowMu.Unlock()
|
|
return now
|
|
}
|
|
|
|
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"),
|
|
}),
|
|
})
|
|
|
|
action, err := hook.PreApply(testJSONHookResourceID(addr), addrs.NotDeposed, plans.Create, priorState, plannedNewState)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.PreProvisionInstanceStep(testJSONHookResourceID(addr), "local-exec")
|
|
testHookReturnValues(t, action, err)
|
|
|
|
hook.ProvisionOutput(testJSONHookResourceID(addr), "local-exec", `Executing: ["/bin/sh" "-c" "touch /etc/motd"]`)
|
|
|
|
action, err = hook.PostProvisionInstanceStep(testJSONHookResourceID(addr), "local-exec", nil)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
// Travel 10s into the future, notify the progress goroutine, and sleep
|
|
// briefly to allow it to execute
|
|
nowMu.Lock()
|
|
now = now.Add(10 * time.Second)
|
|
after <- now
|
|
nowMu.Unlock()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Travel 10s into the future, notify the progress goroutine, and sleep
|
|
// briefly to allow it to execute
|
|
nowMu.Lock()
|
|
now = now.Add(10 * time.Second)
|
|
after <- now
|
|
nowMu.Unlock()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Travel 2s into the future. We have arrived!
|
|
nowMu.Lock()
|
|
now = now.Add(2 * time.Second)
|
|
nowMu.Unlock()
|
|
|
|
action, err = hook.PostApply(testJSONHookResourceID(addr), addrs.NotDeposed, plannedNewState, nil)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
// Shut down the progress goroutine if still active
|
|
hook.resourceProgressMu.Lock()
|
|
for key, progress := range hook.resourceProgress {
|
|
close(progress.done)
|
|
<-progress.heartbeatDone
|
|
delete(hook.resourceProgress, key)
|
|
}
|
|
hook.resourceProgressMu.Unlock()
|
|
|
|
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"),
|
|
}),
|
|
})
|
|
|
|
action, err := hook.PreApply(testJSONHookResourceID(addr), addrs.NotDeposed, plans.Delete, priorState, plannedNewState)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
provisionError := fmt.Errorf("provisioner didn't want to")
|
|
action, err = hook.PostProvisionInstanceStep(testJSONHookResourceID(addr), "local-exec", provisionError)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
applyError := fmt.Errorf("provider was sad")
|
|
action, err = hook.PostApply(testJSONHookResourceID(addr), addrs.NotDeposed, plannedNewState, applyError)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
// Shut down the progress goroutine
|
|
hook.resourceProgressMu.Lock()
|
|
for key, progress := range hook.resourceProgress {
|
|
close(progress.done)
|
|
<-progress.heartbeatDone
|
|
delete(hook.resourceProgress, key)
|
|
}
|
|
hook.resourceProgressMu.Unlock()
|
|
|
|
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"),
|
|
}),
|
|
})
|
|
|
|
action, err := hook.PreRefresh(testJSONHookResourceID(addr), addrs.NotDeposed, state)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.PostRefresh(testJSONHookResourceID(addr), addrs.NotDeposed, state, state)
|
|
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)
|
|
}
|
|
|
|
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) {
|
|
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"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@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"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@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"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@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"),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
stdout := done(t).Stdout()
|
|
testJSONViewOutputEquals(t, stdout, want)
|
|
})
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
action, err := hook.StartAction(actionAHookId)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.ProgressAction(actionAHookId, "Hello world from the lambda function")
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.StartAction(actionBHookId)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.ProgressAction(actionBHookId, "TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]")
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.CompleteAction(actionBHookId, nil)
|
|
testHookReturnValues(t, action, err)
|
|
|
|
action, err = hook.CompleteAction(actionAHookId, errors.New("lambda terminated with exit code 1"))
|
|
testHookReturnValues(t, action, err)
|
|
|
|
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)
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": "test_instance.boop.trigger[0]: Action started: action.aws_lambda_invocation.notify_slack[42]",
|
|
"@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",
|
|
"action": "action.aws_lambda_invocation.notify_slack[42]",
|
|
"action_key": float64(42),
|
|
"action_name": "notify_slack",
|
|
"action_type": "aws_lambda_invocation",
|
|
},
|
|
"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": "test_instance.boop (0): action.aws_lambda_invocation.notify_slack[42] - Hello world from the lambda function",
|
|
"@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",
|
|
"action": "action.aws_lambda_invocation.notify_slack[42]",
|
|
"action_key": float64(42),
|
|
"action_name": "notify_slack",
|
|
"action_type": "aws_lambda_invocation",
|
|
},
|
|
"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),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@level": "info",
|
|
"@message": "module.childMod[\"infra\"].test_instance.boop.trigger[2]: Action started: module.childMod[\"infra\"].action.ansible_playbook.webserver",
|
|
"@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",
|
|
"action": "action.ansible_playbook.webserver",
|
|
"action_key": nil,
|
|
"action_name": "webserver",
|
|
"action_type": "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),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@level": "info",
|
|
"@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]",
|
|
"@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",
|
|
"action": "action.ansible_playbook.webserver",
|
|
"action_key": nil,
|
|
"action_name": "webserver",
|
|
"action_type": "ansible_playbook",
|
|
},
|
|
"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),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@level": "info",
|
|
"@message": "module.childMod[\"infra\"].test_instance.boop (2): Action complete: module.childMod[\"infra\"].action.ansible_playbook.webserver",
|
|
"@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",
|
|
"action": "action.ansible_playbook.webserver",
|
|
"action_key": nil,
|
|
"action_name": "webserver",
|
|
"action_type": "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),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"@level": "info",
|
|
"@message": "test_instance.boop (0): Action errored: action.aws_lambda_invocation.notify_slack[42] - lambda terminated with exit code 1",
|
|
"@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",
|
|
"action": "action.aws_lambda_invocation.notify_slack[42]",
|
|
"action_key": float64(42),
|
|
"action_name": "notify_slack",
|
|
"action_type": "aws_lambda_invocation",
|
|
},
|
|
"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]",
|
|
"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",
|
|
},
|
|
"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",
|
|
},
|
|
"invoke": map[string]interface{}{},
|
|
},
|
|
},
|
|
}
|
|
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|