command: show resource actions using resource addresses

Previously we were using the internal resource id syntax in the UI. Now
we'll use the standard user-facing resource address syntax instead.
This commit is contained in:
Martin Atkins 2017-08-23 17:11:57 -07:00
parent 3ea159297c
commit d4efc95191
3 changed files with 112 additions and 11 deletions

View File

@ -65,6 +65,7 @@ func (h *UiHook) PreApply(
} }
id := n.HumanId() id := n.HumanId()
addr := n.ResourceAddress()
op := uiResourceModify op := uiResourceModify
if d.Destroy { if d.Destroy {
@ -142,7 +143,7 @@ func (h *UiHook) PreApply(
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: %s%s[reset]%s", "[reset][bold]%s: %s%s[reset]%s",
id, addr,
operation, operation,
stateIdSuffix, stateIdSuffix,
attrString))) attrString)))
@ -210,6 +211,7 @@ func (h *UiHook) PostApply(
applyerr error) (terraform.HookAction, error) { applyerr error) (terraform.HookAction, error) {
id := n.HumanId() id := n.HumanId()
addr := n.ResourceAddress()
h.l.Lock() h.l.Lock()
state := h.resources[id] state := h.resources[id]
@ -244,7 +246,7 @@ func (h *UiHook) PostApply(
colorized := h.Colorize.Color(fmt.Sprintf( colorized := h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: %s after %s%s[reset]", "[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) h.ui.Output(colorized)
@ -260,10 +262,10 @@ func (h *UiHook) PreDiff(
func (h *UiHook) PreProvision( func (h *UiHook) PreProvision(
n *terraform.InstanceInfo, n *terraform.InstanceInfo,
provId string) (terraform.HookAction, error) { provId string) (terraform.HookAction, error) {
id := n.HumanId() addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Provisioning with '%s'...[reset]", "[reset][bold]%s: Provisioning with '%s'...[reset]",
id, provId))) addr, provId)))
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
@ -271,11 +273,11 @@ func (h *UiHook) ProvisionOutput(
n *terraform.InstanceInfo, n *terraform.InstanceInfo,
provId string, provId string,
msg string) { msg string) {
id := n.HumanId() addr := n.ResourceAddress()
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(h.Colorize.Color("[reset]")) 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 := bufio.NewScanner(strings.NewReader(msg))
s.Split(scanLines) s.Split(scanLines)
for s.Scan() { for s.Scan() {
@ -293,7 +295,7 @@ func (h *UiHook) PreRefresh(
s *terraform.InstanceState) (terraform.HookAction, error) { s *terraform.InstanceState) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
id := n.HumanId() addr := n.ResourceAddress()
var stateIdSuffix string var stateIdSuffix string
// Data resources refresh before they have ids, whereas managed // 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( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Refreshing state...%s", "[reset][bold]%s: Refreshing state...%s",
id, stateIdSuffix))) addr, stateIdSuffix)))
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
@ -313,9 +315,10 @@ func (h *UiHook) PreImportState(
id string) (terraform.HookAction, error) { id string) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Importing from ID %q...", "[reset][bold]%s: Importing from ID %q...",
n.HumanId(), id))) addr, id)))
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
@ -324,9 +327,9 @@ func (h *UiHook) PostImportState(
s []*terraform.InstanceState) (terraform.HookAction, error) { s []*terraform.InstanceState) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
id := n.HumanId() addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf( 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 { for _, s := range s {
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][green] Imported %s (ID: %s)", "[reset][green] Imported %s (ID: %s)",

View File

@ -88,6 +88,46 @@ func (i *InstanceInfo) HumanId() string {
i.Id) 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 { func (i *InstanceInfo) uniqueId() string {
prefix := i.HumanId() prefix := i.HumanId()
if v := i.uniqueExtra; v != "" { if v := i.uniqueExtra; v != "" {

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strconv"
"testing" "testing"
"github.com/hashicorp/hil" "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) { func TestResourceConfigGet(t *testing.T) {
cases := []struct { cases := []struct {
Config map[string]interface{} Config map[string]interface{}