implement a packer console analogous to the terraform console
This commit is contained in:
parent
2620e18247
commit
b8ac1a800d
|
@ -0,0 +1,176 @@
|
|||
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
|
||||
}
|
|
@ -22,6 +22,11 @@ func init() {
|
|||
Meta: *CommandMeta,
|
||||
}, nil
|
||||
},
|
||||
"console": func() (cli.Command, error) {
|
||||
return &command.ConsoleCommand{
|
||||
Meta: *CommandMeta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"fix": func() (cli.Command, error) {
|
||||
return &command.FixCommand{
|
||||
|
|
|
@ -97,7 +97,6 @@ func NewCore(c *CoreConfig) (*Core, error) {
|
|||
|
||||
result.builds[v] = b
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ import (
|
|||
getter "github.com/hashicorp/go-getter"
|
||||
)
|
||||
|
||||
var ErrInterrupted = errors.New("interrupted")
|
||||
|
||||
type UiColor uint
|
||||
|
||||
const (
|
||||
|
@ -190,7 +192,7 @@ func (rw *BasicUi) Ask(query string) (string, error) {
|
|||
defer rw.l.Unlock()
|
||||
|
||||
if rw.interrupted {
|
||||
return "", errors.New("interrupted")
|
||||
return "", ErrInterrupted
|
||||
}
|
||||
|
||||
if rw.TTY == nil {
|
||||
|
@ -228,7 +230,7 @@ func (rw *BasicUi) Ask(query string) (string, error) {
|
|||
// Mark that we were interrupted so future Ask calls fail.
|
||||
rw.interrupted = true
|
||||
|
||||
return "", errors.New("interrupted")
|
||||
return "", ErrInterrupted
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue