github.com/hugorut/terraform@v1.1.3/src/command/views/hook_json.go (about)

     1  package views
     2  
     3  import (
     4  	"bufio"
     5  	"strings"
     6  	"sync"
     7  	"time"
     8  	"unicode"
     9  
    10  	"github.com/hugorut/terraform/src/addrs"
    11  	"github.com/hugorut/terraform/src/command/format"
    12  	"github.com/hugorut/terraform/src/command/views/json"
    13  	"github.com/hugorut/terraform/src/plans"
    14  	"github.com/hugorut/terraform/src/states"
    15  	"github.com/hugorut/terraform/src/terraform"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  // How long to wait between sending heartbeat/progress messages
    20  const heartbeatInterval = 10 * time.Second
    21  
    22  func newJSONHook(view *JSONView) *jsonHook {
    23  	return &jsonHook{
    24  		view:      view,
    25  		applying:  make(map[string]applyProgress),
    26  		timeNow:   time.Now,
    27  		timeAfter: time.After,
    28  	}
    29  }
    30  
    31  type jsonHook struct {
    32  	terraform.NilHook
    33  
    34  	view *JSONView
    35  
    36  	// Concurrent map of resource addresses to allow the sequence of pre-apply,
    37  	// progress, and post-apply messages to share data about the resource
    38  	applying     map[string]applyProgress
    39  	applyingLock sync.Mutex
    40  
    41  	// Mockable functions for testing the progress timer goroutine
    42  	timeNow   func() time.Time
    43  	timeAfter func(time.Duration) <-chan time.Time
    44  }
    45  
    46  var _ terraform.Hook = (*jsonHook)(nil)
    47  
    48  type applyProgress struct {
    49  	addr   addrs.AbsResourceInstance
    50  	action plans.Action
    51  	start  time.Time
    52  
    53  	// done is used for post-apply to stop the progress goroutine
    54  	done chan struct{}
    55  
    56  	// heartbeatDone is used to allow tests to safely wait for the progress
    57  	// goroutine to finish
    58  	heartbeatDone chan struct{}
    59  }
    60  
    61  func (h *jsonHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
    62  	idKey, idValue := format.ObjectValueIDOrName(priorState)
    63  	h.view.Hook(json.NewApplyStart(addr, action, idKey, idValue))
    64  
    65  	progress := applyProgress{
    66  		addr:          addr,
    67  		action:        action,
    68  		start:         h.timeNow().Round(time.Second),
    69  		done:          make(chan struct{}),
    70  		heartbeatDone: make(chan struct{}),
    71  	}
    72  	h.applyingLock.Lock()
    73  	h.applying[addr.String()] = progress
    74  	h.applyingLock.Unlock()
    75  
    76  	go h.applyingHeartbeat(progress)
    77  	return terraform.HookActionContinue, nil
    78  }
    79  
    80  func (h *jsonHook) applyingHeartbeat(progress applyProgress) {
    81  	defer close(progress.heartbeatDone)
    82  	for {
    83  		select {
    84  		case <-progress.done:
    85  			return
    86  		case <-h.timeAfter(heartbeatInterval):
    87  		}
    88  
    89  		elapsed := h.timeNow().Round(time.Second).Sub(progress.start)
    90  		h.view.Hook(json.NewApplyProgress(progress.addr, progress.action, elapsed))
    91  	}
    92  }
    93  
    94  func (h *jsonHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, err error) (terraform.HookAction, error) {
    95  	key := addr.String()
    96  	h.applyingLock.Lock()
    97  	progress := h.applying[key]
    98  	if progress.done != nil {
    99  		close(progress.done)
   100  	}
   101  	delete(h.applying, key)
   102  	h.applyingLock.Unlock()
   103  
   104  	elapsed := h.timeNow().Round(time.Second).Sub(progress.start)
   105  
   106  	if err != nil {
   107  		// Errors are collected and displayed post-apply, so no need to
   108  		// re-render them here. Instead just signal that this resource failed
   109  		// to apply.
   110  		h.view.Hook(json.NewApplyErrored(addr, progress.action, elapsed))
   111  	} else {
   112  		idKey, idValue := format.ObjectValueID(newState)
   113  		h.view.Hook(json.NewApplyComplete(addr, progress.action, idKey, idValue, elapsed))
   114  	}
   115  	return terraform.HookActionContinue, nil
   116  }
   117  
   118  func (h *jsonHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (terraform.HookAction, error) {
   119  	h.view.Hook(json.NewProvisionStart(addr, typeName))
   120  	return terraform.HookActionContinue, nil
   121  }
   122  
   123  func (h *jsonHook) PostProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string, err error) (terraform.HookAction, error) {
   124  	if err != nil {
   125  		// Errors are collected and displayed post-apply, so no need to
   126  		// re-render them here. Instead just signal that this provisioner step
   127  		// failed.
   128  		h.view.Hook(json.NewProvisionErrored(addr, typeName))
   129  	} else {
   130  		h.view.Hook(json.NewProvisionComplete(addr, typeName))
   131  	}
   132  	return terraform.HookActionContinue, nil
   133  }
   134  
   135  func (h *jsonHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, msg string) {
   136  	s := bufio.NewScanner(strings.NewReader(msg))
   137  	s.Split(scanLines)
   138  	for s.Scan() {
   139  		line := strings.TrimRightFunc(s.Text(), unicode.IsSpace)
   140  		if line != "" {
   141  			h.view.Hook(json.NewProvisionProgress(addr, typeName, line))
   142  		}
   143  	}
   144  }
   145  
   146  func (h *jsonHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (terraform.HookAction, error) {
   147  	idKey, idValue := format.ObjectValueID(priorState)
   148  	h.view.Hook(json.NewRefreshStart(addr, idKey, idValue))
   149  	return terraform.HookActionContinue, nil
   150  }
   151  
   152  func (h *jsonHook) PostRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value, newState cty.Value) (terraform.HookAction, error) {
   153  	idKey, idValue := format.ObjectValueID(newState)
   154  	h.view.Hook(json.NewRefreshComplete(addr, idKey, idValue))
   155  	return terraform.HookActionContinue, nil
   156  }