Repository: openllb/hlb
Pull request #154 (2020-05-21 02:09:58)

Add source mapping to codegen'd LLB
- Source maps runtime LLB errors back to HLB (incl. a stacktrace)
```sh
 => ERROR copy /foo /                                                                                                                                                 0.0s
------
 > copy /foo /:
------
foo.hlb:2
--------------------
   1 |     fs func1() {
   2 | >>> 	func2
   3 |     }
   4 |
--------------------
foo.hlb:6
--------------------
   4 |
   5 |     fs func2() {
   6 | >>> 	func3
   7 |     }
   8 |
--------------------
foo.hlb:11
--------------------
   9 |     fs func3(){
  10 |     	scratch
  11 | >>> 	copy fs {
  12 |     		image "alpine"
  13 |     	} "/foo" "/"
--------------------
failed to solve: rpc error: code = Unknown desc = failed to compute cache key: "/foo" not found: not found
```

## TODO
- [x] Waiting for BuildKit PR to merge: https://github.com/moby/buildkit/pull/1494
- [x] Need to fix source mapping for relative and remote imports 
- [x] Waiting for side effects PR to merge: https://github.com/openllb/hlb/pull/164


Patch:
diff --git a/cmd/hlb/command/run.go b/cmd/hlb/command/run.go
index 7ff131f..044cf06 100644
--- a/cmd/hlb/command/run.go
+++ b/cmd/hlb/command/run.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/mattn/go-isatty"
 	"github.com/moby/buildkit/client"
+	"github.com/moby/buildkit/solver/errdefs"
 	"github.com/moby/buildkit/util/appcontext"
 	"github.com/openllb/hlb"
 	"github.com/openllb/hlb/codegen"
@@ -67,6 +68,7 @@ var runCommand = &cli.Command{
 			Targets:   c.StringSlice("target"),
 			LLB:       c.Bool("llb"),
 			LogOutput: c.String("log-output"),
+			ErrOutput: os.Stderr,
 			Output:    os.Stdout,
 		})
 	},
@@ -78,6 +80,7 @@ type RunOptions struct {
 	Targets   []string
 	LLB       bool
 	LogOutput string
+	ErrOutput io.Writer
 	Output    io.Writer
 
 	// override defaults sources as necessary
@@ -205,7 +208,14 @@ func Run(ctx context.Context, cln *client.Client, rc io.ReadCloser, opts RunOpti
 		return solveReq.Solve(ctx, cln, p.MultiWriter())
 	})
 
-	return p.Wait()
+	err = p.Wait()
+	if err != nil {
+		for _, source := range errdefs.Sources(err) {
+			source.Print(opts.ErrOutput)
+		}
+		return err
+	}
+	return nil
 }
 
 func ModuleReadCloser(args []string) (io.ReadCloser, error) {
diff --git a/codegen/bind.go b/codegen/bind.go
index b40c4c4..98dc675 100644
--- a/codegen/bind.go
+++ b/codegen/bind.go
@@ -37,7 +37,7 @@ func (cg *CodeGen) setBindingValue(b parser.Binding, value interface{}) error {
 	}
 }
 
