8000 add hooks for action invocation lifecycle · hashicorp/terraform@d9b93cd · GitHub
[go: up one dir, main page]

Skip to content

Commit d9b93cd

Browse files
add hooks for action invocation lifecycle
1 parent ac37560 commit d9b93cd

File tree

10 files changed

+538
-0
lines changed

10 files changed

+538
-0
lines changed

internal/command/views/hook_json.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,25 @@ func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plan
258258
json.MessageListResourceFound, result,
259259
)
260260
}
261+
return terraform.HookActionContinue, nil
262+
}
263+
264+
func (h *jsonHook) StartAction(id terraform.HookActionIdentity) (terraform.HookAction, error) {
265+
h.view.Hook(json.NewActionStart(id))
266+
return terraform.HookActionContinue, nil
267+
}
268+
269+
func (h *jsonHook) ProgressAction(id terraform.HookActionIdentity, progress string) (terraform.HookAction, error) {
270+
h.view.Hook(json.NewActionProgress(id, progress))
271+
return terraform.HookActionContinue, nil
272+
}
273+
274+
func (h *jsonHook) CompleteAction(id terraform.HookActionIdentity, err error) (terraform.HookAction, error) {
261275

276+
if err != nil {
277+
h.view.Hook(json.NewActionErrored(id, err))
278+
} else {
279+
h.view.Hook(json.NewActionComplete(id))
280+
}
262281
return terraform.HookActionContinue, nil
263282
}

internal/command/views/hook_json_test.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ func testJSONHookResourceID(addr addrs.AbsResourceInstance) terraform.HookResour
3030
}
3131
}
3232

