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 }