dockerfile: add more source information to errors

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
v0.8
Tonis Tiigi 2020-04-21 22:56:14 -07:00
parent abbda4e941
commit 4b2636acca
7 changed files with 91 additions and 58 deletions

View File

@ -352,7 +352,7 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
if isNotLocalDockerfile {
localNameDockerfile = ""
}
err = wrapSource(err, dtDockerfile, filename, localNameDockerfile, el.Ranges)
err = wrapSource(err, dtDockerfile, filename, localNameDockerfile, el.Location)
}
}()
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{

View File

@ -110,10 +110,10 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
for i, st := range stages {
name, err := shlex.ProcessWordWithMap(st.BaseName, metaArgsToMap(optMetaArgs))
if err != nil {
return nil, nil, err
return nil, nil, parser.WithLocation(err, st.Location)
}
if name == "" {
return nil, nil, errors.Errorf("base name (%s) should not be blank", st.BaseName)
return nil, nil, parser.WithLocation(errors.Errorf("base name (%s) should not be blank", st.BaseName), st.Location)
}
st.BaseName = name
@ -132,12 +132,12 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
if v := st.Platform; v != "" {
v, err := shlex.ProcessWordWithMap(v, metaArgsToMap(optMetaArgs))
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to process arguments for platform %s", v)
return nil, nil, parser.WithLocation(errors.Wrapf(err, "failed to process arguments for platform %s", v), st.Location)
}
p, err := platforms.Parse(v)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to parse platform %s", v)
return nil, nil, parser.WithLocation(errors.Wrapf(err, "failed to parse platform %s", v), st.Location)
}
ds.platform = &p
}
@ -204,7 +204,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
}
if has, state := hasCircularDependency(allDispatchStates.states); has {
return nil, nil, fmt.Errorf("circular dependency detected on stage: %s", state.stageName)
return nil, nil, errors.Errorf("circular dependency detected on stage: %s", state.stageName)
}
if len(allDispatchStates.states) == 1 {
@ -225,7 +225,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
eg.Go(func() error {
ref, err := reference.ParseNormalizedNamed(d.stage.BaseName)
if err != nil {
return errors.Wrapf(err, "failed to parse stage name %q", d.stage.BaseName)
return parser.WithLocation(errors.Wrapf(err, "failed to parse stage name %q", d.stage.BaseName), d.stage.Location)
}
platform := d.platform
if platform == nil {
@ -316,12 +316,12 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
}
if d.image.Config.WorkingDir != "" {
if err = dispatchWorkdir(d, &instructions.WorkdirCommand{Path: d.image.Config.WorkingDir}, false, nil); err != nil {
return nil, nil, err
return nil, nil, parser.WithLocation(err, d.stage.Location)
}
}
if d.image.Config.User != "" {
if err = dispatchUser(d, &instructions.UserCommand{User: d.image.Config.User}, false); err != nil {
return nil, nil, err
return nil, nil, parser.WithLocation(err, d.stage.Location)
}
}
d.state = d.state.Network(opt.ForceNetMode)
@ -346,13 +346,13 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
}
if err = dispatchOnBuildTriggers(d, d.image.Config.OnBuild, opt); err != nil {
return nil, nil, err
return nil, nil, parser.WithLocation(err, d.stage.Location)
}
d.image.Config.OnBuild = nil
for _, cmd := range d.commands {
if err := dispatch(d, cmd, opt); err != nil {
return nil, nil, err
return nil, nil, parser.WithLocation(err, cmd.Location())
}
}

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/pkg/errors"
)
@ -35,6 +36,7 @@ func (kvpo *KeyValuePairOptional) ValueString() string {
// Command is implemented by every command present in a dockerfile
type Command interface {
Name() string
Location() []parser.Range
}
// KeyValuePairs is a slice of KeyValuePair
@ -42,8 +44,9 @@ type KeyValuePairs []KeyValuePair
// withNameAndCode is the base of every command in a Dockerfile (String() returns its source code)
type withNameAndCode struct {
code string
name string
code string
name string
location []parser.Range
}
func (c *withNameAndCode) String() string {
@ -55,8 +58,13 @@ func (c *withNameAndCode) Name() string {
return c.name
}
// Location of the command in source
func (c *withNameAndCode) Location() []parser.Range {
return c.location
}
func newWithNameAndCode(req parseRequest) withNameAndCode {
return withNameAndCode{code: strings.TrimSpace(req.original), name: req.command}
return withNameAndCode{code: strings.TrimSpace(req.original), name: req.command, location: req.location}
}
// SingleWordExpander is a provider for variable expansion where 1 word => 1 output
@ -400,6 +408,7 @@ type Stage struct {
BaseName string
SourceCode string
Platform string
Location []parser.Range
}
// AddCommand to the stage

View File

@ -21,6 +21,7 @@ type parseRequest struct {
attributes map[string]bool
flags *BFlags
original string
location []parser.Range
}
var parseRunPreHooks []func(*RunCommand, parseRequest) error
@ -48,13 +49,14 @@ func newParseRequestFromNode(node *parser.Node) parseRequest {
attributes: node.Attributes,
original: node.Original,
flags: NewBFlagsWithArgs(node.Flags),
location: node.Location(),
}
}
// ParseInstruction converts an AST to a typed instruction (either a command or a build stage beginning when encountering a `FROM` statement)
func ParseInstruction(node *parser.Node) (v interface{}, err error) {
defer func() {
err = node.WrapError(err)
err = parser.WithLocation(err, node.Location())
}()
req := newParseRequestFromNode(node)
switch node.Value {
@ -108,7 +110,7 @@ func ParseCommand(node *parser.Node) (Command, error) {
if c, ok := s.(Command); ok {
return c, nil
}
return nil, node.WrapError(errors.Errorf("%T is not a command type", s))
return nil, parser.WithLocation(errors.Errorf("%T is not a command type", s), node.Location())
}
// UnknownInstruction represents an error occurring when a command is unresolvable
@ -130,7 +132,7 @@ func (e *parseError) Error() string {
return fmt.Sprintf("dockerfile parse error line %d: %v", e.node.StartLine, e.inner.Error())
}
func (e *parseError) Unwarp() error {
func (e *parseError) Unwrap() error {
return e.inner
}
@ -155,11 +157,11 @@ func Parse(ast *parser.Node) (stages []Stage, metaArgs []ArgCommand, err error)
case Command:
stage, err := CurrentStage(stages)
if err != nil {
return nil, nil, n.WrapError(err)
return nil, nil, parser.WithLocation(err, n.Location())
}
stage.AddCommand(c)
default:
return nil, nil, n.WrapError(errors.Errorf("%T is not a command type", cmd))
return nil, nil, parser.WithLocation(errors.Errorf("%T is not a command type", cmd), n.Location())
}
}
@ -282,6 +284,7 @@ func parseFrom(req parseRequest) (*Stage, error) {
SourceCode: code,
Commands: []Command{},
Platform: flPlatform.Value,
Location: req.location,
}, nil
}

View File

@ -2,26 +2,35 @@ package parser
import "github.com/pkg/errors"
// ErrorLocation gives a location in source code that caused the error
type ErrorLocation struct {
Ranges []Range
Location []Range
error
}
// Unwrap unwraps to the next error
func (e *ErrorLocation) Unwrap() error {
return e.error
}
// Range is a code section between two positions
type Range struct {
Start Position
End Position
}
// Position is a point in source code
type Position struct {
Line int
Character int
}
func withLocation(err error, startLine, endLine int) error {
func withLocation(err error, start, end int) error {
return WithLocation(err, toRanges(start, end))
}
// WithLocation extends an error with a source code location
func WithLocation(err error, location []Range) error {
if err == nil {
return nil
}
@ -29,10 +38,14 @@ func withLocation(err error, startLine, endLine int) error {
if errors.As(err, &el) {
return err
}
return errors.WithStack(&ErrorLocation{
error: err,
Ranges: toRanges(startLine, endLine),
})
var err1 error = &ErrorLocation{
error: err,
Location: location,
}
if !hasLocalStackTrace(err) {
err1 = errors.WithStack(err1)
}
return err1
}
func toRanges(start, end int) (r []Range) {
@ -44,3 +57,17 @@ func toRanges(start, end int) (r []Range) {
}
return
}
func hasLocalStackTrace(err error) bool {
wrapped, ok := err.(interface {
Unwrap() error
})
if ok && hasLocalStackTrace(wrapped.Unwrap()) {
return true
}
_, ok = err.(interface {
StackTrace() errors.StackTrace
})
return ok
}

View File

@ -38,6 +38,11 @@ type Node struct {
EndLine int // the line in the original dockerfile where the node ends
}
// Location return the location of node in source code
func (node *Node) Location() []Range {
return toRanges(node.StartLine, node.EndLine)
}
// Dump dumps the AST defined by `node` as a list of sexps.
// Returns a string suitable for printing.
func (node *Node) Dump() string {
@ -63,11 +68,6 @@ func (node *Node) Dump() string {
return strings.TrimSpace(str)
}
// WrapError returns new error that implements ErrorLocation
func (node *Node) WrapError(err error) error {
return withLocation(err, node.StartLine, node.EndLine)
}
func (node *Node) lines(start, end int) {
node.StartLine = start
node.EndLine = end

View File

@ -5,7 +5,6 @@ package errdefs
import (
fmt "fmt"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/golang/protobuf/proto"
math "math"
)
@ -355,31 +354,26 @@ func init() {
func init() { proto.RegisterFile("errdefs.proto", fileDescriptor_689dc58a5060aff5) }
var fileDescriptor_689dc58a5060aff5 = []byte{
// 405 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0xcd, 0x8a, 0xd4, 0x40,
0x10, 0x36, 0x66, 0x3a, 0xbb, 0xa9, 0x55, 0xd1, 0xc6, 0x43, 0x58, 0xc4, 0x0d, 0x11, 0x74, 0x10,
0x37, 0x03, 0xe3, 0xd5, 0xd3, 0xae, 0xec, 0x41, 0x44, 0xa4, 0x57, 0xbd, 0xf7, 0x24, 0x95, 0x6c,
0x63, 0xd2, 0x3d, 0x74, 0x7a, 0x06, 0x0f, 0xbe, 0x84, 0x6f, 0xe5, 0x23, 0x88, 0x87, 0x39, 0xf8,
0x24, 0xd2, 0xd5, 0x99, 0x8c, 0xe0, 0x9e, 0x52, 0xdf, 0x4f, 0xd7, 0x57, 0x55, 0x04, 0xee, 0xa3,
0xb5, 0x35, 0x36, 0x43, 0xb9, 0xb6, 0xc6, 0x19, 0x7e, 0x34, 0xc2, 0xd3, 0xf3, 0x56, 0xb9, 0x9b,
0xcd, 0xaa, 0xac, 0x4c, 0xbf, 0x68, 0x4d, 0x6b, 0x16, 0xa4, 0xaf, 0x36, 0x0d, 0x21, 0x02, 0x54,
0x85, 0x77, 0xc5, 0x8f, 0x08, 0xd8, 0xb5, 0x93, 0xd5, 0x57, 0xfe, 0x1c, 0x92, 0xc6, 0xca, 0x1e,
0x87, 0x2c, 0xca, 0xe3, 0xf9, 0xc9, 0xf2, 0x41, 0xb9, 0x4f, 0xb8, 0xf2, 0xb4, 0x18, 0x55, 0x9e,
0xc1, 0x51, 0xd5, 0xd7, 0x9d, 0xd2, 0x98, 0xdd, 0xcd, 0xe3, 0x79, 0x2a, 0xf6, 0x90, 0x3f, 0x84,
0x78, 0xad, 0xea, 0x2c, 0xce, 0xa3, 0x39, 0x13, 0xbe, 0xf4, 0xde, 0x2d, 0xda, 0x41, 0x19, 0x9d,
0xcd, 0xf2, 0xc8, 0x7b, 0x47, 0xc8, 0x4f, 0xe1, 0xd8, 0xe2, 0x56, 0x91, 0xc4, 0x48, 0x9a, 0x70,
0x71, 0x09, 0x8c, 0x22, 0x39, 0x87, 0xd9, 0x07, 0xd9, 0x63, 0x16, 0x91, 0x81, 0x6a, 0xcf, 0x5d,
0xa9, 0xce, 0x67, 0x13, 0xe7, 0x6b, 0xcf, 0xbd, 0xf7, 0xf3, 0x84, 0x64, 0xaa, 0x8b, 0x4f, 0x90,
0x7c, 0x41, 0xeb, 0xf0, 0x1b, 0x7f, 0x07, 0x49, 0xad, 0x5a, 0x1c, 0x5c, 0xe8, 0x73, 0xb1, 0xfc,
0xb9, 0x3b, 0xbb, 0xf3, 0x7b, 0x77, 0xf6, 0xf2, 0x9f, 0x4b, 0x99, 0x35, 0xea, 0xca, 0x68, 0x27,
0x95, 0x46, 0x3b, 0x2c, 0x5a, 0x73, 0x1e, 0x9e, 0x94, 0x6f, 0xe9, 0x23, 0xc6, 0x0e, 0xc5, 0x77,
0x48, 0xae, 0xcd, 0xc6, 0x56, 0xc8, 0x5f, 0x41, 0xda, 0x99, 0x4a, 0x3a, 0x65, 0xf4, 0xff, 0x17,
0x13, 0x52, 0xb7, 0x28, 0x0e, 0x06, 0xbf, 0x6e, 0xa3, 0x3a, 0xd4, 0x7e, 0x9b, 0x30, 0xf9, 0x84,
0xf9, 0x63, 0x60, 0xde, 0xd8, 0xd1, 0xf8, 0xa9, 0x08, 0xc0, 0xef, 0x54, 0x4b, 0x27, 0xe9, 0x6e,
0xf7, 0x04, 0xd5, 0xc5, 0x67, 0x60, 0xd4, 0x99, 0xbf, 0x00, 0x36, 0x38, 0x69, 0xc3, 0x46, 0x27,
0xcb, 0x47, 0x53, 0xf0, 0x47, 0x33, 0x28, 0x9f, 0x28, 0x82, 0xce, 0x9f, 0x41, 0x8c, 0xba, 0xa6,
0xc8, 0x5b, 0x6d, 0x5e, 0x2d, 0xde, 0xc0, 0xf1, 0x9e, 0x98, 0x4e, 0x19, 0x1d, 0x4e, 0xc9, 0x9f,
0x40, 0x7a, 0x79, 0x23, 0xad, 0xac, 0x1c, 0x5a, 0x6a, 0xc5, 0xc4, 0x81, 0xb8, 0x98, 0xfd, 0xfa,
0xf3, 0x34, 0x5a, 0x25, 0xf4, 0x3b, 0xbd, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x78, 0xd9, 0x55,
0x34, 0x97, 0x02, 0x00, 0x00,
// 330 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x4f, 0x4b, 0xfb, 0x40,
0x10, 0x25, 0xbf, 0x74, 0xd3, 0x66, 0xfa, 0x53, 0x74, 0x10, 0x09, 0xe2, 0x21, 0x44, 0xd0, 0x1e,
0xa4, 0x87, 0x7a, 0xf5, 0x56, 0xe8, 0x49, 0x44, 0xb6, 0xe8, 0x7d, 0x4d, 0xa6, 0x75, 0x31, 0x4d,
0xca, 0x66, 0x2d, 0x1e, 0xfc, 0x12, 0x7e, 0x63, 0x99, 0x49, 0x9a, 0x1e, 0xf4, 0x36, 0xef, 0x4f,
0xe6, 0xbd, 0x0c, 0x0b, 0x47, 0xe4, 0x5c, 0x41, 0xab, 0x66, 0xba, 0x75, 0xb5, 0xaf, 0x71, 0xd8,
0xc1, 0xec, 0x3b, 0x00, 0xb5, 0xf4, 0x26, 0x7f, 0xc7, 0x6b, 0x88, 0x56, 0xce, 0x6c, 0xa8, 0x49,
0x82, 0x34, 0x9c, 0x8c, 0x67, 0xc7, 0xd3, 0xfd, 0x27, 0x0b, 0xa6, 0x75, 0xa7, 0x62, 0x02, 0xc3,
0x7c, 0x53, 0x94, 0xb6, 0xa2, 0xe4, 0x5f, 0x1a, 0x4e, 0x62, 0xbd, 0x87, 0x78, 0x02, 0xe1, 0xd6,
0x16, 0x49, 0x98, 0x06, 0x13, 0xa5, 0x79, 0x64, 0xef, 0x8e, 0x5c, 0x63, 0xeb, 0x2a, 0x19, 0xa4,
0x01, 0x7b, 0x3b, 0x88, 0x17, 0x30, 0x72, 0xb4, 0xb3, 0x22, 0x29, 0x91, 0x7a, 0x9c, 0xcd, 0x41,
0x49, 0x24, 0x22, 0x0c, 0x1e, 0xcd, 0x86, 0x92, 0x40, 0x0c, 0x32, 0x33, 0xb7, 0xb0, 0x25, 0x67,
0x0b, 0xc7, 0x33, 0x73, 0x0f, 0xdc, 0xa7, 0x4d, 0x96, 0x39, 0x4b, 0x21, 0x7a, 0x21, 0xe7, 0xe9,
0x13, 0xcf, 0x21, 0x2a, 0xec, 0x9a, 0x1a, 0xdf, 0xed, 0xe9, 0x50, 0xf6, 0x05, 0xd1, 0xb2, 0xfe,
0x70, 0x39, 0xe1, 0x2d, 0xc4, 0x65, 0x9d, 0x1b, 0x6f, 0xeb, 0xea, 0xf7, 0xdf, 0x6b, 0x53, 0xad,
0x49, 0x1f, 0x0c, 0x5c, 0x7d, 0x65, 0x4b, 0xaa, 0xb8, 0x59, 0xdb, 0xa2, 0xc7, 0x78, 0x06, 0x8a,
0x8d, 0xa5, 0x54, 0x89, 0x75, 0x0b, 0xb8, 0x5f, 0x61, 0xbc, 0x91, 0x1b, 0xfc, 0xd7, 0x32, 0x67,
0xcf, 0xa0, 0x64, 0x33, 0xde, 0x80, 0x6a, 0xbc, 0x71, 0x6d, 0xbb, 0xf1, 0xec, 0xb4, 0x0f, 0x7e,
0xaa, 0x1b, 0xcb, 0x89, 0xba, 0xd5, 0xf1, 0x0a, 0x42, 0xaa, 0x0a, 0x89, 0xfc, 0xd3, 0xc6, 0x6a,
0x76, 0x0f, 0xa3, 0x3d, 0xd1, 0x9f, 0x25, 0x38, 0x9c, 0x05, 0x2f, 0x21, 0x9e, 0xbf, 0x19, 0x67,
0x72, 0x4f, 0x4e, 0x56, 0x29, 0x7d, 0x20, 0x5e, 0x23, 0x79, 0x1d, 0x77, 0x3f, 0x01, 0x00, 0x00,
0xff, 0xff, 0x52, 0xed, 0x4a, 0x2a, 0x2e, 0x02, 0x00, 0x00,
}