github.com/nalum/terraform@v0.3.2-0.20141223102918-aa2c22ffeff6/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  		msg = fmt.Sprintf("Error: %s", applyerr)
   144  	}
   145  
   146  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   147  		"[reset][bold]%s: %s[reset_bold]",
   148  		id, msg)))
   149  
   150  	return terraform.HookActionContinue, nil
   151  }
   152  
   153  func (h *UiHook) PreDiff(
   154  	n *terraform.InstanceInfo,
   155  	s *terraform.InstanceState) (terraform.HookAction, error) {
   156  	return terraform.HookActionContinue, nil
   157  }
   158  
   159  func (h *UiHook) PreProvision(
   160  	n *terraform.InstanceInfo,
   161  	provId string) (terraform.HookAction, error) {
   162  	id := n.HumanId()
   163  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   164  		"[reset][bold]%s: Provisioning with '%s'...[reset_bold]",
   165  		id, provId)))
   166  	return terraform.HookActionContinue, nil
   167  }
   168  
   169  func (h *UiHook) ProvisionOutput(
   170  	n *terraform.InstanceInfo,
   171  	provId string,
   172  	msg string) {
   173  	id := n.HumanId()
   174  	var buf bytes.Buffer
   175  	buf.WriteString(h.Colorize.Color("[reset]"))
   176  
   177  	prefix := fmt.Sprintf("%s (%s): ", id, provId)
   178  	s := bufio.NewScanner(strings.NewReader(msg))
   179  	s.Split(scanLines)
   180  	for s.Scan() {
   181  		line := strings.TrimRightFunc(s.Text(), unicode.IsSpace)
   182  		if line != "" {
   183  			buf.WriteString(fmt.Sprintf("%s%s\n", prefix, line))
   184  		}
   185  	}
   186  
   187  	h.ui.Output(strings.TrimSpace(buf.String()))
   188  }
   189  
   190  func (h *UiHook) PreRefresh(
   191  	n *terraform.InstanceInfo,
   192  	s *terraform.InstanceState) (terraform.HookAction, error) {
   193  	h.once.Do(h.init)
   194  
   195  	id := n.HumanId()
   196  	h.ui.Output(h.Colorize.Color(fmt.Sprintf(
   197  		"[reset][bold]%s: Refreshing state... (ID: %s)",
   198  		id, s.ID)))
   199  	return terraform.HookActionContinue, nil
   200  }
   201  
   202  func (h *UiHook) init() {
   203  	if h.Colorize == nil {
   204  		panic("colorize not given")
   205  	}
   206  
   207  	h.resources = make(map[string]uiResourceOp)
   208  
   209  	// Wrap the ui so that it is safe for concurrency regardless of the
   210  	// underlying reader/writer that is in place.
   211  	h.ui = &cli.ConcurrentUi{Ui: h.Ui}
   212  }
   213  
   214  // scanLines is basically copied from the Go standard library except
   215  // we've modified it to also fine `\r`.
   216  func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
   217  	if atEOF && len(data) == 0 {
   218  		return 0, nil, nil
   219  	}
   220  	if i := bytes.IndexByte(data, '\n'); i >= 0 {
   221  		// We have a full newline-terminated line.
   222  		return i + 1, dropCR(data[0:i]), nil
   223  	}
   224  	if i := bytes.IndexByte(data, '\r'); i >= 0 {
   225  		// We have a full newline-terminated line.
   226  		return i + 1, dropCR(data[0:i]), nil
   227  	}
   228  	// If we're at EOF, we have a final, non-terminated line. Return it.
   229  	if atEOF {
   230  		return len(data), dropCR(data), nil
   231  	}
   232  	// Request more data.
   233  	return 0, nil, nil
   234  }
   235  
   236  // dropCR drops a terminal \r from the data.
   237  func dropCR(data []byte) []byte {
   238  	if len(data) > 0 && data[len(data)-1] == '\r' {
   239  		return data[0 : len(data)-1]
   240  	}
   241  	return data
   242  }