github.com/HashDataInc/packer@v1.3.2/helper/multistep/debug_runner.go (about)

     1  package multistep
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"sync"
     8  )
     9  
    10  // DebugLocation is the location where the pause is occurring when debugging
    11  // a step sequence. "DebugLocationAfterRun" is after the run of the named
    12  // step. "DebugLocationBeforeCleanup" is before the cleanup of the named
    13  // step.
    14  type DebugLocation uint
    15  
    16  const (
    17  	DebugLocationAfterRun DebugLocation = iota
    18  	DebugLocationBeforeCleanup
    19  )
    20  
    21  // StepWrapper is an interface that wrapped steps can implement to expose their
    22  // inner step names to the debug runner.
    23  type StepWrapper interface {
    24  	// InnerStepName should return the human readable name of the wrapped step.
    25  	InnerStepName() string
    26  }
    27  
    28  // DebugPauseFn is the type signature for the function that is called
    29  // whenever the DebugRunner pauses. It allows the caller time to
    30  // inspect the state of the multi-step sequence at a given step.
    31  type DebugPauseFn func(DebugLocation, string, StateBag)
    32  
    33  // DebugRunner is a Runner that runs the given set of steps in order,
    34  // but pauses between each step until it is told to continue.
    35  type DebugRunner struct {
    36  	// Steps is the steps to run. These will be run in order.
    37  	Steps []Step
    38  
    39  	// PauseFn is the function that is called whenever the debug runner
    40  	// pauses. The debug runner continues when this function returns.
    41  	// The function is given the state so that the state can be inspected.
    42  	PauseFn DebugPauseFn
    43  
    44  	l      sync.Mutex
    45  	runner *BasicRunner
    46  }
    47  
    48  func (r *DebugRunner) Run(state StateBag) {
    49  	r.l.Lock()
    50  	if r.runner != nil {
    51  		panic("already running")
    52  	}
    53  	r.runner = new(BasicRunner)
    54  	r.l.Unlock()
    55  
    56  	pauseFn := r.PauseFn
    57  
    58  	// If no PauseFn is specified, use the default
    59  	if pauseFn == nil {
    60  		pauseFn = DebugPauseDefault
    61  	}
    62  
    63  	// Rebuild the steps so that we insert the pause step after each
    64  	steps := make([]Step, len(r.Steps)*2)
    65  	for i, step := range r.Steps {
    66  		steps[i*2] = step
    67  		name := ""
    68  		if wrapped, ok := step.(StepWrapper); ok {
    69  			name = wrapped.InnerStepName()
    70  		} else {
    71  			name = reflect.Indirect(reflect.ValueOf(step)).Type().Name()
    72  		}
    73  		steps[(i*2)+1] = &debugStepPause{
    74  			name,
    75  			pauseFn,
    76  		}
    77  	}
    78  
    79  	// Then just use a basic runner to run it
    80  	r.runner.Steps = steps
    81  	r.runner.Run(state)
    82  }
    83  
    84  func (r *DebugRunner) Cancel() {
    85  	r.l.Lock()
    86  	defer r.l.Unlock()
    87  
    88  	if r.runner != nil {
    89  		r.runner.Cancel()
    90  	}
    91  }
    92  
    93  // DebugPauseDefault is the default pause function when using the
    94  // DebugRunner if no PauseFn is specified. It outputs some information
    95  // to stderr about the step and waits for keyboard input on stdin before
    96  // continuing.
    97  func DebugPauseDefault(loc DebugLocation, name string, state StateBag) {
    98  	var locationString string
    99  	switch loc {
   100  	case DebugLocationAfterRun:
   101  		locationString = "after run of"
   102  	case DebugLocationBeforeCleanup:
   103  		locationString = "before cleanup of"
   104  	}
   105  
   106  	fmt.Printf("Pausing %s step '%s'. Press any key to continue.\n", locationString, name)
   107  
   108  	var line string
   109  	fmt.Scanln(&line)
   110  }
   111  
   112  type debugStepPause struct {
   113  	StepName string
   114  	PauseFn  DebugPauseFn
   115  }
   116  
   117  func (s *debugStepPause) Run(_ context.Context, state StateBag) StepAction {
   118  	s.PauseFn(DebugLocationAfterRun, s.StepName, state)
   119  	return ActionContinue
   120  }
   121  
   122  func (s *debugStepPause) Cleanup(state StateBag) {
   123  	s.PauseFn(DebugLocationBeforeCleanup, s.StepName, state)
   124  }