33+
func testJSONHookActionID(addr addrs.ActionInvocation) terraform.HookActionIdentity {
34+
return terraform.HookActionIdentity{
35+
TriggeringResource: addr.TriggeringResource,
36+
Action: addr.Action,
37+
TriggerIndex: addr.TriggerIndex,
38+
}
39+
}
40+
3341
// Test a sequence of hooks associated with creating a resource
3442
func TestJSONHook_create(t *testing.T) {
3543
streams, done := terminal.StreamsForTesting(t)
@@ -567,6 +575,239 @@ func TestJSONHook_EphemeralOp_error(t *testing.T) {
567575
testJSONViewOutputEquals(t, done(t).Stdout(), want)
568576
}
569577

578+
func TestJSONHook_actions(t *testing.T) {
579+
streams, done := terminal.StreamsForTesting(t)
580+
hook := newJSONHook(NewJSONView(NewView(streams)))
581+
582+
actionA := addrs.AbsActionInstance{
583+
Module: addrs.RootModuleInstance,
584+
Action: addrs.Action{
585+
Type: "aws_lambda_invocation",
586+
Name: "notify_slack",
587+
}.Instance(addrs.IntKey 2D03 (42)),
588+
}
589+
590+
resourceA := addrs.Resource{
591+
Mode: addrs.ManagedResourceMode,
592+
Type: "test_instance",
593+
Name: "boop",
594+
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
595+
596+
invocationA := addrs.ActionInvocation{
597+
TriggeringResource: resourceA,
598+
Action: actionA,
599+
TriggerIndex: 23,
600+
}
601+
602+
subModule := addrs.RootModuleInstance.Child("childMod", addrs.StringKey("infra"))
603+
actionB := addrs.AbsActionInstance{
604+
Module: subModule,
605+
Action: addrs.Action{
606+
Type: "ansible_playbook",
607+
Name: "webserver",
608+
}.Instance(addrs.NoKey),
609+
}
610+
611+
resourceB := addrs.Resource{
612+
Mode: addrs.ManagedResourceMode,
613+
Type: "test_instance",
614+
Name: "boop",
615+
}.Instance(addrs.NoKey).Absolute(subModule)
616+
617+
invocationB := addrs.ActionInvocation{
618+
TriggeringResource: resourceB,
619+
Action: actionB,
620+
TriggerIndex: 0,
621+
}
622+
action, err := hook.StartAction(testJSONHookActionID(invocationA))
623+
testHookReturnValues(t, action, err)
624+
625+
action, err = hook.ProgressAction(testJSONHookActionID(invocationA), "Hello world from the lambda function")
626+
testHookReturnValues(t, action, err)
627+
628+
action, err = hook.StartAction(testJSONHookActionID(invocationB))
629+
testHookReturnValues(t, action, err)
630+
631+
action, err = hook.ProgressAction(testJSONHookActionID(invocationB), "TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]")
632+
testHookReturnValues(t, action, err)
633+
634+
action, err = hook.CompleteAction(testJSONHookActionID(invocationB), nil)
635+
testHookReturnValues(t, action, err)
636+
637+
action, err = hook.CompleteAction(testJSONHookActionID(invocationA), errors.New("lambda terminated with exit code 1"))
638+
testHookReturnValues(t, action, err)
639+
640+
want := []map[string]interface{}{
641+
{
642+
"@level": "info",
643+
"@message": "test_instance.boop.trigger[23]: Action Started: action.aws_lambda_invocation.notify_slack[42]",
644+
"@module": "terraform.ui",
645+
"type": "action_start",
646+
"hook": map[string]interface{}{
647+
"action": map[string]interface{}{
648+
"addr": "action.aws_lambda_invocation.notify_slack[42]",
649+
"module": "",
650+
"implied_provider": "aws",
651+
"resource": "action.aws_lambda_invocation.notify_slack[42]",
652+
"resource_key": float64(42),
653+
"resource_name": "notify_slack",
654+
"resource_type": "aws_lambda_invocation",
655+
},
656+
"resource": map[string]interface{}{
657+
"addr": "test_instance.boop",
658+
"implied_provider": "test",
659+
"module": "",
660+
"resource": "test_instance.boop",
661+
"resource_key": nil,
662+
"resource_name": "boop",
663+
"resource_type": "test_instance",
664+
},
665+
"trigger_index": float64(23),
666+
},
667+
},
668+
{
669+
"@level": "info",
670+
"@message": "test_instance.boop (23): action.aws_lambda_invocation.notify_slack[42] - Hello world from the lambda function",
671+
"@module": "terraform.ui",
672+
"type": "action_progress",
673+
"hook": map[string]interface{}{
674+
"action": map[string]interface{}{
675+
"addr": "action.aws_lambda_invocation.notify_slack[42]",
676+
"module": "",
677+
"implied_provider": "aws",
678+
"resource": "action.aws_lambda_invocation.notify_slack[42]",
679+
"resource_key": float64(42),
680+
"resource_name": "notify_slack",
681+
"resource_type": "aws_lambda_invocation",
682+
},
683+
"message": "Hello world from the lambda function",
684+
"resource": map[string]interface{}{
685+
"addr": "test_instance.boop",
686+
"implied_provider": "test",
687+
"module": "",
688+
"resource": "test_instance.boop",
689+
"resource_key": nil,
690+
"resource_name": "boop",
691+
"resource_type": "test_instance",
692+
},
693+
"trigger_index": float64(23),
694+
},
695+
},
696+
{
697+
"@level": "info",
698+
"@message": "module.childMod[\"infra\"].test_instance.boop.trigger[0]: Action Started: module.childMod[\"infra\"].action.ansible_playbook.webserver",
699+
"@module": "terraform.ui",
700+
"type": "action_start",
701+
"hook": map[string]interface{}{
702+
"action": map[string]interface{}{
703+
"addr": "module.childMod[\"infra\"].action.ansible_playbook.webserver",
704+
"module": "module.childMod[\"infra\"]",
705+
"implied_provider": "ansible",
706+
"resource": "action.ansible_playbook.webserver",
707+
"resource_key": nil,
708+
"resource_name": "webserver",
709+
"resource_type": "ansible_playbook",
710+
},
711+
"resource": map[string]interface{}{
712+
"addr": "module.childMod[\"infra\"].test_instance.boop",
713+
"implied_provider": "test",
714+
"module": "module.childMod[\"infra\"]",
715+
"resource": "test_instance.boop",
716+
"resource_key": nil,
717+
"resource_name": "boop",
718+
"resource_type": "test_instance",
719+
},
720+
"trigger_index": float64(0),
721+
},
722+
},
723+
{
724+
"@level": "info",
725+
"@message": & 2D03 quot;module.childMod[\"infra\"].test_instance.boop (0): module.childMod[\"infra\"].action.ansible_playbook.webserver - TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]",
726+
"@module": "terraform.ui",
727+
"type": "action_progress",
728+
"hook": map[string]interface{}{
729+
"action": map[string]interface{}{
730+
"addr": "module.childMod[\"infra\"].action.ansible_playbook.webserver",
731+
"module": "module.childMod[\"infra\"]",
732+
"implied_provider": "ansible",
733+
"resource": "action.ansible_playbook.webserver",
734+
"resource_key": nil,
735+
"resource_name": "webserver",
736+
"resource_type": "ansible_playbook",
737+
},
738+
"message": "TASK: [hello]\n ok: [localhost] => (item=Hello world from the ansible playbook]",
739+
"resource": map[string]interface{}{
740+
"addr": "module.childMod[\"infra\"].test_instance.boop",
741+
"implied_provider": "test",
742+
"module": "module.childMod[\"infra\"]",
743+
"resource": "test_instance.boop",
744+
"resource_key": nil,
745+
"resource_name": "boop",
746+
"resource_type": "test_instance",
747+
},
748+
"trigger_index": float64(0),
749+
},
750+
},
751+
{
752+
"@level": "info",
753+
"@message": "module.childMod[\"infra\"].test_instance.boop (0): Action Complete: module.childMod[\"infra\"].action.ansible_playbook.webserver",
754+
"@module": "terraform.ui",
755+
"type": "action_complete",
756+
"hook": map[string]interface{}{
757+
"action": map[string]interface{}{
758+
"addr": "module.childMod[\"infra\"].action.ansible_playbook.webserver",
759+
"module": "module.childMod[\"infra\"]",
760+
"implied_provider": "ansible",
761+
"resource": "action.ansible_playbook.webserver",
762+
"resource_key": nil,
763+
"resource_name": "webserver",
764+
"resource_type": "ansible_playbook",
765+
},
766+
"resource": map[string]interface{}{
767+
"addr": "module.childMod[\"infra\"].test_instance.boop",
768+
"implied_provider": "test",
769+
"module": "module.childMod[\"infra\"]",
770+
"resource": "test_instance.boop",
771+
"resource_key": nil,
772+
"resource_name": "boop",
773+
"resource_type": "test_instance",
774+
},
775+
"trigger_index": float64(0),
776+
},
777+
},
778+
{
779+
"@level": "info",
780+
"@message": "test_instance.boop (23): Action Errored: action.aws_lambda_invocation.notify_slack[42] - lambda terminated with exit code 1",
781+
"@module": "terraform.ui",
782+
"type": "action_errored",
783+
"hook": map[string]interface{}{
784+
"action": map[string]interface{}{
785+
"addr": "action.aws_lambda_invocation.notify_slack[42]",
786+
"module": "",
787+
"implied_provider": "aws",
788+
"resource": "action.aws_lambda_invocation.notify_slack[42]",
789+
"resource_key": float64(42),
790+
"resource_name": "notify_slack",
791+
"resource_type": "aws_lambda_invocation",
792+
},
793+
"error": "lambda terminated with exit code 1",
794+
"resource": map[string]interface{}{
795+
"addr": "test_instance.boop",
796+
"implied_provider": "test",
797+
"module": "",
798+
"resource": "test_instance.boop",
799+
"resource_key": nil,
800+
"resource_name": "boop",
801+
"resource_type": "test_instance",
802+
},
803+
"trigger_index": float64(23),
804+
},
805+
},
806+
}
807+
808+
testJSONViewOutputEquals(t, done(t).Stdout(), want)
809+
}
810+
570811
func testHookReturnValues(t *testing.T, action terraform.HookAction, err error) {
571812
t.Helper()
572813

internal/command/views/hook_ui.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,39 @@ func (h *UiHook) PostListQuery(id terraform.HookResourceIdentity, results plans.
525525
value.GetAttr("display_name").AsString(),
526526
))
527527
}
528+
return terraform.HookActionContinue, nil
529+
}
530+
531+
func (h *UiHook) StartAction(id terraform.HookActionIdentity) (terraform.HookAction, error) {
532+
h.println(fmt.Sprintf(
533+
h.view.colorize.Color("[reset][bold]Action started: %s[reset]"),
534+
id.String(),
535+
))
536+
return terraform.HookActionContinue, nil
537+
}
538+
539+
func (h *UiHook) ProgressAction(id terraform.HookActionIdentity, progress string) (terraform.HookAction, error) {
540+
h.println(fmt.Sprintf(
541+
h.view.colorize.Color("[reset][bold]Action %s:[reset] %s[reset]"),
542+
id.String(),
543+
progress,
544+
))
545+
return terraform.HookActionContinue, nil
546+
}
528547

548+
func (h *UiHook) CompleteAction(id terraform.HookActionIdentity, err error) (terraform.HookAction, error) {
549+
if err != nil {
550+
h.println(fmt.Sprintf(
551+
h.view.colorize.Color("[reset][bold][red]Action failed: %s - %v[reset]"),
552+
id.String(),
553+
err,
554+
))
555+
} else {
556+
h.println(fmt.Sprintf(
557+
h.view.colorize.Color("[reset][bold][green]Action complete: %s[reset]"),
558+
id.String(),
559+
))
560+
}
529561
return terraform.HookActionContinue, nil
530562
}
531563

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package json
5+
6+
import (
7+
"github.com/zclconf/go-cty/cty"
8+
ctyjson "github.com/zclconf/go-cty/cty/json"
9+
10+
"github.com/hashicorp/terraform/internal/addrs"
11+
)
12+
13+
type ActionAddr struct {
14+
Addr string `json:"addr"`
15+
Module string `json:"module"`
16+
Action string `json:"resource"`
17+
ImpliedProvider string `json:"implied_provider"`
18+
ActionType string `json:"resource_type"`
19+
ActionName string `json:"resource_name"`
20+
ActionKey ctyjson.SimpleJSONValue `json:"resource_key"`
21+
}
22+
23+
func newActionAddr(addr addrs.AbsActionInstance) ActionAddr {
24+
actionKey := ctyjson.SimpleJSONValue{Value: cty.NilVal}
25+
if addr.Action.Key != nil {
26+
actionKey.Value = addr.Action.Key.Value()
27+
}
28+
return ActionAddr{
29+
Addr: addr.String(),
30+
Module: addr.Module.String(),
31+
Action: addr.Action.String(),
32+
ImpliedProvider: addr.Action.Action.ImpliedProvider(),
33+
ActionType: addr.Action.Action.Type,
34+
ActionName: addr.Action.Action.Name,
35+
ActionKey: actionKey,
36+
}
37+
}

0 commit comments

Comments
 (0)
0