-func (cg *CodeGen) EmitBinding(ctx context.Context, scope *parser.Scope, b parser.Binding, args []*parser.Expr, chainStart interface{}) (interface{}, error) {
+func (cg *CodeGen) EmitBinding(ctx context.Context, scope *parser.Scope, caller parser.Node, b parser.Binding, args []*parser.Expr, chainStart interface{}) (interface{}, error) {
 	v, ok := cg.values[b]
 
 	// When the binding value is not yet set, emit the containing func to compute a value.
@@ -49,7 +49,7 @@ func (cg *CodeGen) EmitBinding(ctx context.Context, scope *parser.Scope, b parse
 		// Codegen can short-circuit control flow by returning ErrBindingCycle up the
 		// stack. If it matches b then we've finished early and the error is ignored.
 		var err error
-		v, err = cg.EmitFuncDecl(ctx, scope, b.Bind.Lexical, args, chainStart)
+		v, err = cg.EmitFuncDecl(ctx, scope, caller, b.Bind.Lexical, args, chainStart)
 		if errors.As(err, &cycle) && cycle.Binding == b {
 			err = nil
 			v = cg.values[b]
diff --git a/codegen/chain.go b/codegen/chain.go
index b1b9c6f..28cedfc 100644
--- a/codegen/chain.go
+++ b/codegen/chain.go
@@ -91,10 +91,10 @@ func (cg *CodeGen) EmitFilesystemChainStmt(ctx context.Context, scope *parser.Sc
 		var err error
 		switch n := obj.Node.(type) {
 		case *parser.FuncDecl:
-			v, err = cg.EmitFuncDecl(ctx, scope, n, args, chainStart)
+			v, err = cg.EmitFuncDecl(ctx, scope, expr, n, args, chainStart)
 		case *parser.BindClause:
 			b := n.TargetBinding(expr.Name())
-			v, err = cg.EmitBinding(ctx, scope, b, args, chainStart)
+			v, err = cg.EmitBinding(ctx, scope, expr, b, args, chainStart)
 		case *parser.ImportDecl:
 			importScope := obj.Data.(*parser.Scope)
 			importName := expr.Selector.Select.Name
@@ -105,10 +105,10 @@ func (cg *CodeGen) EmitFilesystemChainStmt(ctx context.Context, scope *parser.Sc
 
 			switch m := importObj.Node.(type) {
 			case *parser.FuncDecl:
-				v, err = cg.EmitFuncDecl(ctx, scope, m, args, chainStart)
+				v, err = cg.EmitFuncDecl(ctx, scope, expr, m, args, chainStart)
 			case *parser.BindClause:
 				b := m.TargetBinding(importName)
-				v, err = cg.EmitBinding(ctx, scope, b, args, chainStart)
+				v, err = cg.EmitBinding(ctx, scope, expr, b, args, chainStart)
 			default:
 				return fc, errors.WithStack(ErrCodeGen{m, errors.Errorf("unknown obj type")})
 			}
@@ -189,6 +189,9 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			opt := iopt.(llb.ImageOption)
 			opts = append(opts, opt)
 		}
+		for _, opt := range cg.SourceMap(expr) {
+			opts = append(opts, opt)
+		}
 
 		fc = func(_ llb.State) (llb.State, error) {
 			return llb.Image(ref, opts...), nil
@@ -204,6 +207,9 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			opt := iopt.(llb.HTTPOption)
 			opts = append(opts, opt)
 		}
+		for _, opt := range cg.SourceMap(expr) {
+			opts = append(opts, opt)
+		}
 
 		fc = func(_ llb.State) (llb.State, error) {
 			return llb.HTTP(url, opts...), nil
@@ -223,6 +229,10 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			opt := iopt.(llb.GitOption)
 			opts = append(opts, opt)
 		}
+		for _, opt := range cg.SourceMap(expr) {
+			opts = append(opts, opt)
+		}
+
 		fc = func(_ llb.State) (llb.State, error) {
 			return llb.Git(remote, ref, opts...), nil
 		}
@@ -232,7 +242,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			return fc, err
 		}
 
-		path, err = ResolvePathForNode(scope.Node, path)
+		path, err = parser.ResolvePath(scope.Node, path)
 		if err != nil {
 			return fc, err
 		}
@@ -247,6 +257,9 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			opt := iopt.(llb.LocalOption)
 			opts = append(opts, opt)
 		}
+		for _, opt := range cg.SourceMap(expr) {
+			opts = append(opts, opt)
+		}
 
 		if !fi.IsDir() {
 			filename := filepath.Base(path)
@@ -350,6 +363,9 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 				wantShlex = true
 			}
 		}
+		for _, opt := range cg.SourceMap(expr) {
+			opts = append(opts, opt)
+		}
 
 		cmd, err := cg.EmitShellCommand(ctx, scope, args, wantShlex)
 		if err != nil {
@@ -461,6 +477,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 		fc = func(st llb.State) (llb.State, error) {
 			return st.File(
 				llb.Mkdir(path, os.FileMode(mode), opts...),
+				cg.SourceMap(expr)...,
 			), nil
 		}
 	case "mkfile":
@@ -488,6 +505,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 		fc = func(st llb.State) (llb.State, error) {
 			return st.File(
 				llb.Mkfile(path, os.FileMode(mode), []byte(content), opts...),
+				cg.SourceMap(expr)...,
 			), nil
 		}
 	case "rm":
@@ -505,6 +523,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 		fc = func(st llb.State) (llb.State, error) {
 			return st.File(
 				llb.Rm(path, opts...),
+				cg.SourceMap(expr)...,
 			), nil
 		}
 	case "copy":
@@ -532,6 +551,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 		fc = func(st llb.State) (llb.State, error) {
 			return st.File(
 				llb.Copy(input, src, dest, info),
+				cg.SourceMap(expr)...,
 			), nil
 		}
 	case "entrypoint":
@@ -696,7 +716,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			return fc, err
 		}
 
-		localPath, err = ResolvePathForNode(scope.Node, localPath)
+		localPath, err = parser.ResolvePath(scope.Node, localPath)
 		if err != nil {
 			return fc, err
 		}
@@ -715,7 +735,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			return fc, err
 		}
 
-		localPath, err = ResolvePathForNode(scope.Node, localPath)
+		localPath, err = parser.ResolvePath(scope.Node, localPath)
 		if err != nil {
 			return fc, err
 		}
@@ -734,7 +754,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			return fc, err
 		}
 
-		localPath, err = ResolvePathForNode(scope.Node, localPath)
+		localPath, err = parser.ResolvePath(scope.Node, localPath)
 		if err != nil {
 			return fc, err
 		}
@@ -753,7 +773,7 @@ func (cg *CodeGen) EmitFilesystemBuiltinChainStmt(ctx context.Context, scope *pa
 			return fc, err
 		}
 
-		localPath, err = ResolvePathForNode(scope.Node, localPath)
+		localPath, err = parser.ResolvePath(scope.Node, localPath)
 		if err != nil {
 			return fc, err
 		}
@@ -1064,10 +1084,10 @@ func (cg *CodeGen) EmitStringChainStmt(ctx context.Context, scope *parser.Scope,
 		var err error
 		switch n := obj.Node.(type) {
 		case *parser.FuncDecl:
-			v, err = cg.EmitFuncDecl(ctx, scope, n, args, chainStart)
+			v, err = cg.EmitFuncDecl(ctx, scope, expr, n, args, chainStart)
 		case *parser.BindClause:
 			b := n.TargetBinding(expr.Name())
-			v, err = cg.EmitBinding(ctx, scope, b, args, chainStart)
+			v, err = cg.EmitBinding(ctx, scope, expr, b, args, chainStart)
 		case *parser.ImportDecl:
 			importScope := obj.Data.(*parser.Scope)
 			importName := expr.Selector.Select.Name
@@ -1078,10 +1098,10 @@ func (cg *CodeGen) EmitStringChainStmt(ctx context.Context, scope *parser.Scope,
 
 			switch m := importObj.Node.(type) {
 			case *parser.FuncDecl:
-				v, err = cg.EmitFuncDecl(ctx, scope, m, args, chainStart)
+				v, err = cg.EmitFuncDecl(ctx, scope, expr, m, args, chainStart)
 			case *parser.BindClause:
 				b := m.TargetBinding(importName)
-				v, err = cg.EmitBinding(ctx, scope, b, args, chainStart)
+				v, err = cg.EmitBinding(ctx, scope, expr, b, args, chainStart)
 			default:
 				return nil, errors.WithStack(ErrCodeGen{n, errors.Errorf("unknown obj type")})
 			}
@@ -1136,10 +1156,10 @@ func (cg *CodeGen) EmitGroupChainStmt(ctx context.Context, scope *parser.Scope,
 		var v interface{}
 		switch n := obj.Node.(type) {
 		case *parser.FuncDecl:
-			v, err = cg.EmitFuncDecl(ctx, scope, n, args, chainStart)
+			v, err = cg.EmitFuncDecl(ctx, scope, expr, n, args, chainStart)
 		case *parser.BindClause:
 			b := n.TargetBinding(expr.Name())
-			v, err = cg.EmitBinding(ctx, scope, b, args, chainStart)
+			v, err = cg.EmitBinding(ctx, scope, expr, b, args, chainStart)
 		case *parser.ImportDecl:
 			importScope := obj.Data.(*parser.Scope)
 			importName := expr.Selector.Select.Name
@@ -1150,10 +1170,10 @@ func (cg *CodeGen) EmitGroupChainStmt(ctx context.Context, scope *parser.Scope,
 
 			switch m := importObj.Node.(type) {
 			case *parser.FuncDecl:
-				v, err = cg.EmitFuncDecl(ctx, scope, m, args, chainStart)
+				v, err = cg.EmitFuncDecl(ctx, scope, expr, m, args, chainStart)
 			case *parser.BindClause:
 				b := m.TargetBinding(importName)
-				v, err = cg.EmitBinding(ctx, scope, b, args, chainStart)
+				v, err = cg.EmitBinding(ctx, scope, expr, b, args, chainStart)
 			default:
 				return gc, errors.WithStack(ErrCodeGen{m, errors.Errorf("unknown obj type")})
 			}
diff --git a/codegen/codegen.go b/codegen/codegen.go
index 3fed6c0..239ae91 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -15,7 +15,6 @@ import (
 
 	"github.com/docker/buildx/util/progress"
 	"github.com/docker/cli/cli/command"
-	homedir "github.com/mitchellh/go-homedir"
 	"github.com/moby/buildkit/client"
 	"github.com/moby/buildkit/client/llb"
 	"github.com/moby/buildkit/client/llb/imagemetaresolver"
@@ -39,9 +38,11 @@ import (
 )
 
 type CodeGen struct {
-	Debug     Debugger
-	cln       *client.Client
-	sessionID string
+	Debug      Debugger
+	cln        *client.Client
+	sessionID  string
+	fbs        map[string]*parser.FileBuffer
+	stacktrace []Frame
 
 	values map[parser.Binding]interface{}
 
@@ -80,6 +81,13 @@ func WithClient(cln *client.Client) CodeGenOption {
 	}
 }
 
+func WithFileBuffers(fbs map[string]*parser.FileBuffer) CodeGenOption {
+	return func(i *CodeGen) error {
+		i.fbs = fbs
+		return nil
+	}
+}
+
 func New(opts ...CodeGenOption) (*CodeGen, error) {
 	cg := &CodeGen{
 		Debug:           NewNoopDebugger(),
@@ -133,7 +141,7 @@ func (cg *CodeGen) Generate(ctx context.Context, mod *parser.Module, targets []T
 					return nil, checker.ErrInvalidTarget{Node: n, Target: target.Name}
 				}
 
-				v, err = cg.EmitFuncDecl(ctx, mod.Scope, n, nil, nil)
+				v, err = cg.EmitFuncDecl(ctx, mod.Scope, n.Name, n, nil, nil)
 				if err != nil {
 					return nil, err
 				}
@@ -144,7 +152,7 @@ func (cg *CodeGen) Generate(ctx context.Context, mod *parser.Module, targets []T
 					return nil, checker.ErrInvalidTarget{Node: n, Target: target.Name}
 				}
 
-				v, err = cg.EmitBinding(ctx, mod.Scope, b, nil, nil)
+				v, err = cg.EmitBinding(ctx, mod.Scope, n.Ident, b, nil, nil)
 				if err != nil {
 					return nil, err
 				}
@@ -459,7 +467,7 @@ func (cg *CodeGen) EmitOptionLookup(ctx context.Context, scope *parser.Scope, ex
 	case parser.DeclKind:
 		switch n := obj.Node.(type) {
 		case *parser.FuncDecl:
-			return cg.EmitOptionFuncDecl(ctx, scope, n, args)
+			return cg.EmitOptionFuncDecl(ctx, scope, expr, n, args)
 		case *parser.ImportDecl:
 			importScope := obj.Data.(*parser.Scope)
 			importObj := importScope.Lookup(expr.Selector.Select.Name)
@@ -469,7 +477,7 @@ func (cg *CodeGen) EmitOptionLookup(ctx context.Context, scope *parser.Scope, ex
 
 			switch m := importObj.Node.(type) {
 			case *parser.FuncDecl:
-				return cg.EmitOptionFuncDecl(ctx, scope, m, args)
+				return cg.EmitOptionFuncDecl(ctx, scope, expr, m, args)
 			default:
 				return opts, errors.WithStack(ErrCodeGen{expr, errors.Errorf("unknown option decl kind")})
 			}
@@ -1109,7 +1117,7 @@ func (cg *CodeGen) EmitExecOptions(ctx context.Context, scope *parser.Scope, op
 				)
 				switch srcUri.Scheme {
 				case "unix":
-					path, err = ResolvePathForNode(scope.Node, srcUri.Path)
+					path, err = parser.ResolvePath(scope.Node, srcUri.Path)
 					if err != nil {
 						return opts, err
 					}
@@ -1175,7 +1183,7 @@ func (cg *CodeGen) EmitExecOptions(ctx context.Context, scope *parser.Scope, op
 					return opts, err
 				}
 
-				localPathArg, err = ResolvePathForNode(scope.Node, localPathArg)
+				localPathArg, err = parser.ResolvePath(scope.Node, localPathArg)
 				if err != nil {
 					return opts, err
 				}
@@ -1402,7 +1410,7 @@ func (cg *CodeGen) EmitSSHOptions(ctx context.Context, scope *parser.Scope, op s
 						return opts, err
 					}
 
-					localPath, err = ResolvePathForNode(scope.Node, localPath)
+					localPath, err = parser.ResolvePath(scope.Node, localPath)
 					if err != nil {
 						return opts, err
 					}
@@ -1652,16 +1660,3 @@ func outputFromWriter(w io.WriteCloser) func(map[string]string) (io.WriteCloser,
 		return w, nil
 	}
 }
-
-func ResolvePathForNode(node parser.Node, path string) (string, error) {
-	path, err := homedir.Expand(path)
-	if err != nil {
-		return path, err
-	}
-
-	if filepath.IsAbs(path) {
-		return path, nil
-	}
-
-	return filepath.Join(filepath.Dir(node.Position().Filename), path), nil
-}
diff --git a/codegen/debug.go b/codegen/debug.go
index 1f981ab..debb2c4 100644
--- a/codegen/debug.go
+++ b/codegen/debug.go
@@ -41,7 +41,7 @@ type snapshot struct {
 	value interface{}
 }
 
-func NewDebugger(c *client.Client, w io.Writer, r *bufio.Reader, ibs map[string]*report.IndexedBuffer) Debugger {
+func NewDebugger(c *client.Client, w io.Writer, r *bufio.Reader, fbs map[string]*parser.FileBuffer) Debugger {
 	color := aurora.NewAurora(true)
 
 	var (
@@ -100,7 +100,7 @@ func NewDebugger(c *client.Client, w io.Writer, r *bufio.Reader, ibs map[string]
 			}
 
 			if showList && !cont {
-				err := printList(color, ibs, w, s.node)
+				err := printList(color, fbs, w, s.node)
 				if err != nil {
 					return err
 				}
@@ -277,7 +277,7 @@ func NewDebugger(c *client.Client, w io.Writer, r *bufio.Reader, ibs map[string]
 					fmt.Fprintf(w, "security - print security mode\n")
 				case "list", "l":
 					if showList {
-						err = printList(color, ibs, w, s.node)
+						err = printList(color, fbs, w, s.node)
 						if err != nil {
 							return err
 						}
@@ -386,9 +386,9 @@ func NewDebugger(c *client.Client, w io.Writer, r *bufio.Reader, ibs map[string]
 	}
 }
 
-func printList(color aurora.Aurora, ibs map[string]*report.IndexedBuffer, w io.Writer, node parser.Node) error {
+func printList(color aurora.Aurora, fbs map[string]*parser.FileBuffer, w io.Writer, node parser.Node) error {
 	pos := node.Position()
-	ib := ibs[pos.Filename]
+	fb := fbs[pos.Filename]
 
 	var lines []string
 
@@ -398,8 +398,8 @@ func printList(color aurora.Aurora, ibs map[string]*report.IndexedBuffer, w io.W
 	}
 
 	end := start + 10
-	if end > ib.Len() {
-		end = ib.Len()
+	if end > fb.Len() {
+		end = fb.Len()
 	}
 
 	length := 1
@@ -419,7 +419,7 @@ func printList(color aurora.Aurora, ibs map[string]*report.IndexedBuffer, w io.W
 	lines = append(lines, header)
 
 	for i := start; i < end; i++ {
-		line, err := ib.Line(i)
+		line, err := fb.Line(i)
 		if err != nil {
 			return err
 		}
diff --git a/codegen/decl.go b/codegen/decl.go
index 9dd885e..792ef9d 100644
--- a/codegen/decl.go
+++ b/codegen/decl.go
@@ -10,7 +10,12 @@ import (
 	"github.com/pkg/errors"
 )
 
-func (cg *CodeGen) EmitFuncDecl(ctx context.Context, scope *parser.Scope, fun *parser.FuncDecl, args []*parser.Expr, chainStart interface{}) (interface{}, error) {
+func (cg *CodeGen) EmitFuncDecl(ctx context.Context, scope *parser.Scope, caller parser.Node, fun *parser.FuncDecl, args []*parser.Expr, chainStart interface{}) (interface{}, error) {
+	cg.stacktrace = append(cg.stacktrace, Frame{Node: caller})
+	defer func() {
+		cg.stacktrace = cg.stacktrace[:len(cg.stacktrace)-1]
+	}()
+
 	nonVariadicArgs := 0
 	for _, field := range fun.Params.List {
 		if field.Variadic == nil {
@@ -46,21 +51,8 @@ func (cg *CodeGen) EmitFuncDecl(ctx context.Context, scope *parser.Scope, fun *p
 	}
 }
 
-func (cg *CodeGen) EmitFilesystemFuncDecl(ctx context.Context, scope *parser.Scope, fun *parser.FuncDecl, args []*parser.Expr, chainStart interface{}) (st llb.State, err error) {
-	v, err := cg.EmitFuncDecl(ctx, scope, fun, args, chainStart)
-	if err != nil {
-		return
-	}
-
-	st, ok := v.(llb.State)
-	if !ok {
-		return st, errors.WithStack(ErrCodeGen{fun, ErrBadCast})
-	}
-	return
-}
-
-func (cg *CodeGen) EmitOptionFuncDecl(ctx context.Context, scope *parser.Scope, fun *parser.FuncDecl, args []*parser.Expr) (opts []interface{}, err error) {
-	v, err := cg.EmitFuncDecl(ctx, scope, fun, args, nil)
+func (cg *CodeGen) EmitOptionFuncDecl(ctx context.Context, scope *parser.Scope, caller parser.Node, fun *parser.FuncDecl, args []*parser.Expr) (opts []interface{}, err error) {
+	v, err := cg.EmitFuncDecl(ctx, scope, caller, fun, args, nil)
 	if err != nil {
 		return
 	}
@@ -72,47 +64,6 @@ func (cg *CodeGen) EmitOptionFuncDecl(ctx context.Context, scope *parser.Scope,
 	return
 }
 
-func (cg *CodeGen) EmitStringFuncDecl(ctx context.Context, scope *parser.Scope, fun *parser.FuncDecl, args []*parser.Expr, chainStart interface{}) (str string, err error) {
-	v, err := cg.EmitFuncDecl(ctx, scope, fun, args, chainStart)
-	if err != nil {
-		return
-	}
-
-	str, ok := v.(string)
-	if !ok {
-		return str, errors.WithStack(ErrCodeGen{fun, ErrBadCast})
-	}
-	return
-}
-
-func (cg *CodeGen) EmitGroupFuncDecl(ctx context.Context, scope *parser.Scope, fun *parser.FuncDecl, args []*parser.Expr, chainStart interface{}) (solver.Request, error) {
-	v, err := cg.EmitFuncDecl(ctx, scope, fun, args, chainStart)
-	if v == nil {
-		return nil, err
-	}
-
-	var request solver.Request
-	switch t := v.(type) {
-	case solver.Request:
-		request = t
-	case llb.State:
-		request, err = cg.outputRequest(ctx, t, Output{})
-		if err != nil {
-			return request, err
-		}
-
-		if len(cg.requests) > 0 {
-			request = solver.Parallel(append([]solver.Request{request}, cg.requests...)...)
-		}
-
-		cg.reset()
-	default:
-		return request, errors.WithStack(ErrCodeGen{fun, errors.Errorf("unknown group func decl")})
-	}
-
-	return request, err
-}
-
 func (cg *CodeGen) ParameterizedScope(ctx context.Context, scope *parser.Scope, fun *parser.FuncDecl, args []*parser.Expr) error {
 	for i, field := range fun.Params.List {
 		var (
diff --git a/codegen/stacktrace.go b/codegen/stacktrace.go
new file mode 100644
index 0000000..6463449
--- /dev/null
+++ b/codegen/stacktrace.go
@@ -0,0 +1,46 @@
+package codegen
+
+import (
+	"github.com/moby/buildkit/client/llb"
+	"github.com/moby/buildkit/solver/pb"
+	"github.com/openllb/hlb/parser"
+)
+
+type Frame struct {
+	Node parser.Node
+}
+
+func (cg *CodeGen) SourceMap(node parser.Node) []llb.ConstraintsOpt {
+	if cg.fbs == nil {
+		return nil
+	}
+
+	stacktrace := make([]Frame, len(cg.stacktrace)+1)
+	copy(stacktrace, cg.stacktrace)
+	stacktrace[len(stacktrace)-1] = Frame{Node: node}
+
+	var opts []llb.ConstraintsOpt
+
+	for i := len(stacktrace) - 1; i >= 0; i-- {
+		node := stacktrace[i].Node
+		fb, ok := cg.fbs[node.Position().Filename]
+		if !ok {
+			continue
+		}
+
+		opts = append(opts, fb.SourceMap().Location([]*pb.Range{
+			{
+				Start: pb.Position{
+					Line:      int32(node.Position().Line),
+					Character: int32(node.Position().Column),
+				},
+				End: pb.Position{
+					Line:      int32(node.End().Line),
+					Character: int32(node.End().Column),
+				},
+			},
+		}))
+	}
+
+	return opts
+}
diff --git a/gen/builtins.go b/gen/builtins.go
index efe9f5a..45a0dc7 100644
--- a/gen/builtins.go
+++ b/gen/builtins.go
@@ -28,7 +28,7 @@ type ParsedFunc struct {
 }
 
 func GenerateBuiltins(r io.Reader) ([]byte, error) {
-	file, err := parser.Parse(r)
+	file, _, err := parser.Parse(r)
 	if err != nil {
 		return nil, err
 	}
diff --git a/gen/documentation.go b/gen/documentation.go
index 22fbbc0..8257f9b 100644
--- a/gen/documentation.go
+++ b/gen/documentation.go
@@ -36,7 +36,7 @@ type Field struct {
 }
 
 func GenerateDocumentation(r io.Reader) (*Documentation, error) {
-	file, err := parser.Parse(r)
+	file, _, err := parser.Parse(r)
 	if err != nil {
 		return nil, err
 	}
diff --git a/go.hlb b/go.hlb
index 20a9801..3d53c86 100644
--- a/go.hlb
+++ b/go.hlb
@@ -42,7 +42,7 @@ fs crossBuild(fs src, string package, string verPackage) {
 fs lint(fs src) {
 	image "golang:1.13.10-alpine"
 	run "apk add -U git gcc libc-dev"
-	run "sh /golangci/install.sh -b /usr/bin v1.23.8" with option {
+	run "sh /golangci/install.sh -b /usr/bin v1.27.0" with option {
 		mount fs {
 			http "https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
 		} "/golangci"
diff --git a/go.mod b/go.mod
index e1df759..5a2bf90 100644
--- a/go.mod
+++ b/go.mod
@@ -7,19 +7,20 @@ require (
 	github.com/creachadair/jrpc2 v0.8.1
 	github.com/docker/buildx v0.3.2-0.20200410204309-f4ac640252b8
 	github.com/docker/cli v1.14.0-0.20190523191156-ab688a9a79a1
+	github.com/docker/go-metrics v0.0.1 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 	github.com/lithammer/dedent v1.1.0
 	github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23
 	github.com/mattn/go-isatty v0.0.11
 	github.com/mitchellh/go-homedir v1.1.0
-	github.com/moby/buildkit v0.7.1-0.20200409032528-226a5db9ad3d
-	github.com/opencontainers/go-digest v1.0.0-rc1
+	github.com/moby/buildkit v0.7.1-0.20200525141520-d6f5e972def2
+	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/image-spec v1.0.1
 	github.com/openllb/doxygen-parser v0.0.0-20200128221307-2aa2d8be1c35
 	github.com/pkg/errors v0.9.1
 	github.com/sourcegraph/go-lsp v0.0.0-20200117082640-b19bb38222e2
-	github.com/stretchr/testify v1.4.0
-	github.com/tonistiigi/fsutil v0.0.0-20200326231323-c2c7d7b0e144
+	github.com/stretchr/testify v1.5.1
+	github.com/tonistiigi/fsutil v0.0.0-20200512175118-ae3a8d753069
 	github.com/urfave/cli/v2 v2.1.1
 	github.com/xlab/treeprint v1.0.0
 	golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
diff --git a/go.sum b/go.sum
index ea6d8b4..6523913 100644
--- a/go.sum
+++ b/go.sum
@@ -2,29 +2,36 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxo
 bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M=
 bitbucket.org/creachadair/stringset v0.0.8 h1:gQqe4vs8XWgMyijfyKE6K8o4TcyGGrRXe0JvHgx5H+M=
 bitbucket.org/creachadair/stringset v0.0.8/go.mod h1:AgthVMyMxC/6FK1KBJ2ALdqkZObGN8hOetgpwXyMn34=
+cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/AkihiroSuda/containerd-fuse-overlayfs v0.0.0-20200220082720-bb896865146c/go.mod h1:K4kx7xAA5JimeQCnN+dbeLlfaBxzZLaLiDD8lusFI8w=
+github.com/AkihiroSuda/containerd-fuse-overlayfs v0.0.0-20200512015515-32086ef23a5a/go.mod h1:RkqizX9+ro7Pp7RxEZAJWIr1/FrkZKCuUDi944JHt0U=
 github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
 github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
-github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg=
 github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
+github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
+github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
 github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
 github.com/alecthomas/go-thrift v0.0.0-20170109061633-7914173639b2/go.mod h1:CxCgO+NdpMdi9SsTlGbc0W+/UNxO3I0AabOEJZ3w61w=
 github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
+github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E=
 github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
@@ -34,58 +41,75 @@ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
 github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
+github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
+github.com/bugsnag/bugsnag-go v1.4.1 h1:TT3P9AX69w8mbSGE8L7IJOO2KBlPN0iQtYD0dUlrWHc=
 github.com/bugsnag/bugsnag-go v1.4.1/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
 github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
 github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
 github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
 github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e h1:Qux+lbuMaRzkQyTdzgtz8MgzPtzmaPQy6DXmxpdxT3U=
 github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
-github.com/containerd/cgroups v0.0.0-20200217135630-d732e370d46d h1:UKAt78F1OvM4ceTn1VvXuYuatXohsFU1eSI2IBtTw9g=
 github.com/containerd/cgroups v0.0.0-20200217135630-d732e370d46d/go.mod h1:CStdkl05lBnJej94BPFoJ7vB8cELKXwViS+dgfW0/M8=
+github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989 h1:ziD9Q8QdO0vybP3fYg5MQ+PqDAb61jEbdCsL2f5xXK4=
+github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989/go.mod h1:CStdkl05lBnJej94BPFoJ7vB8cELKXwViS+dgfW0/M8=
 github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
 github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
 github.com/containerd/console v0.0.0-20191219165238-8375c3424e4d h1:VuiIRfgJ2M3vYEU0F6E5lg3+V0l9YpbGQr3jpZor5fo=
 github.com/containerd/console v0.0.0-20191219165238-8375c3424e4d/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
+github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
+github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
 github.com/containerd/containerd v1.3.1-0.20200227195959-4d242818bf55 h1:FGO0nwSBESgoGCakj+w3OQXyrMLsz2omdo9b2UfG/BQ=
 github.com/containerd/containerd v1.3.1-0.20200227195959-4d242818bf55/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/continuity v0.0.0-20181001140422-bd77b46c8352/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
 github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41 h1:kIFnQBO7rQ0XkMe6xEwbybYHBEaWmh/f++laI6Emt7M=
 github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
+github.com/containerd/continuity v0.0.0-20200228182428-0f16d7a0959c/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
+github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk=
+github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
 github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
 github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
+github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b h1:qUtCegLdOUVfVJOw+KDg6eJyE1TGvLlkGEd1091kSSQ=
+github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
 github.com/containerd/go-cni v0.0.0-20200107172653-c154a49e2c75/go.mod h1:0mg8r6FCdbxvLDqCXwAx2rO+KA37QICjKL8+wHOG5OE=
 github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
 github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
 github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
 github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
-github.com/containerd/ttrpc v0.0.0-20200121165050-0be804eadb15 h1:+jgiLE5QylzgADj0Yldb4id1NQNRrDOROj7KDvY9PEc=
 github.com/containerd/ttrpc v0.0.0-20200121165050-0be804eadb15/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
+github.com/containerd/ttrpc v1.0.1 h1:IfVOxKbjyBn9maoye2JN95pgGYOmPkQVqxtOu7rtNIc=
+github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
 github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
 github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
 github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
+github.com/containerd/typeurl v1.0.1 h1:PvuK4E3D5S5q6IqsPDCy928FhP0LUIGcmZ/Yhgp5Djw=
+github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
 github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
 github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coryb/fsutil v0.0.0-20200508061958-7d16a3dcbd1d h1:A7to1XdmvKRghdKwiC/bc2Y5Uwwu1eg3+eaaAxaNriU=
-github.com/coryb/fsutil v0.0.0-20200508061958-7d16a3dcbd1d/go.mod h1:0G1sLZ/0ttFf09xvh7GR4AEECnjifHRNJN/sYbLianU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -96,6 +120,7 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 h1:tTngnoO/B6HQnJ+pK8tN7kEAhmhIfaJOutqq/A4/JTM=
 github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -108,6 +133,8 @@ github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go
 github.com/docker/distribution v0.0.0-20200223014041-6b972e50feee/go.mod h1:xgJxuOjyp98AvnpRTR1+lGOqQ493ylRnRPmewD5GWtc=
 github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
 github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
+github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v1.4.2-0.20200227233006-38f52c9fec82 h1:kZwwJwYnVWtU/byBNjD9rEGWVMvwnfiKu9lFJXjrk04=
 github.com/docker/docker v1.4.2-0.20200227233006-38f52c9fec82/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.6.0/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
@@ -118,14 +145,18 @@ github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK
 github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
-github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA=
 github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
+github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
+github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
 github.com/docker/go-units v0.3.1/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/libnetwork v0.8.0-dev.2.0.20200226230617-d8334ccdb9be h1:GJzljYRqZapOwyfeRyExF2/5qfv8f1feNDMiz0hmRPY=
 github.com/docker/libnetwork v0.8.0-dev.2.0.20200226230617-d8334ccdb9be/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
 github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
+github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 h1:k8TfKGeAcDQFFQOGCQMRN04N4a9YrPlRMMKnzAuvM9Q=
 github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
@@ -139,22 +170,31 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
 github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
+github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
 github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
+github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.3.2 h1:kX1es4djPJrsDhY7aZKJy7aZasdcB5oSOEphMjSB53c=
 github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
@@ -172,48 +212,62 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
 github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
 github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
 github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
-github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
+github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
+github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg=
+github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
 github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
 github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
 github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
 github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0=
 github.com/hinshun/participle v0.4.2-0.20200115220927-0afe0602c1fc h1:/IDEdmcUnCJtSDErozACKN9d++/yboRmwCC8DgFQro0=
 github.com/hinshun/participle v0.4.2-0.20200115220927-0afe0602c1fc/go.mod h1:T8u4bQOSMwrkTWOSyt8/jSFPEnRtd0FKFMjVfYBlqPs=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg=
+github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw=
 github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
+github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
 github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns=
 github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
@@ -223,20 +277,26 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
 github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
 github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc=
 github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
 github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -245,11 +305,13 @@ github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19/go.mod h1:WCBAbTOdfhH
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
+github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
 github.com/moby/buildkit v0.7.0/go.mod h1:zOhLO1TiQepuSfzoNDQ542IGJQy0CHr79T4MOJvaewY=
-github.com/moby/buildkit v0.7.1-0.20200409032528-226a5db9ad3d h1:enoJGnje9YxuvZcF7UJmWlkDH2ABMUCfGHwnx+pcT2o=
-github.com/moby/buildkit v0.7.1-0.20200409032528-226a5db9ad3d/go.mod h1:D3DN/Nl4DyMH1LkwpRUJuoghqdigdXd1A6HXt5aZS40=
+github.com/moby/buildkit v0.7.1-0.20200525141520-d6f5e972def2 h1:fkZgDui8mQtyBEicaZuXiE/REAGDxep2OZjgwkxU+Zw=
+github.com/moby/buildkit v0.7.1-0.20200525141520-d6f5e972def2/go.mod h1:4otpTPV294PXwTbjBlYnS1Er42Pe6baNDIS84W/Q2PM=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -275,24 +337,34 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1
 github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runc v1.0.0-rc6 h1:7AoN22rYxxkmsJS48wFaziH/n0OvrZVqL/TglgHKbKQ=
 github.com/opencontainers/runc v1.0.0-rc6/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runc v1.0.0-rc9.0.20200102164712-2b52db75279c/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runc v1.0.0-rc9.0.20200221051241-688cf6d43cc4 h1:JhRvjyrjq24YPSDS0MQo9KJHQh95naK5fYl9IT+dzPM=
 github.com/opencontainers/runc v1.0.0-rc9.0.20200221051241-688cf6d43cc4/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
+github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
 github.com/opencontainers/selinux v1.3.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
 github.com/opencontainers/selinux v1.3.3/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
+github.com/opencontainers/selinux v1.5.1 h1:jskKwSMFYqyTrHEuJgQoUlTcId0av64S6EWObrIfn5Y=
+github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
 github.com/openllb/doxygen-parser v0.0.0-20200128221307-2aa2d8be1c35 h1:4jZOiVrsqwaDOpqX4xznnKywSHdMFcdJCtMCOTdKN6A=
 github.com/openllb/doxygen-parser v0.0.0-20200128221307-2aa2d8be1c35/go.mod h1:DuDo237gkAyYMOJOYpLlgRT2zuSXh8j43RfwcYgdnN4=
 github.com/opentracing-contrib/go-stdlib v0.0.0-20171029140428-b1a47cfbdd75/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=
-github.com/opentracing/opentracing-go v0.0.0-20171003133519-1361b9cd60be h1:vn0ruyYif1hUWDS2aEUdh6JGUfgK8gOOLpz/iTjb6pQ=
 github.com/opentracing/opentracing-go v0.0.0-20171003133519-1361b9cd60be/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -312,6 +384,7 @@ github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7q
 github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 h1:osmNoEW2SCW3L7EX0km2LYM8HKpNWRiouxjE3XHkyGc=
 github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1 h1:Lo6mRUjdS99f3zxYOUalftWHUoOGaDRqFk1+j0Q57/I=
 github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
 github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
@@ -333,17 +406,21 @@ github.com/slushie/fsutil v0.0.0-20200508061958-7d16a3dcbd1d h1:+rqYXBNuULpf/QXG
 github.com/slushie/fsutil v0.0.0-20200508061958-7d16a3dcbd1d/go.mod h1:0G1sLZ/0ttFf09xvh7GR4AEECnjifHRNJN/sYbLianU=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -354,21 +431,21 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
+github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
 github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
-github.com/tonistiigi/fsutil v0.0.0-20200225063759-013a9fe6aee2/go.mod h1:0G1sLZ/0ttFf09xvh7GR4AEECnjifHRNJN/sYbLianU=
-github.com/tonistiigi/fsutil v0.0.0-20200326231323-c2c7d7b0e144 h1:6RY1EKxCnPQShPM46xFDHta2JSOd+YKCgHyyBHtKuo8=
-github.com/tonistiigi/fsutil v0.0.0-20200326231323-c2c7d7b0e144/go.mod h1:0G1sLZ/0ttFf09xvh7GR4AEECnjifHRNJN/sYbLianU=
 github.com/tonistiigi/go-immutable-radix v0.0.0-20170803185627-826af9ccf0fe/go.mod h1:/+MCh11CJf2oz0BXmlmqyopK/ad1rKkcOXPoYuPCJYU=
 github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
 github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
 github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305 h1:y/1cL5AL2oRcfzz8CAHHhR6kDDfIOT0WEyH5k40sccM=
 github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305/go.mod h1:gXOLibKqQTRAVuVZ9gX7G9Ykky8ll8yb4slxsEMoY0c=
 github.com/uber/jaeger-client-go v0.0.0-20180103221425-e02c85f9069e/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-client-go v2.11.2+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v1.2.1/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
@@ -376,10 +453,15 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
 github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
 github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
 github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
+github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
+github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
 github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
+github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
 github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
 github.com/xlab/treeprint v1.0.0 h1:J0TkWtiuYgtdlrkkrDLISYBQ92M+X5m4LrIIMKrbDTs=
 github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg=
@@ -387,9 +469,13 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
 github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
+go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -436,6 +522,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -444,8 +531,9 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -462,13 +550,16 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
@@ -479,6 +570,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -490,11 +582,15 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ=
 gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
+gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
 gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
+gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
 gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -504,7 +600,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/hlb.go b/hlb.go
index bf2145b..69d7c6b 100644
--- a/hlb.go
+++ b/hlb.go
@@ -13,7 +13,7 @@ import (
 	"github.com/openllb/hlb/checker"
 	"github.com/openllb/hlb/codegen"
 	"github.com/openllb/hlb/module"
-	"github.com/openllb/hlb/report"
+	"github.com/openllb/hlb/parser"
 	"github.com/openllb/hlb/solver"
 )
 
@@ -26,12 +26,12 @@ func DefaultParseOpts() []ParseOption {
 }
 
 func Compile(ctx context.Context, cln *client.Client, p solver.Progress, targets []codegen.Target, r io.Reader) (solver.Request, error) {
-	mod, ib, err := Parse(r, DefaultParseOpts()...)
+	mod, fb, err := Parse(r, DefaultParseOpts()...)
 	if err != nil {
 		return nil, err
 	}
-	ibs := map[string]*report.IndexedBuffer{
-		mod.Pos.Filename: ib,
+	fbs := map[string]*parser.FileBuffer{
+		mod.Pos.Filename: fb,
 	}
 
 	err = checker.Check(mod)
@@ -51,7 +51,7 @@ func Compile(ctx context.Context, cln *client.Client, p solver.Progress, targets
 	}
 	defer res.Close()
 
-	err = module.ResolveGraph(ctx, resolver, res, mod, nil)
+	err = module.ResolveGraph(ctx, resolver, res, mod, fbs, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -69,12 +69,12 @@ func Compile(ctx context.Context, cln *client.Client, p solver.Progress, targets
 		names = append(names, target.Name)
 	}
 
-	var opts []codegen.CodeGenOption
+	opts := []codegen.CodeGenOption{codegen.WithFileBuffers(fbs)}
 	if mw != nil {
 		opts = append(opts, codegen.WithMultiWriter(mw), codegen.WithClient(cln))
 	} else {
 		r := bufio.NewReader(os.Stdin)
-		opts = append(opts, codegen.WithDebugger(codegen.NewDebugger(cln, os.Stderr, r, ibs)))
+		opts = append(opts, codegen.WithDebugger(codegen.NewDebugger(cln, os.Stderr, r, fbs)))
 	}
 
 	var request solver.Request
diff --git a/module/resolve.go b/module/resolve.go
index 20c2de2..a73ebec 100644
--- a/module/resolve.go
+++ b/module/resolve.go
@@ -52,11 +52,6 @@ type Resolved interface {
 // NewResolver returns a resolver based on whether the modules path exists in
 // the current working directory.
 func NewResolver(cln *client.Client, mw *progress.MultiWriter) (Resolver, error) {
-	_, err := filepath.Abs(ModulesPath)
-	if err != nil {
-		return nil, err
-	}
-
 	root, exist, err := modulesPathExist()
 	if err != nil {
 		return nil, err
@@ -77,12 +72,8 @@ func ModulesPathExist() (bool, error) {
 }
 
 func modulesPathExist() (string, bool, error) {
-	root, err := filepath.Abs(ModulesPath)
-	if err != nil {
-		return root, false, err
-	}
-
-	_, err = os.Stat(root)
+	root := ModulesPath
+	_, err := os.Stat(root)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return root, false, nil
@@ -125,7 +116,7 @@ func resolveLocal(ctx context.Context, scope *parser.Scope, lit *parser.FuncLit,
 		return nil, err
 	}
 
-	dgst, _, _, err := st.Output().Vertex(ctx).Marshal(ctx, &llb.Constraints{})
+	dgst, _, _, _, err := st.Output().Vertex(ctx).Marshal(ctx, &llb.Constraints{})
 	if err != nil {
 		return nil, err
 	}
@@ -188,7 +179,7 @@ func (r *remoteResolver) Resolve(ctx context.Context, scope *parser.Scope, decl
 		return nil, err
 	}
 
-	dgst, _, _, err := st.Output().Vertex(ctx).Marshal(ctx, &llb.Constraints{})
+	dgst, _, _, _, err := st.Output().Vertex(ctx).Marshal(ctx, &llb.Constraints{})
 	if err != nil {
 		return nil, err
 	}
@@ -246,10 +237,12 @@ func (r *remoteResolver) Resolve(ctx context.Context, scope *parser.Scope, decl
 		return nil, g.Wait()
 	}
 
-	return &remoteResolved{dgst, ref, g, ctx, closed}, nil
+	root := fmt.Sprintf("%s#%s", decl.Pos.Filename, decl.Ident)
+	return &remoteResolved{root, dgst, ref, g, ctx, closed}, nil
 }
 
 type remoteResolved struct {
+	root   string
 	dgst   digest.Digest
 	ref    gateway.Reference
 	g      *errgroup.Group
@@ -276,7 +269,10 @@ func (r *remoteResolved) Open(filename string) (io.ReadCloser, error) {
 		return nil, err
 	}
 
-	return &noopCloser{bytes.NewReader(data)}, nil
+	return &parser.NamedReader{
+		Reader: bytes.NewReader(data),
+		Value:  filepath.Join(r.root, filename),
+	}, nil
 }
 
 func (r *remoteResolved) Close() error {
@@ -284,12 +280,6 @@ func (r *remoteResolved) Close() error {
 	return r.g.Wait()
 }
 
-type noopCloser struct {
-	io.Reader
-}
-
-func (nc *noopCloser) Close() error { return nil }
-
 type tidyResolver struct {
 	remote *remoteResolver
 }
@@ -364,7 +354,7 @@ func VendorPath(root string, dgst digest.Digest) string {
 type Visitor func(decl *parser.ImportDecl, dgst digest.Digest, mod, importMod *parser.Module) error
 
 // ResolveGraph traverses the import graph of a given module.
-func ResolveGraph(ctx context.Context, resolver Resolver, res Resolved, mod *parser.Module, visitor Visitor) error {
+func ResolveGraph(ctx context.Context, resolver Resolver, res Resolved, mod *parser.Module, fbs map[string]*parser.FileBuffer, visitor Visitor) error {
 	g, ctx := errgroup.WithContext(ctx)
 
 	var (
@@ -392,7 +382,14 @@ func ResolveGraph(ctx context.Context, resolver Resolver, res Resolved, mod *par
 					filename = ModuleFilename
 				case n.ImportPath != nil:
 					importRes = res
+
 					filename = n.ImportPath.Path.Unquoted()
+					if res.Digest() == "" {
+						filename, err = parser.ResolvePath(mod, filename)
+						if err != nil {
+							return err
+						}
+					}
 				}
 
 				rc, err := importRes.Open(filename)
@@ -404,7 +401,7 @@ func ResolveGraph(ctx context.Context, resolver Resolver, res Resolved, mod *par
 				}
 				defer rc.Close()
 
-				importMod, err := parser.Parse(rc)
+				importMod, fb, err := parser.Parse(rc)
 				if err != nil {
 					return err
 				}
@@ -421,13 +418,16 @@ func ResolveGraph(ctx context.Context, resolver Resolver, res Resolved, mod *par
 					}
 				}
 
-				err = ResolveGraph(ctx, resolver, importRes, importMod, visitor)
+				err = ResolveGraph(ctx, resolver, importRes, importMod, fbs, visitor)
 				if err != nil {
 					return err
 				}
 
 				mu.Lock()
 				imports[n.Ident.Name] = importMod
+				if fbs != nil {
+					fbs[importMod.Pos.Filename] = fb
+				}
 				mu.Unlock()
 				return nil
 			})
diff --git a/module/tree.go b/module/tree.go
index 7800eaa..aa378ad 100644
--- a/module/tree.go
+++ b/module/tree.go
@@ -37,7 +37,7 @@ func NewTree(ctx context.Context, cln *client.Client, mw *progress.MultiWriter,
 	tree.SetValue(mod.Pos.Filename)
 	nodeByModule[mod] = tree
 
-	err = ResolveGraph(ctx, resolver, res, mod, func(decl *parser.ImportDecl, dgst digest.Digest, mod, importMod *parser.Module) error {
+	err = ResolveGraph(ctx, resolver, res, mod, nil, func(decl *parser.ImportDecl, dgst digest.Digest, mod, importMod *parser.Module) error {
 		var prefix string
 		if dgst != "" {
 			encoded := dgst.Encoded()
diff --git a/module/vendor.go b/module/vendor.go
index 00ec944..726bb9f 100644
--- a/module/vendor.go
+++ b/module/vendor.go
@@ -46,7 +46,7 @@ func Vendor(ctx context.Context, cln *client.Client, mw *progress.MultiWriter, m
 	g, ctx := errgroup.WithContext(ctx)
 
 	ready := make(chan struct{})
-	err = ResolveGraph(ctx, resolver, res, mod, func(decl *parser.ImportDecl, dgst digest.Digest, parentMod *parser.Module, importMod *parser.Module) error {
+	err = ResolveGraph(ctx, resolver, res, mod, nil, func(decl *parser.ImportDecl, dgst digest.Digest, parentMod *parser.Module, importMod *parser.Module) error {
 		g.Go(func() error {
 			<-ready
 
diff --git a/parse.go b/parse.go
index 43af5eb..02a415e 100644
--- a/parse.go
+++ b/parse.go
@@ -11,9 +11,9 @@ import (
 	"golang.org/x/sync/errgroup"
 )
 
-func ParseMultiple(rs []io.Reader, opts ...ParseOption) ([]*parser.Module, map[string]*report.IndexedBuffer, error) {
+func ParseMultiple(rs []io.Reader, opts ...ParseOption) ([]*parser.Module, map[string]*parser.FileBuffer, error) {
 	mods := make([]*parser.Module, len(rs))
-	buffers := make([]*report.IndexedBuffer, len(rs))
+	buffers := make([]*parser.FileBuffer, len(rs))
 
 	var g errgroup.Group
 	for i, r := range rs {
@@ -35,15 +35,15 @@ func ParseMultiple(rs []io.Reader, opts ...ParseOption) ([]*parser.Module, map[s
 		return nil, nil, err
 	}
 
-	ibs := make(map[string]*report.IndexedBuffer)
+	fbs := make(map[string]*parser.FileBuffer)
 	for i, mod := range mods {
-		ibs[mod.Pos.Filename] = buffers[i]
+		fbs[mod.Pos.Filename] = buffers[i]
 	}
 
-	return mods, ibs, nil
+	return mods, fbs, nil
 }
 
-func Parse(r io.Reader, opts ...ParseOption) (*parser.Module, *report.IndexedBuffer, error) {
+func Parse(r io.Reader, opts ...ParseOption) (*parser.Module, *parser.FileBuffer, error) {
 	info := ParseInfo{
 		Stdout: os.Stdout,
 		Stderr: os.Stderr,
@@ -63,40 +63,40 @@ func Parse(r io.Reader, opts ...ParseOption) (*parser.Module, *report.IndexedBuf
 	}
 	r = &parser.NewlinedReader{Reader: r}
 
-	ib := report.NewIndexedBuffer()
-	r = io.TeeReader(r, ib)
+	fb := parser.NewFileBuffer(name)
+	r = io.TeeReader(r, fb)
 
 	lex, err := parser.Parser.Lexer().Lex(&parser.NamedReader{
 		Reader: r,
 		Value:  name,
 	})
 	if err != nil {
-		return nil, ib, err
+		return nil, fb, err
 	}
 
 	mod := &parser.Module{}
 	peeker, err := lexer.Upgrade(lex)
 	if err != nil {
-		nerr, err := report.NewLexerError(info.Color, ib, peeker, err)
+		nerr, err := report.NewLexerError(info.Color, fb, peeker, err)
 		if err != nil {
-			return mod, ib, err
+			return mod, fb, err
 		}
 
 		parser.Parser.ParseFromLexer(peeker, mod)
-		return mod, ib, nerr
+		return mod, fb, nerr
 	}
 
 	err = parser.Parser.ParseFromLexer(peeker, mod)
 	if err != nil {
-		nerr, err := report.NewSyntaxError(info.Color, ib, peeker, err)
+		nerr, err := report.NewSyntaxError(info.Color, fb, peeker, err)
 		if err != nil {
-			return mod, ib, err
+			return mod, fb, err
 		}
 
-		return mod, ib, nerr
+		return mod, fb, nerr
 	}
 
-	return mod, ib, nil
+	return mod, fb, nil
 }
 
 type ParseOption func(*ParseInfo) error
diff --git a/parser/cst.go b/parser/cst.go
index 7e9f16e..1a8e0e4 100644
--- a/parser/cst.go
+++ b/parser/cst.go
@@ -15,7 +15,7 @@ import (
 
 var (
 	// Lexer lexes HLB into tokens for the parser.
-	Lexer = lexer.Must(regex.New(fmt.Sprintf(`
+	Lexer = lexer.Must(regex.New(`
 		Whitespace = [\r\t ]+
 		HereDoc = <<[-~]?
 		Keyword  = \b(with|as|import|export|from)\b
@@ -31,7 +31,7 @@ var (
 		Operator = {|}|\(|\)|,|;
 		Comment  = #[^\n]*\n
 		Bad      = [^\r\t\n ]*
-	`)))
+	`))
 
 	// Parser parses HLB into a concrete syntax tree rooted from a Module.
 	Parser = participle.MustBuild(
diff --git a/parser/filebuffer.go b/parser/filebuffer.go
new file mode 100644
index 0000000..6f91928
--- /dev/null
+++ b/parser/filebuffer.go
@@ -0,0 +1,129 @@
+package parser
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"sort"
+
+	"github.com/moby/buildkit/client/llb"
+)
+
+type FileBuffer struct {
+	filename string
+	buf      *bytes.Buffer
+	offset   int
+	offsets  []int
+}
+
+func NewFileBuffer(filename string) *FileBuffer {
+	return &FileBuffer{
+		filename: filename,
+		buf:      new(bytes.Buffer),
+	}
+}
+
+func (fb *FileBuffer) SourceMap() *llb.SourceMap {
+	return llb.NewSourceMap(nil, fb.filename, fb.buf.Bytes())
+}
+
+func (fb *FileBuffer) Len() int {
+	return len(fb.offsets)
+}
+
+func (fb *FileBuffer) Bytes() []byte {
+	return fb.buf.Bytes()
+}
+
+func (fb *FileBuffer) Write(p []byte) (n int, err error) {
+	n, err = fb.buf.Write(p)
+
+	start := 0
+	index := bytes.IndexByte(p[:n], byte('\n'))
+	for index >= 0 {
+		fb.offsets = append(fb.offsets, fb.offset+start+index)
+		start += index + 1
+		index = bytes.IndexByte(p[start:n], byte('\n'))
+	}
+	fb.offset += n
+
+	return n, err
+}
+
+func (fb *FileBuffer) Segment(offset int) ([]byte, error) {
+	if len(fb.offsets) == 0 {
+		return fb.buf.Bytes(), nil
+	}
+
+	index := fb.findNearestLineIndex(offset)
+
+	start := 0
+	if index >= 0 {
+		start = fb.offsets[index] + 1
+	}
+
+	if start > fb.buf.Len()-1 {
+		return nil, io.EOF
+	}
+
+	var end int
+	if offset < fb.offsets[len(fb.offsets)-1] {
+		end = fb.offsets[index+1]
+	} else {
+		end = fb.buf.Len()
+	}
+
+	return fb.read(start, end)
+}
+
+func (fb *FileBuffer) Line(num int) ([]byte, error) {
+	if num > len(fb.offsets) {
+		return nil, fmt.Errorf("line %d outside of offsets", num)
+	}
+
+	start := 0
+	if num > 0 {
+		start = fb.offsets[num-1] + 1
+	}
+
+	end := fb.offsets[0]
+	if num > 0 {
+		end = fb.offsets[num]
+	}
+
+	return fb.read(start, end)
+}
+
+func (fb *FileBuffer) findNearestLineIndex(offset int) int {
+	index := sort.Search(len(fb.offsets), func(i int) bool {
+		return fb.offsets[i] >= offset
+	})
+
+	if index < len(fb.offsets) {
+		if fb.offsets[index] < offset {
+			return index
+		}
+		return index - 1
+	} else {
+		// If offset is further than any newline, then the last newline is the
+		// nearest.
+		return index - 1
+	}
+}
+
+func (fb *FileBuffer) read(start, end int) ([]byte, error) {
+	r := bytes.NewReader(fb.buf.Bytes())
+
+	_, err := r.Seek(int64(start), io.SeekStart)
+	if err != nil {
+		return nil, err
+	}
+
+	line := make([]byte, end-start)
+	n, err := r.Read(line)
+	if err != nil && err != io.EOF {
+		return nil, err
+	}
+
+	return line[:n], nil
+}
diff --git a/parser/parse.go b/parser/parse.go
index 164c6aa..a2186df 100644
--- a/parser/parse.go
+++ b/parser/parse.go
@@ -7,31 +7,34 @@ import (
 	"github.com/alecthomas/participle/lexer"
 )
 
-func Parse(r io.Reader) (*Module, error) {
+func Parse(r io.Reader) (*Module, *FileBuffer, error) {
 	name := lexer.NameOfReader(r)
 	if name == "" {
 		name = "<stdin>"
 	}
 	r = &NewlinedReader{Reader: r}
 
+	fb := NewFileBuffer(name)
+	r = io.TeeReader(r, fb)
+
 	mod := &Module{}
 	lex, err := Parser.Lexer().Lex(&NamedReader{r, name})
 	if err != nil {
-		return mod, err
+		return mod, fb, err
 	}
 
 	peeker, err := lexer.Upgrade(lex)
 	if err != nil {
-		return mod, err
+		return mod, fb, err
 	}
 
 	err = Parser.ParseFromLexer(peeker, mod)
 	if err != nil {
-		return mod, err
+		return mod, fb, err
 	}
 	AssignDocStrings(mod)
 
-	return mod, nil
+	return mod, fb, nil
 }
 
 type NamedReader struct {
@@ -43,6 +46,10 @@ func (nr *NamedReader) Name() string {
 	return nr.Value
 }
 
+func (nr NamedReader) Close() error {
+	return nil
+}
+
 // NewlinedReader appends one more newline after an EOF is reached, so that
 // parsing is made easier when inputs that don't end with a newline.
 type NewlinedReader struct {
diff --git a/parser/path.go b/parser/path.go
new file mode 100644
index 0000000..bf11c36
--- /dev/null
+++ b/parser/path.go
@@ -0,0 +1,20 @@
+package parser
+
+import (
+	"path/filepath"
+
+	homedir "github.com/mitchellh/go-homedir"
+)
+
+func ResolvePath(node Node, path string) (string, error) {
+	path, err := homedir.Expand(path)
+	if err != nil {
+		return path, err
+	}
+
+	if filepath.IsAbs(path) {
+		return path, nil
+	}
+
+	return filepath.Join(filepath.Dir(node.Position().Filename), path), nil
+}
diff --git a/report/helper.go b/report/helper.go
index 955b312..e054b56 100644
--- a/report/helper.go
+++ b/report/helper.go
@@ -71,13 +71,13 @@ func findMatchingStart(lex *lexer.PeekingLexer, start, end string, n int) (lexer
 	return token, n, nil
 }
 
-func getSegmentAndToken(ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (segment []byte, token lexer.Token, n int, err error) {
+func getSegmentAndToken(fb *parser.FileBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (segment []byte, token lexer.Token, n int, err error) {
 	token, n, err = searchToken(lex, unexpected.Pos.Offset)
 	if err != nil {
 		return
 	}
 
-	segment, err = getSegment(ib, token)
+	segment, err = getSegment(fb, token)
 	if err != nil {
 		return
 	}
@@ -85,12 +85,12 @@ func getSegmentAndToken(ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected l
 	return
 }
 
-func getSegment(ib *IndexedBuffer, token lexer.Token) ([]byte, error) {
+func getSegment(fb *parser.FileBuffer, token lexer.Token) ([]byte, error) {
 	if token.EOF() {
 		return []byte(token.String()), nil
 	}
 
-	segment, err := ib.Segment(token.Pos.Offset)
+	segment, err := fb.Segment(token.Pos.Offset)
 	if err != nil {
 		return segment, err
 	}
diff --git a/report/lexer.go b/report/lexer.go
index ebb0104..8a525d4 100644
--- a/report/lexer.go
+++ b/report/lexer.go
@@ -7,15 +7,16 @@ import (
 
 	"github.com/alecthomas/participle/lexer"
 	"github.com/logrusorgru/aurora"
+	"github.com/openllb/hlb/parser"
 )
 
-func NewLexerError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, err error) (error, error) {
+func NewLexerError(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, err error) (error, error) {
 	lerr, ok := err.(*lexer.Error)
 	if !ok {
 		return nil, err
 	}
 
-	r := bytes.NewReader(ib.buf.Bytes())
+	r := bytes.NewReader(fb.Bytes())
 	_, err = r.Seek(int64(lerr.Token().Pos.Offset), io.SeekStart)
 	if err != nil {
 		return nil, err
@@ -35,9 +36,9 @@ func NewLexerError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLex
 
 	unexpected := strings.TrimPrefix(lerr.Msg, "invalid token ")
 	if unexpected == `'"'` {
-		group, err = errLiteral(color, ib, lex, token)
+		group, err = errLiteral(color, fb, lex, token)
 	} else {
-		group, err = errToken(color, ib, lex, token)
+		group, err = errToken(color, fb, lex, token)
 	}
 	if err != nil {
 		return nil, err
@@ -47,8 +48,8 @@ func NewLexerError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLex
 	return Error{Groups: []AnnotationGroup{group}}, nil
 }
 
-func errLiteral(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
-	segment, err := getSegment(ib, token)
+func errLiteral(color aurora.Aurora, fb *parser.FileBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
+	segment, err := getSegment(fb, token)
 	if err != nil {
 		return group, err
 	}
@@ -66,8 +67,8 @@ func errLiteral(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, t
 	}, nil
 }
 
-func errToken(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
-	segment, err := getSegment(ib, token)
+func errToken(color aurora.Aurora, fb *parser.FileBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
+	segment, err := getSegment(fb, token)
 	if err != nil {
 		return group, err
 	}
diff --git a/report/report.go b/report/report.go
index 1da0fbc..5b3c1c8 100644
--- a/report/report.go
+++ b/report/report.go
@@ -3,8 +3,6 @@ package report
 import (
 	"bytes"
 	"fmt"
-	"io"
-	"sort"
 	"strings"
 	"unicode"
 
@@ -183,112 +181,3 @@ func (a Annotation) Lines(color aurora.Aurora) []string {
 
 	return lines
 }
-
-type IndexedBuffer struct {
-	buf     *bytes.Buffer
-	offset  int
-	offsets []int
-}
-
-func NewIndexedBuffer() *IndexedBuffer {
-	return &IndexedBuffer{
-		buf: new(bytes.Buffer),
-	}
-}
-
-func (ib *IndexedBuffer) Len() int {
-	return len(ib.offsets)
-}
-
-func (ib *IndexedBuffer) Write(p []byte) (n int, err error) {
-	n, err = ib.buf.Write(p)
-
-	start := 0
-	index := bytes.IndexByte(p[:n], byte('\n'))
-	for index >= 0 {
-		ib.offsets = append(ib.offsets, ib.offset+start+index)
-		start += index + 1
-		index = bytes.IndexByte(p[start:n], byte('\n'))
-	}
-	ib.offset += n
-
-	return n, err
-}
-
-func (ib *IndexedBuffer) Segment(offset int) ([]byte, error) {
-	if len(ib.offsets) == 0 {
-		return ib.buf.Bytes(), nil
-	}
-
-	index := ib.findNearestLineIndex(offset)
-
-	start := 0
-	if index >= 0 {
-		start = ib.offsets[index] + 1
-	}
-
-	if start > ib.buf.Len()-1 {
-		return nil, io.EOF
-	}
-
-	var end int
-	if offset < ib.offsets[len(ib.offsets)-1] {
-		end = ib.offsets[index+1]
-	} else {
-		end = ib.buf.Len()
-	}
-
-	return ib.read(start, end)
-}
-
-func (ib *IndexedBuffer) Line(num int) ([]byte, error) {
-	if num > len(ib.offsets) {
-		return nil, fmt.Errorf("line %d outside of offsets", num)
-	}
-
-	start := 0
-	if num > 0 {
-		start = ib.offsets[num-1] + 1
-	}
-
-	end := ib.offsets[0]
-	if num > 0 {
-		end = ib.offsets[num]
-	}
-
-	return ib.read(start, end)
-}
-
-func (ib *IndexedBuffer) findNearestLineIndex(offset int) int {
-	index := sort.Search(len(ib.offsets), func(i int) bool {
-		return ib.offsets[i] >= offset
-	})
-
-	if index < len(ib.offsets) {
-		if ib.offsets[index] < offset {
-			return index
-		}
-		return index - 1
-	} else {
-		// If offset is further than any newline, then the last newline is the
-		// nearest.
-		return index - 1
-	}
-}
-
-func (ib *IndexedBuffer) read(start, end int) ([]byte, error) {
-	r := bytes.NewReader(ib.buf.Bytes())
-
-	_, err := r.Seek(int64(start), io.SeekStart)
-	if err != nil {
-		return nil, err
-	}
-
-	line := make([]byte, end-start)
-	n, err := r.Read(line)
-	if err != nil && err != io.EOF {
-		return nil, err
-	}
-
-	return line[:n], nil
-}
diff --git a/report/syntax.go b/report/syntax.go
index 8a12ea8..082bd48 100644
--- a/report/syntax.go
+++ b/report/syntax.go
@@ -6,9 +6,10 @@ import (
 	"github.com/alecthomas/participle"
 	"github.com/alecthomas/participle/lexer"
 	"github.com/logrusorgru/aurora"
+	"github.com/openllb/hlb/parser"
 )
 
-func NewSyntaxError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, err error) (error, error) {
+func NewSyntaxError(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, err error) (error, error) {
 	perr, ok := err.(participle.Error)
 	if !ok {
 		return nil, err
@@ -29,24 +30,24 @@ func NewSyntaxError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLe
 		case "":
 			if !Contains(Types, unexpected.Value) {
 				// Invalid function type.
-				group, err = errFunc(color, ib, lex, unexpected)
+				group, err = errFunc(color, fb, lex, unexpected)
 			} else {
 				// Valid decl type but invalid name.
-				group, err = errFuncName(color, ib, lex, unexpected)
+				group, err = errFuncName(color, fb, lex, unexpected)
 			}
 		case `"("`:
 			// Missing signature.
-			group, err = errSignatureStart(color, ib, lex, unexpected)
+			group, err = errSignatureStart(color, fb, lex, unexpected)
 		case `")"`, "<ident>":
 			// Invalid signature.
-			group, err = errSignatureEnd(color, ib, lex, unexpected)
+			group, err = errSignatureEnd(color, fb, lex, unexpected)
 		case `"{"`:
 			// Missing block.
-			group, err = errBlockStart(color, ib, lex, unexpected)
+			group, err = errBlockStart(color, fb, lex, unexpected)
 		case `"}"`:
-			group, err = errBlockEnd(color, ib, lex, unexpected)
+			group, err = errBlockEnd(color, fb, lex, unexpected)
 		default:
-			group, err = errDefault(color, ib, lex, perr, unexpected)
+			group, err = errDefault(color, fb, lex, perr, unexpected)
 		}
 		if err != nil {
 			return nil, err
@@ -61,7 +62,7 @@ func NewSyntaxError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLe
 			return nil, err
 		}
 
-		group, err := errDefault(color, ib, lex, perr, token)
+		group, err := errDefault(color, fb, lex, perr, token)
 		if err != nil {
 			return nil, err
 		}
@@ -75,8 +76,8 @@ func NewSyntaxError(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLe
 	return Error{Groups: groups}, nil
 }
 
-func errFunc(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
-	segment, err := getSegment(ib, token)
+func errFunc(color aurora.Aurora, fb *parser.FileBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
+	segment, err := getSegment(fb, token)
 	if err != nil {
 		return group, err
 	}
@@ -102,8 +103,8 @@ func errFunc(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, toke
 	}, nil
 }
 
-func errFuncName(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
-	startSegment, startToken, n, err := getSegmentAndToken(ib, lex, unexpected)
+func errFuncName(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
+	startSegment, startToken, n, err := getSegmentAndToken(fb, lex, unexpected)
 	if err != nil {
 		return group, err
 	}
@@ -114,10 +115,10 @@ func errFuncName(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 	}
 
 	if isSymbol(endToken, "Type") {
-		return errKeyword(color, ib, lex, endToken)
+		return errKeyword(color, fb, lex, endToken)
 	}
 
-	endSegment, err := getSegment(ib, endToken)
+	endSegment, err := getSegment(fb, endToken)
 	if err != nil {
 		return group, err
 	}
@@ -145,8 +146,8 @@ func errFuncName(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 	}, nil
 }
 
-func errKeyword(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
-	segment, err := getSegment(ib, token)
+func errKeyword(color aurora.Aurora, fb *parser.FileBuffer, _ *lexer.PeekingLexer, token lexer.Token) (group AnnotationGroup, err error) {
+	segment, err := getSegment(fb, token)
 	if err != nil {
 		return group, err
 	}
@@ -166,8 +167,8 @@ func errKeyword(color aurora.Aurora, ib *IndexedBuffer, _ *lexer.PeekingLexer, t
 	}, nil
 }
 
-func errSignatureStart(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
-	endSegment, endToken, n, err := getSegmentAndToken(ib, lex, unexpected)
+func errSignatureStart(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
+	endSegment, endToken, n, err := getSegmentAndToken(fb, lex, unexpected)
 	if err != nil {
 		return group, err
 	}
@@ -177,7 +178,7 @@ func errSignatureStart(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.Peekin
 		return group, err
 	}
 
-	startSegment, err := getSegment(ib, startToken)
+	startSegment, err := getSegment(fb, startToken)
 	if err != nil {
 		return group, err
 	}
@@ -204,8 +205,8 @@ func errSignatureStart(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.Peekin
 	}, nil
 }
 
-func errSignatureEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
-	endSegment, endToken, n, err := getSegmentAndToken(ib, lex, unexpected)
+func errSignatureEnd(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
+	endSegment, endToken, n, err := getSegmentAndToken(fb, lex, unexpected)
 	if err != nil {
 		return group, err
 	}
@@ -232,11 +233,11 @@ func errSignatureEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingL
 		if (expected == "," && token.Value != ",") || (expected != "," && !isSymbol(token, expected)) {
 			switch expected {
 			case "Type":
-				return errArgType(color, ib, lex, m)
+				return errArgType(color, fb, lex, m)
 			case "Ident":
-				return errArgIdent(color, ib, lex, m)
+				return errArgIdent(color, fb, lex, m)
 			case ",":
-				return errArgDelim(color, ib, lex, m)
+				return errArgDelim(color, fb, lex, m)
 			}
 		}
 
@@ -250,7 +251,7 @@ func errSignatureEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingL
 		}
 	}
 
-	startSegment, err := getSegment(ib, startToken)
+	startSegment, err := getSegment(fb, startToken)
 	if err != nil {
 		return group, err
 	}
@@ -282,13 +283,13 @@ func errSignatureEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingL
 	}, nil
 }
 
-func errArgType(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, n int) (group AnnotationGroup, err error) {
+func errArgType(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, n int) (group AnnotationGroup, err error) {
 	startToken, err := lex.Peek(n - 1)
 	if err != nil {
 		return group, err
 	}
 
-	startSegment, err := getSegment(ib, startToken)
+	startSegment, err := getSegment(fb, startToken)
 	if err != nil {
 		return group, err
 	}
@@ -298,7 +299,7 @@ func errArgType(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer,
 		return group, err
 	}
 
-	endSegment, err := getSegment(ib, endToken)
+	endSegment, err := getSegment(fb, endToken)
 	if err != nil {
 		return group, err
 	}
@@ -328,13 +329,13 @@ func errArgType(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer,
 	}, nil
 }
 
-func errArgIdent(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, n int) (group AnnotationGroup, err error) {
+func errArgIdent(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, n int) (group AnnotationGroup, err error) {
 	startToken, err := lex.Peek(n - 1)
 	if err != nil {
 		return group, err
 	}
 
-	startSegment, err := getSegment(ib, startToken)
+	startSegment, err := getSegment(fb, startToken)
 	if err != nil {
 		return group, err
 	}
@@ -345,10 +346,10 @@ func errArgIdent(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 	}
 
 	if isSymbol(endToken, "Type") {
-		return errKeyword(color, ib, lex, endToken)
+		return errKeyword(color, fb, lex, endToken)
 	}
 
-	endSegment, err := getSegment(ib, endToken)
+	endSegment, err := getSegment(fb, endToken)
 	if err != nil {
 		return group, err
 	}
@@ -379,13 +380,13 @@ func errArgIdent(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 	}, nil
 }
 
-func errArgDelim(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, n int) (group AnnotationGroup, err error) {
+func errArgDelim(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, n int) (group AnnotationGroup, err error) {
 	token, err := lex.Peek(n - 1)
 	if err != nil {
 		return group, err
 	}
 
-	segment, err := getSegment(ib, token)
+	segment, err := getSegment(fb, token)
 	if err != nil {
 		return group, err
 	}
@@ -406,8 +407,8 @@ func errArgDelim(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 	}, nil
 }
 
-func errBlockStart(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
-	endSegment, endToken, n, err := getSegmentAndToken(ib, lex, unexpected)
+func errBlockStart(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
+	endSegment, endToken, n, err := getSegmentAndToken(fb, lex, unexpected)
 	if err != nil {
 		return group, err
 	}
@@ -417,7 +418,7 @@ func errBlockStart(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLex
 		return group, err
 	}
 
-	startSegment, err := getSegment(ib, startToken)
+	startSegment, err := getSegment(fb, startToken)
 	if err != nil {
 		return group, err
 	}
@@ -445,8 +446,8 @@ func errBlockStart(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLex
 	}, nil
 }
 
-func errBlockEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
-	endSegment, endToken, n, err := getSegmentAndToken(ib, lex, unexpected)
+func errBlockEnd(color aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, unexpected lexer.Token) (group AnnotationGroup, err error) {
+	endSegment, endToken, n, err := getSegmentAndToken(fb, lex, unexpected)
 	if err != nil {
 		return group, err
 	}
@@ -456,7 +457,7 @@ func errBlockEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 		return group, err
 	}
 
-	startSegment, err := getSegment(ib, startToken)
+	startSegment, err := getSegment(fb, startToken)
 	if err != nil {
 		return group, err
 	}
@@ -484,8 +485,8 @@ func errBlockEnd(color aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer
 	}, nil
 }
 
-func errDefault(_ aurora.Aurora, ib *IndexedBuffer, lex *lexer.PeekingLexer, perr participle.Error, unexpected lexer.Token) (group AnnotationGroup, err error) {
-	segment, token, _, err := getSegmentAndToken(ib, lex, unexpected)
+func errDefault(_ aurora.Aurora, fb *parser.FileBuffer, lex *lexer.PeekingLexer, perr participle.Error, unexpected lexer.Token) (group AnnotationGroup, err error) {
+	segment, token, _, err := getSegmentAndToken(fb, lex, unexpected)
 	if err != nil {
 		return group, err
 	}


Test patch:
diff --git a/checker/checker_test.go b/checker/checker_test.go
index 568f9f8..b2ef8f7 100644
--- a/checker/checker_test.go
+++ b/checker/checker_test.go
@@ -513,7 +513,7 @@ func TestChecker_Check(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			in := strings.NewReader(cleanup(tc.input))
 
-			mod, err := parser.Parse(in)
+			mod, _, err := parser.Parse(in)
 			require.NoError(t, err)
 
 			err = Check(mod)
@@ -611,14 +611,14 @@ func TestChecker_CheckSelectors(t *testing.T) {
 				option::image resolveImage() { resolve; }
 			`
 
-			importedModule, err := parser.Parse(strings.NewReader(importedModuleDefinition))
+			importedModule, _, err := parser.Parse(strings.NewReader(importedModuleDefinition))
 			require.NoError(t, err)
 			err = Check(importedModule)
 			require.NoError(t, err)
 
 			in := strings.NewReader(cleanup(tc.input))
 
-			module, err := parser.Parse(in)
+			module, _, err := parser.Parse(in)
 			require.NoError(t, err)
 
 			err = Check(module)
diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go
index 40c699a..b4463ec 100644
--- a/codegen/codegen_test.go
+++ b/codegen/codegen_test.go
@@ -793,7 +793,7 @@ func TestCodeGen(t *testing.T) {
 			cg, err := New()
 			require.NoError(t, err, tc.name)
 
-			mod, err := parser.Parse(strings.NewReader(cleanup(tc.input)))
+			mod, _, err := parser.Parse(strings.NewReader(cleanup(tc.input)))
 			require.NoError(t, err, tc.name)
 
 			err = checker.Check(mod)