github.com/hashicorp/packer@v1.14.3/command/console.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package command
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"strings"
    12  
    13  	"github.com/chzyer/readline"
    14  	"github.com/hashicorp/packer/helper/wrappedreadline"
    15  	"github.com/hashicorp/packer/helper/wrappedstreams"
    16  	"github.com/hashicorp/packer/packer"
    17  	"github.com/posener/complete"
    18  )
    19  
    20  var TiniestBuilder = strings.NewReader(`{
    21  	"builders": [
    22  		{
    23  			"type":"null",
    24  			"communicator": "none"
    25  		}
    26  	]
    27  }`)
    28  
    29  type ConsoleCommand struct {
    30  	Meta
    31  }
    32  
    33  func (c *ConsoleCommand) Run(args []string) int {
    34  	ctx := context.Background()
    35  
    36  	cfg, ret := c.ParseArgs(args)
    37  	if ret != 0 {
    38  		return ret
    39  	}
    40  
    41  	return c.RunContext(ctx, cfg)
    42  }
    43  
    44  func (c *ConsoleCommand) ParseArgs(args []string) (*ConsoleArgs, int) {
    45  	var cfg ConsoleArgs
    46  	flags := c.Meta.FlagSet("console")
    47  	flags.Usage = func() { c.Ui.Say(c.Help()) }
    48  	cfg.AddFlagSets(flags)
    49  	cfg.MetaArgs.AddFlagSets(flags)
    50  	if err := flags.Parse(args); err != nil {
    51  		return &cfg, 1
    52  	}
    53  
    54  	args = flags.Args()
    55  	if len(args) == 1 {
    56  		cfg.Path = args[0]
    57  	}
    58  	return &cfg, 0
    59  }
    60  
    61  func (c *ConsoleCommand) RunContext(ctx context.Context, cla *ConsoleArgs) int {
    62  	packerStarter, ret := c.GetConfig(&cla.MetaArgs)
    63  	if ret != 0 {
    64  		return ret
    65  	}
    66  
    67  	_ = packerStarter.Initialize(packer.InitializeOptions{
    68  		UseSequential: cla.UseSequential,
    69  	})
    70  
    71  	// Determine if stdin is a pipe. If so, we evaluate directly.
    72  	if c.StdinPiped() {
    73  		return c.modePiped(packerStarter)
    74  	}
    75  
    76  	return c.modeInteractive(packerStarter)
    77  }
    78  
    79  func (*ConsoleCommand) Help() string {
    80  	helpText := `
    81  Usage: packer console [options] [TEMPLATE]
    82  
    83    Creates a console for testing variable interpolation.
    84    If a template is provided, this command will load the template and any
    85    variables defined therein into its context to be referenced during
    86    interpolation.
    87  
    88  Options:
    89    -var 'key=value'              Variable for templates, can be used multiple times.
    90    -var-file=path                JSON or HCL2 file containing user variables.
    91    -config-type                  Set to 'hcl2' to run in HCL2 mode when no file is passed. Defaults to json.
    92    -use-sequential-evaluation    Fallback to using a sequential approach for local/datasource evaluation.
    93  `
    94  
    95  	return strings.TrimSpace(helpText)
    96  }
    97  
    98  func (*ConsoleCommand) Synopsis() string {
    99  	return "creates a console for testing variable interpolation"
   100  }
   101  
   102  func (*ConsoleCommand) AutocompleteArgs() complete.Predictor {
   103  	return complete.PredictNothing
   104  }
   105  
   106  func (*ConsoleCommand) AutocompleteFlags() complete.Flags {
   107  	return complete.Flags{
   108  		"-var":      complete.PredictNothing,
   109  		"-var-file": complete.PredictNothing,
   110  	}
   111  }
   112  
   113  func (c *ConsoleCommand) modePiped(cfg packer.Evaluator) int {
   114  	var lastResult string
   115  	scanner := bufio.NewScanner(wrappedstreams.Stdin())
   116  	ret := 0
   117  	for scanner.Scan() {
   118  		result, _, diags := cfg.EvaluateExpression(strings.TrimSpace(scanner.Text()))
   119  		if len(diags) > 0 {
   120  			ret = writeDiags(c.Ui, nil, diags)
   121  		}
   122  		// Store the last result
   123  		lastResult = result
   124  	}
   125  
   126  	// Output the final result
   127  	c.Ui.Message(lastResult)
   128  	return ret
   129  }
   130  
   131  func (c *ConsoleCommand) modeInteractive(cfg packer.Evaluator) int {
   132  	// Setup the UI so we can output directly to stdout
   133  	l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{
   134  		Prompt:            "> ",
   135  		InterruptPrompt:   "^C",
   136  		EOFPrompt:         "exit",
   137  		HistorySearchFold: true,
   138  	}))
   139  	if err != nil {
   140  		c.Ui.Error(fmt.Sprintf(
   141  			"Error initializing console: %s",
   142  			err))
   143  		return 1
   144  	}
   145  	for {
   146  		// Read a line
   147  		line, err := l.Readline()
   148  		if err == readline.ErrInterrupt {
   149  			if len(line) == 0 {
   150  				break
   151  			} else {
   152  				continue
   153  			}
   154  		} else if err == io.EOF {
   155  			break
   156  		}
   157  		out, exit, diags := cfg.EvaluateExpression(line)
   158  		ret := writeDiags(c.Ui, nil, diags)
   159  		if exit {
   160  			return ret
   161  		}
   162  		c.Ui.Say(out)
   163  		if exit {
   164  			return ret
   165  		}
   166  	}
   167  
   168  	return 0
   169  }