github.com/prudhvitella/terraform@v0.6.7-0.20151120205041-cf87ede5ddc5/command/hook_ui.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"unicode"
    11  
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/mitchellh/colorstring"
    15  )
    16  
    17  type UiHook struct {
    18  	terraform.NilHook
    19  
    20  	Colorize *colorstring.Colorize
    21  	Ui       cli.Ui
    22  
    23  	l         sync.Mutex
    24  	once      sync.Once
    25  	resources map[string]uiResourceOp
    26  	ui        cli.Ui
    27  }
    28  
    29  type uiResourceOp byte
    30  
    31  const (
    32  	uiResourceUnknown uiResourceOp = iota
    33  	uiResourceCreate
    34  	uiResourceModify
    35  	uiResourceDestroy
    36  )
    37  
    38  func (h *UiHook) PreApply(
    39  	n *terraform.InstanceInfo,
    40  	s *terraform.InstanceState,
    41  	d *terraform.InstanceDiff) (terraform.HookAction, error) {
    42  	h.once.Do(h.init)
    43  
    44  	id := n.HumanId()
    45  
    46  	op := uiResourceModify
    47  	if d.Destroy {
    48  		op = uiResourceDestroy
    49  	} else if s.ID == "" {
    50  		op = uiResourceCreate
    51  	}
    52  
    53  	h.l.Lock()
    54  	h.resources[id] = op
    55  	h.l.Unlock()
    56  
    57  	var operation string
    58  	switch op {
    59  	case uiResourceModify:
    60  		operation = "Modifying..."
    61  	case uiResourceDestroy:
    62  		operation = "Destroying..."
    63  	case uiResourceCreate:
    64  		operation = "Creating..."
    65  	case uiResourceUnknown:
    66  		return terraform.HookActionContinue, nil
    67  	}
    68  
    69  	attrBuf := new(bytes.Buffer)
    70  
    71  	// Get all the attributes that are changing, and sort them. Also
    72  	// determine the longest key so that we can align them all.
    73  	keyLen := 0
    74  	keys := make([]string, 0, len(d.Attributes))
    75  	for key, _ := range d.Attributes {
    76  		// Skip the ID since we do that specially
    77  		if key == "id" {
    78  			continue
    79  		}
    80  
    81  		keys = append(keys, key)
    82  		if len(key) > keyLen {
    83  			keyLen = len(key)
    84  		}
    85  	}
    86  	sort.Strings(keys)
    87  
    88  	// Go through and output each attribute
    89  	for _, attrK := range keys {
    90  		attrDiff := d.Attributes[attrK]
    91  
    92  		v := attrDiff.New
    93  		if attrDiff.NewComputed {
    94  			v = "<computed>"
    95  		}
    96  
    97  		attrBuf.WriteString(fmt.Sprintf(
    98  			"  %s:%s %#v => %#v\n",
    99  			attrK,
   100  			strings.Repeat(" ", keyLen-len(attrK)),
   101  			attrDiff.Old,
   102  			v))
   103  	}
   104  
   105  	attrString := strings.TrimSpace(attrBuf.String())
   106  	if attrString != "" {
   107  		attrString = "\n  " + attrString
   108  	}
   109  
   110  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   111  		"[reset][bold]%s: %s[reset_bold]%s",
   112  		id,
   113  		operation,
   114  		attrString)))
   115  
   116  	return terraform.HookActionContinue, nil
   117  }
   118  
   119  func (h *UiHook) PostApply(
   120  	n *terraform.InstanceInfo,
   121  	s *terraform.InstanceState,
   122  	applyerr error) (terraform.HookAction, error) {
   123  	id := n.HumanId()
   124  
   125  	h.l.Lock()
   126  	op := h.resources[id]
   127  	delete(h.resources, id)
   128  	h.l.Unlock()
   129  
   130  	var msg string
   131  	switch op {
   132  	case uiResourceModify:
   133  		msg = "Modifications complete"
   134  	case uiResourceDestroy:
   135  		msg = "Destruction complete"
   136  	case uiResourceCreate:
   137  		msg = "Creation complete"
   138  	case uiResourceUnknown:
   139  		return terraform.HookActionContinue, nil
   140  	}
   141  
   142  	if applyerr != nil {
   143  		// Errors are collected and printed in ApplyCommand, no need to duplicate
   144  		return terraform.HookActionContinue, nil
   145  	}
   146  
   147  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   148  		"[reset][bold]%s: %s[reset_bold]",
   149  		id, msg)))
   150  
   151  	return terraform.HookActionContinue, nil
   152  }
   153  
   154  func (h *UiHook) PreDiff(
   155  	n *terraform.InstanceInfo,
   156  	s *terraform.InstanceState) (terraform.HookAction, error) {
   157  	return terraform.HookActionContinue, nil
   158  }
   159  
   160  func (h *UiHook) PreProvision(
   161  	n *terraform.InstanceInfo,
   162  	provId string) (terraform.HookAction, error) {
   163  	id := n.HumanId()
   164  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   165  		"[reset][bold]%s: Provisioning with '%s'...[reset_bold]",
   166  		id, provId)))
   167  	return terraform.HookActionContinue, nil
   168  }
   169  
   170  func (h *UiHook) ProvisionOutput(
   171  	n *terraform.InstanceInfo,
   172  	provId string,
   173  	msg string) {
   174  	id := n.HumanId()
   175  	var buf bytes.Buffer
   176  	buf.WriteString(h.Colorize.Color("[reset]"))
   177  
   178  	prefix := fmt.Sprintf("%s (%s): ", id, provId)
   179  	s := bufio.NewScanner(strings.NewReader(msg))
   180  	s.Split(scanLines)
   181  	for s.Scan() {
   182  		line := strings.TrimRightFunc(s.Text(), unicode.IsSpace)
   183  		if line != "" {
   184  			buf.WriteString(fmt.Sprintf("%s%s\n", prefix, line))
   185  		}
   186  	}
   187  
   188  	h.ui.Output(strings.TrimSpace(buf.String()))
   189  }
   190  
   191  func (h *UiHook) PreRefresh(
   192  	n *terraform.InstanceInfo,
   193  	s *terraform.InstanceState) (terraform.HookAction, error) {
   194  	h.once.Do(h.init)
   195  
   196  	id := n.HumanId()
   197  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   198  		"[reset][bold]%s: Refreshing state... (ID: %s)",
   199  		id, s.ID)))
   200  	return terraform.HookActionContinue, nil
   201  }
   202  
   203  func (h *UiHook) init() {
   204  	if h.Colorize == nil {
   205  		panic("colorize not given")
   206  	}
   207  
   208  	h.resources = make(map[string]uiResourceOp)
   209  
   210  	// Wrap the ui so that it is safe for concurrency regardless of the
   211  	// underlying reader/writer that is in place.
   212  	h.ui = &cli.ConcurrentUi{Ui: h.Ui}
   213  }
   214  
   215  // scanLines is basically copied from the Go standard library except
   216  // we've modified it to also fine `\r`.
   217  func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
   218  	if atEOF && len(data) == 0 {
   219  		return 0, nil, nil
   220  	}
   221  	if i := bytes.IndexByte(data, '\n'); i >= 0 {
   222  		// We have a full newline-terminated line.
   223  		return i + 1, dropCR(data[0:i]), nil
   224  	}
   225  	if i := bytes.IndexByte(data, '\r'); i >= 0 {
   226  		// We have a full newline-terminated line.
   227  		return i + 1, dropCR(data[0:i]), nil
   228  	}
   229  	// If we're at EOF, we have a final, non-terminated line. Return it.
   230  	if atEOF {
   231  		return len(data), dropCR(data), nil
   232  	}
   233  	// Request more data.
   234  	return 0, nil, nil
   235  }
   236  
   237  // dropCR drops a terminal \r from the data.
   238  func dropCR(data []byte) []byte {
   239  	if len(data) > 0 && data[len(data)-1] == '\r' {
   240  		return data[0 : len(data)-1]
   241  	}
   242  	return data
   243  }