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