177 lines
3.9 KiB
Go
177 lines
3.9 KiB
Go
|
package command
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/hashicorp/packer/packer"
|
||
|
"github.com/hashicorp/packer/template"
|
||
|
"github.com/hashicorp/packer/template/interpolate"
|
||
|
"github.com/posener/complete"
|
||
|
)
|
||
|
|
||
|
const TiniestBuilder = `{
|
||
|
"builders": [
|
||
|
{
|
||
|
"type":"null",
|
||
|
"communicator": "none"
|
||
|
}
|
||
|
]
|
||
|
}`
|
||
|
|
||
|
type ConsoleCommand struct {
|
||
|
Meta
|
||
|
}
|
||
|
|
||
|
func (c *ConsoleCommand) Run(args []string) int {
|
||
|
flags := c.Meta.FlagSet("console", FlagSetVars)
|
||
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||
|
if err := flags.Parse(args); err != nil {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
var templ *template.Template
|
||
|
|
||
|
args = flags.Args()
|
||
|
if len(args) < 1 {
|
||
|
// If user has not definied a builder, create a tiny null placeholder
|
||
|
// builder so that we can properly initialize the core
|
||
|
tpl, err := template.Parse(strings.NewReader(TiniestBuilder))
|
||
|
if err != nil {
|
||
|
c.Ui.Error(fmt.Sprintf("Failed to generate placeholder template: %s", err))
|
||
|
return 1
|
||
|
}
|
||
|
templ = tpl
|
||
|
} else if len(args) == 1 {
|
||
|
// Parse the provided template
|
||
|
tpl, err := template.ParseFile(args[0])
|
||
|
if err != nil {
|
||
|
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||
|
return 1
|
||
|
}
|
||
|
templ = tpl
|
||
|
} else {
|
||
|
// User provided too many arguments
|
||
|
flags.Usage()
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// Get the core
|
||
|
core, err := c.Meta.Core(templ)
|
||
|
if err != nil {
|
||
|
c.Ui.Error(err.Error())
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// IO Loop
|
||
|
session := &REPLSession{
|
||
|
Core: core,
|
||
|
}
|
||
|
|
||
|
return c.modeInteractive(session)
|
||
|
}
|
||
|
|
||
|
func (*ConsoleCommand) Help() string {
|
||
|
helpText := `
|
||
|
Usage: packer console [options] [TEMPLATE]
|
||
|
|
||
|
Creates a console for testing variable interpolation.
|
||
|
If a template is provided, this command will load the template and any
|
||
|
variables defined therein into its context to be referenced during
|
||
|
interpolation.
|
||
|
|
||
|
Options:
|
||
|
-var 'key=value' Variable for templates, can be used multiple times.
|
||
|
-var-file=path JSON file containing user variables.
|
||
|
`
|
||
|
|
||
|
return strings.TrimSpace(helpText)
|
||
|
}
|
||
|
|
||
|
func (*ConsoleCommand) Synopsis() string {
|
||
|
return "check that a template is valid"
|
||
|
}
|
||
|
|
||
|
func (*ConsoleCommand) AutocompleteArgs() complete.Predictor {
|
||
|
return complete.PredictNothing
|
||
|
}
|
||
|
|
||
|
func (*ConsoleCommand) AutocompleteFlags() complete.Flags {
|
||
|
return complete.Flags{
|
||
|
"-var": complete.PredictNothing,
|
||
|
"-var-file": complete.PredictNothing,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *ConsoleCommand) modeInteractive(session *REPLSession) int {
|
||
|
for {
|
||
|
// Read a line
|
||
|
line, err := c.Ui.Ask("> ")
|
||
|
if err == packer.ErrInterrupted {
|
||
|
break
|
||
|
} else if err == io.EOF {
|
||
|
break
|
||
|
}
|
||
|
out, err := session.Handle(line)
|
||
|
if err == ErrSessionExit {
|
||
|
break
|
||
|
}
|
||
|
if err != nil {
|
||
|
c.Ui.Error(err.Error())
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
c.Ui.Say(out)
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// ErrSessionExit is a special error result that should be checked for
|
||
|
// from Handle to signal a graceful exit.
|
||
|
var ErrSessionExit = errors.New("Session exit")
|
||
|
|
||
|
// Session represents the state for a single REPL session.
|
||
|
type REPLSession struct {
|
||
|
// Core is used for constructing interpolations based off packer templates
|
||
|
Core *packer.Core
|
||
|
}
|
||
|
|
||
|
// Handle handles a single line of input from the REPL.
|
||
|
//
|
||
|
// The return value is the output and the error to show.
|
||
|
func (s *REPLSession) Handle(line string) (string, error) {
|
||
|
switch {
|
||
|
case strings.TrimSpace(line) == "exit":
|
||
|
return "", ErrSessionExit
|
||
|
case strings.TrimSpace(line) == "help":
|
||
|
return s.handleHelp()
|
||
|
default:
|
||
|
return s.handleEval(line)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *REPLSession) handleEval(line string) (string, error) {
|
||
|
ctx := s.Core.Context()
|
||
|
rendered, err := interpolate.Render(line, ctx)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Error interpolating: %s", err)
|
||
|
}
|
||
|
return rendered, nil
|
||
|
}
|
||
|
|
||
|
func (s *REPLSession) handleHelp() (string, error) {
|
||
|
text := `
|
||
|
The Packer console allows you to experiment with Packer interpolations.
|
||
|
You may access variables in the Packer config you called the console with.
|
||
|
|
||
|
Type in the interpolation to test and hit <enter> to see the result.
|
||
|
|
||
|
To exit the console, type "exit" and hit <enter>, or use Control-C.
|
||
|
`
|
||
|
|
||
|
return strings.TrimSpace(text), nil
|
||
|
}
|