diff --git a/command/hook_ui.go b/command/hook_ui.go index 5d4690677c..76b1ca59cf 100644 --- a/command/hook_ui.go +++ b/command/hook_ui.go @@ -65,6 +65,7 @@ func (h *UiHook) PreApply( } id := n.HumanId() + addr := n.ResourceAddress() op := uiResourceModify if d.Destroy { @@ -142,7 +143,7 @@ func (h *UiHook) PreApply( h.ui.Output(h.Colorize.Color(fmt.Sprintf( "[reset][bold]%s: %s%s[reset]%s", - id, + addr, operation, stateIdSuffix, attrString))) @@ -210,6 +211,7 @@ func (h *UiHook) PostApply( applyerr error) (terraform.HookAction, error) { id := n.HumanId() + addr := n.ResourceAddress() h.l.Lock() state := h.resources[id] @@ -244,7 +246,7 @@ func (h *UiHook) PostApply( colorized := h.Colorize.Color(fmt.Sprintf( "[reset][bold]%s: %s after %s%s[reset]", - id, msg, time.Now().Round(time.Second).Sub(state.Start), stateIdSuffix)) + addr, msg, time.Now().Round(time.Second).Sub(state.Start), stateIdSuffix)) h.ui.Output(colorized) @@ -260,10 +262,10 @@ func (h *UiHook) PreDiff( func (h *UiHook) PreProvision( n *terraform.InstanceInfo, provId string) (terraform.HookAction, error) { - id := n.HumanId() + addr := n.ResourceAddress() h.ui.Output(h.Colorize.Color(fmt.Sprintf( "[reset][bold]%s: Provisioning with '%s'...[reset]", - id, provId))) + addr, provId))) return terraform.HookActionContinue, nil } @@ -271,11 +273,11 @@ func (h *UiHook) ProvisionOutput( n *terraform.InstanceInfo, provId string, msg string) { - id := n.HumanId() + addr := n.ResourceAddress() var buf bytes.Buffer buf.WriteString(h.Colorize.Color("[reset]")) - prefix := fmt.Sprintf("%s (%s): ", id, provId) + prefix := fmt.Sprintf("%s (%s): ", addr, provId) s := bufio.NewScanner(strings.NewReader(msg)) s.Split(scanLines) for s.Scan() { @@ -293,7 +295,7 @@ func (h *UiHook) PreRefresh( s *terraform.InstanceState) (terraform.HookAction, error) { h.once.Do(h.init) - id := n.HumanId() + addr := n.ResourceAddress() var stateIdSuffix string // Data resources refresh before they have ids, whereas managed @@ -304,7 +306,7 @@ func (h *UiHook) PreRefresh( h.ui.Output(h.Colorize.Color(fmt.Sprintf( "[reset][bold]%s: Refreshing state...%s", - id, stateIdSuffix))) + addr, stateIdSuffix))) return terraform.HookActionContinue, nil } @@ -313,9 +315,10 @@ func (h *UiHook) PreImportState( id string) (terraform.HookAction, error) { h.once.Do(h.init) + addr := n.ResourceAddress() h.ui.Output(h.Colorize.Color(fmt.Sprintf( "[reset][bold]%s: Importing from ID %q...", - n.HumanId(), id))) + addr, id))) return terraform.HookActionContinue, nil } @@ -324,9 +327,9 @@ func (h *UiHook) PostImportState( s []*terraform.InstanceState) (terraform.HookAction, error) { h.once.Do(h.init) - id := n.HumanId() + addr := n.ResourceAddress() h.ui.Output(h.Colorize.Color(fmt.Sprintf( - "[reset][bold][green]%s: Import complete!", id))) + "[reset][bold][green]%s: Import complete!", addr))) for _, s := range s { h.ui.Output(h.Colorize.Color(fmt.Sprintf( "[reset][green] Imported %s (ID: %s)", diff --git a/terraform/resource.go b/terraform/resource.go index 0acf0beb2a..a8cd8dd9f0 100644 --- a/terraform/resource.go +++ b/terraform/resource.go @@ -88,6 +88,46 @@ func (i *InstanceInfo) HumanId() string { i.Id) } +// ResourceAddress returns the address of the resource that the receiver is describing. +func (i *InstanceInfo) ResourceAddress() *ResourceAddress { + // GROSS: for tainted and deposed instances, their status gets appended + // to i.Id to create a unique id for the graph node. Historically these + // ids were displayed to the user, so it's designed to be human-readable: + // "aws_instance.bar.0 (deposed #0)" + // + // So here we detect such suffixes and try to interpret them back to + // their original meaning so we can then produce a ResourceAddress + // with a suitable InstanceType. + id := i.Id + instanceType := TypeInvalid + if idx := strings.Index(id, " ("); idx != -1 { + remain := id[idx:] + id = id[:idx] + + switch { + case strings.Contains(remain, "tainted"): + instanceType = TypeTainted + case strings.Contains(remain, "deposed"): + instanceType = TypeDeposed + } + } + + addr, err := parseResourceAddressInternal(id) + if err != nil { + // should never happen, since that would indicate a bug in the + // code that constructed this InstanceInfo. + panic(fmt.Errorf("InstanceInfo has invalid Id %s", id)) + } + if len(i.ModulePath) > 1 { + addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied + } + if instanceType != TypeInvalid { + addr.InstanceTypeSet = true + addr.InstanceType = instanceType + } + return addr +} + func (i *InstanceInfo) uniqueId() string { prefix := i.HumanId() if v := i.uniqueExtra; v != "" { diff --git a/terraform/resource_test.go b/terraform/resource_test.go index 750e7060d1..31d511e5c5 100644 --- a/terraform/resource_test.go +++ b/terraform/resource_test.go @@ -3,6 +3,7 @@ package terraform import ( "fmt" "reflect" + "strconv" "testing" "github.com/hashicorp/hil" @@ -46,6 +47,63 @@ func TestInstanceInfo(t *testing.T) { } } +func TestInstanceInfoResourceAddress(t *testing.T) { + tests := []struct { + Input *InstanceInfo + Want string + }{ + { + &InstanceInfo{ + Id: "test_resource.baz", + }, + "test_resource.baz", + }, + { + &InstanceInfo{ + Id: "test_resource.baz", + ModulePath: rootModulePath, + }, + "test_resource.baz", + }, + { + &InstanceInfo{ + Id: "test_resource.baz", + ModulePath: []string{"root", "foo"}, + }, + "module.foo.test_resource.baz", + }, + { + &InstanceInfo{ + Id: "test_resource.baz", + ModulePath: []string{"root", "foo", "bar"}, + }, + "module.foo.module.bar.test_resource.baz", + }, + { + &InstanceInfo{ + Id: "test_resource.baz (tainted)", + }, + "test_resource.baz.tainted", + }, + { + &InstanceInfo{ + Id: "test_resource.baz (deposed #0)", + }, + "test_resource.baz.deposed", + }, + } + + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + gotAddr := test.Input.ResourceAddress() + got := gotAddr.String() + if got != test.Want { + t.Fatalf("wrong result\ngot: %s\nwant: %s", got, test.Want) + } + }) + } +} + func TestResourceConfigGet(t *testing.T) { cases := []struct { Config map[string]interface{}