Skip to content

Commit 0ef0e67

Browse files
committed
Include identity in import apply UI output (#37044)
* Include identity in import apply UI output * Add logging to hook ui errors
1 parent db5060d commit 0ef0e67

File tree

3 files changed

+212
-8
lines changed

3 files changed

+212
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: Include resource identity in import apply UI output
3+
time: 2025-05-14T11:21:52.75746+02:00
4+
custom:
5+
Issue: "37044"

internal/command/views/hook_ui.go

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import (
1414

1515
"github.com/zclconf/go-cty/cty"
1616

17+
"github.com/hashicorp/go-hclog"
1718
"github.com/hashicorp/terraform/internal/addrs"
1819
"github.com/hashicorp/terraform/internal/command/format"
20+
"github.com/hashicorp/terraform/internal/logging"
1921
"github.com/hashicorp/terraform/internal/plans"
2022
"github.com/hashicorp/terraform/internal/providers"
2123
"github.com/hashicorp/terraform/internal/terraform"
@@ -31,6 +33,7 @@ func NewUiHook(view *View) *UiHook {
3133
view: view,
3234
periodicUiTimer: defaultPeriodicUiTimer,
3335
resources: make(map[string]uiResourceState),
36+
log: logging.HCLogger(),
3437
}
3538
}
3639

@@ -44,6 +47,8 @@ type UiHook struct {
4447

4548
resources map[string]uiResourceState
4649
resourcesLock sync.Mutex
50+
51+
log hclog.Logger
4752
}
4853

4954
var _ terraform.Hook = (*UiHook)(nil)
@@ -342,19 +347,71 @@ func (h *UiHook) PrePlanImport(id terraform.HookResourceIdentity, importTarget c
342347
}
343348

344349
func (h *UiHook) PreApplyImport(id terraform.HookResourceIdentity, importing plans.ImportingSrc) (terraform.HookAction, error) {
345-
h.println(fmt.Sprintf(
346-
h.view.colorize.Color("[reset][bold]%s: Importing... [id=%s]"),
347-
id.Addr, importing.ID,
348-
))
350+
if importing.Identity != nil {
351+
ty, err := importing.Identity.ImpliedType()
352+
if err != nil {
353+
h.log.Debug("UiHook: PreApplyImport failed to get identity ImpliedType", err)
354+
h.println(fmt.Sprintf(
355+
h.view.colorize.Color("[reset][bold]%s: Importing... [identity=(type error)]"),
356+
id.Addr,
357+
))
358+
return terraform.HookActionContinue, nil
359+
}
360+
val, err := importing.Identity.Decode(ty)
361+
if err != nil {
362+
h.log.Debug("UiHook: PreApplyImport failed to decode identity", err)
363+
h.println(fmt.Sprintf(
364+
h.view.colorize.Color("[reset][bold]%s: Importing... [identity=(decode error)]"),
365+
id.Addr,
366+
))
367+
return terraform.HookActionContinue, nil
368+
}
369+
370+
h.println(fmt.Sprintf(
371+
h.view.colorize.Color("[reset][bold]%s: Importing... [identity=%s]"),
372+
id.Addr, tfdiags.ObjectToString(val),
373+
))
374+
} else {
375+
h.println(fmt.Sprintf(
376+
h.view.colorize.Color("[reset][bold]%s: Importing... [id=%s]"),
377+
id.Addr, importing.ID,
378+
))
379+
}
349380

350381
return terraform.HookActionContinue, nil
351382
}
352383

353384
func (h *UiHook) PostApplyImport(id terraform.HookResourceIdentity, importing plans.ImportingSrc) (terraform.HookAction, error) {
354-
h.println(fmt.Sprintf(
355-
h.view.colorize.Color("[reset][bold]%s: Import complete [id=%s]"),
356-
id.Addr, importing.ID,
357-
))
385+
if importing.Identity != nil {
386+
ty, err := importing.Identity.ImpliedType()
387+
if err != nil {
388+
h.log.Debug("UiHook: PostApplyImport failed to get identity ImpliedType", err)
389+
h.println(fmt.Sprintf(
390+
h.view.colorize.Color("[reset][bold]%s: Import complete [identity=(type error)]"),
391+
id.Addr,
392+
))
393+
return terraform.HookActionContinue, nil
394+
}
395+
val, err := importing.Identity.Decode(ty)
396+
if err != nil {
397+
h.log.Debug("UiHook: PostApplyImport failed to decode identity", err)
398+
h.println(fmt.Sprintf(
399+
h.view.colorize.Color("[reset][bold]%s: Import complete [identity=(decode error)]"),
400+
id.Addr,
401+
))
402+
return terraform.HookActionContinue, nil
403+
}
404+
405+
h.println(fmt.Sprintf(
406+
h.view.colorize.Color("[reset][bold]%s: Import complete [identity=%s]"),
407+
id.Addr, tfdiags.ObjectToString(val),
408+
))
409+
} else {
410+
h.println(fmt.Sprintf(
411+
h.view.colorize.Color("[reset][bold]%s: Import complete [id=%s]"),
412+
id.Addr, importing.ID,
413+
))
414+
}
358415

359416
return terraform.HookActionContinue, nil
360417
}

internal/command/views/hook_ui_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,140 @@ func TestUiHookPreImportState(t *testing.T) {
512512
}
513513
}
514514

515+
func TestUiHookPreApplyImport(t *testing.T) {
516+
testCases := map[string]struct {
517+
importingSrc plans.ImportingSrc
518+
want string
519+
}{
520+
"id": {
521+
importingSrc: plans.ImportingSrc{
522+
ID: "test",
523+
},
524+
want: "test_instance.foo: Importing... [id=test]\n",
525+
},
526+
"identity": {
527+
importingSrc: plans.ImportingSrc{
528+
Identity: mustNewDynamicValue(
529+
cty.ObjectVal(map[string]cty.Value{
530+
"id": cty.StringVal("test"),
531+
}),
532+
cty.Object(map[string]cty.Type{
533+
"id": cty.String,
534+
}),
535+
),
536+
},
537+
want: "test_instance.foo: Importing... [identity=id=test]\n",
538+
},
539+
"identity type error": {
540+
importingSrc: plans.ImportingSrc{
541+
Identity: mustNewDynamicValue(
542+
cty.ObjectVal(map[string]cty.Value{
543+
"id": cty.StringVal("test"),
544+
}),
545+
cty.DynamicPseudoType,
546+
),
547+
},
548+
want: "test_instance.foo: Importing... [identity=(type error)]\n",
549+
},
550+
}
551+
552+
addr := addrs.Resource{
553+
Mode: addrs.ManagedResourceMode,
554+
Type: "test_instance",
555+
Name: "foo",
556+
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
557+
558+
for name, tc := range testCases {
559+
t.Run(name, func(t *testing.T) {
560+
streams, done := terminal.StreamsForTesting(t)
561+
view := NewView(streams)
562+
h := NewUiHook(view)
563+
564+
action, err := h.PreApplyImport(testUiHookResourceID(addr), tc.importingSrc)
565+
566+
if err != nil {
567+
t.Fatal(err)
568+
}
569+
if action != terraform.HookActionContinue {
570+
t.Fatalf("Expected hook to continue, given: %#v", action)
571+
}
572+
result := done(t)
573+
got := result.Stdout()
574+
575+
if got != tc.want {
576+
t.Fatalf("unexpected output\n got: %q\nwant: %q", got, tc.want)
577+
}
578+
})
579+
}
580+
}
581+
582+
func TestUiHookPostApplyImport(t *testing.T) {
583+
testCases := map[string]struct {
584+
importingSrc plans.ImportingSrc
585+
want string
586+
}{
587+
"id": {
588+
importingSrc: plans.ImportingSrc{
589+
ID: "test",
590+
},
591+
want: "test_instance.foo: Import complete [id=test]\n",
592+
},
593+
"identity": {
594+
importingSrc: plans.ImportingSrc{
595+
Identity: mustNewDynamicValue(
596+
cty.ObjectVal(map[string]cty.Value{
597+
"id": cty.StringVal("test"),
598+
}),
599+
cty.Object(map[string]cty.Type{
600+
"id": cty.String,
601+
}),
602+
),
603+
},
604+
want: "test_instance.foo: Import complete [identity=id=test]\n",
605+
},
606+
"identity type error": {
607+
importingSrc: plans.ImportingSrc{
608+
Identity: mustNewDynamicValue(
609+
cty.ObjectVal(map[string]cty.Value{
610+
"id": cty.StringVal("test"),
611+
}),
612+
cty.DynamicPseudoType,
613+
),
614+
},
615+
want: "test_instance.foo: Import complete [identity=(type error)]\n",
616+
},
617+
}
618+
619+
addr := addrs.Resource{
620+
Mode: addrs.ManagedResourceMode,
621+
Type: "test_instance",
622+
Name: "foo",
623+
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
624+
625+
for name, tc := range testCases {
626+
t.Run(name, func(t *testing.T) {
627+
streams, done := terminal.StreamsForTesting(t)
628+
view := NewView(streams)
629+
h := NewUiHook(view)
630+
631+
action, err := h.PostApplyImport(testUiHookResourceID(addr), tc.importingSrc)
632+
633+
if err != nil {
634+
t.Fatal(err)
635+
}
636+
if action != terraform.HookActionContinue {
637+
t.Fatalf("Expected hook to continue, given: %#v", action)
638+
}
639+
result := done(t)
640+
got := result.Stdout()
641+
642+
if got != tc.want {
643+
t.Fatalf("unexpected output\n got: %q\nwant: %q", got, tc.want)
644+
}
645+
})
646+
}
647+
}
648+
515649
// Test the PostImportState UI hook. Again, this hook behaviour seems odd to
516650
// me (see below), so please don't consider these tests as justification for
517651
// keeping this behaviour.
@@ -792,3 +926,11 @@ func TestTruncateId(t *testing.T) {
792926
})
793927
}
794928
}
929+
930+
func mustNewDynamicValue(val cty.Value, ty cty.Type) plans.DynamicValue {
931+
ret, err := plans.NewDynamicValue(val, ty)
932+
if err != nil {
933+
panic(err)
934+
}
935+
return ret
936+
}

0 commit comments

Comments
 (0)