diff --git a/common/bootcommand/boot_command_ast.go b/common/bootcommand/boot_command_ast.go index 16b562e95..02ec5fbff 100644 --- a/common/bootcommand/boot_command_ast.go +++ b/common/bootcommand/boot_command_ast.go @@ -42,13 +42,22 @@ func (k KeyAction) String() string { } type expression interface { + // Do executes the expression Do(context.Context, BCDriver) error + // Validate validates the expression without executing it + Validate() error } type expressionSequence []expression // Do executes every expression in the sequence and then finalizes the driver. func (s expressionSequence) Do(ctx context.Context, b BCDriver) error { + // validate should never fail here, since it should be called before + // expressionSequence.Do. Only reason we don't panic is so we can clean up. + if errs := s.Validate(); errs != nil { + return fmt.Errorf("Found an invalid boot command. This is likely an error in Packer, so please open a ticket.") + } + for _, exp := range s { if err := ctx.Err(); err != nil { return err @@ -60,6 +69,16 @@ func (s expressionSequence) Do(ctx context.Context, b BCDriver) error { return b.Finalize() } +// Do executes every expression in the sequence and then finalizes the driver. +func (s expressionSequence) Validate() (errs []error) { + for _, exp := range s { + if err := exp.Validate(); err != nil { + errs = append(errs, err) + } + } + return +} + // GenerateExpressionSequence generates a sequence of expressions from the // given command. This is the primary entry point to the boot command parser. func GenerateExpressionSequence(command string) (expressionSequence, error) { @@ -67,9 +86,6 @@ func GenerateExpressionSequence(command string) (expressionSequence, error) { if err != nil { return nil, err } - if got == nil { - return nil, fmt.Errorf("No expressions found.") - } seq := expressionSequence{} for _, exp := range got.([]interface{}) { seq = append(seq, exp.(expression)) @@ -93,6 +109,14 @@ func (w *waitExpression) Do(ctx context.Context, _ BCDriver) error { } } +// Validate returns an error if the time is <= 0 +func (w *waitExpression) Validate() error { + if w.d <= 0 { + return fmt.Errorf("Expecting a positive wait value. Got %s", w.d) + } + return nil +} + func (w *waitExpression) String() string { return fmt.Sprintf("Wait<%s>", w.d) } @@ -107,6 +131,11 @@ func (s *specialExpression) Do(ctx context.Context, driver BCDriver) error { return driver.SendSpecial(s.s, s.action) } +// Validate always passes +func (s *specialExpression) Validate() error { + return nil +} + func (s *specialExpression) String() string { return fmt.Sprintf("Spec-%s(%s)", s.action, s.s) } @@ -121,6 +150,11 @@ func (l *literal) Do(ctx context.Context, driver BCDriver) error { return driver.SendKey(l.s, l.action) } +// Validate always passes +func (l *literal) Validate() error { + return nil +} + func (l *literal) String() string { return fmt.Sprintf("LIT-%s(%s)", l.action, string(l.s)) } diff --git a/common/bootcommand/boot_command_ast_test.go b/common/bootcommand/boot_command_ast_test.go index 8536f58c0..9638da42f 100644 --- a/common/bootcommand/boot_command_ast_test.go +++ b/common/bootcommand/boot_command_ast_test.go @@ -100,17 +100,41 @@ func Test_special(t *testing.T) { } } -func Test_negativeWait(t *testing.T) { - in := "" - _, err := ParseReader("", strings.NewReader(in)) - if err != nil { - log.Fatal(err) +func Test_validation(t *testing.T) { + var expressions = []struct { + in string + valid bool + }{ + { + "", + true, + }, + { + "", + false, + }, + { + "", + true, + }, + { + "<", + true, + }, } + for _, tt := range expressions { + got, err := ParseReader("", strings.NewReader(tt.in)) + if err != nil { + log.Fatal(err) + } - /* gL := toIfaceSlice(got) - for _, g := range gL { - assert.Equal(t, tt.out, g.(*specialExpression).String()) - } - */ + assert.Len(t, gL, 1) + err = gL[0].(expression).Validate() + if tt.valid { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + } } diff --git a/common/bootcommand/config.go b/common/bootcommand/config.go index 058efc2ed..ac5834701 100644 --- a/common/bootcommand/config.go +++ b/common/bootcommand/config.go @@ -33,6 +33,16 @@ func (c *BootConfig) Prepare(ctx *interpolate.Context) (errs []error) { c.BootWait = bw } } + + if c.BootCommand != nil { + expSeq, err := GenerateExpressionSequence(c.FlatBootCommand()) + if err != nil { + errs = append(errs, err) + } else if vErrs := expSeq.Validate(); vErrs != nil { + errs = append(errs, vErrs...) + } + } + return }