commit
7d0fea5b60
|
@ -105,7 +105,7 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.solver.Solve(ctx, req.Ref, frontend, req.Definition, expi, req.FrontendAttrs); err != nil {
|
if err := c.solver.Solve(ctx, req.Ref, frontend, req.Definition, expi, req.FrontendAttrs, c.opt.Frontends); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &controlapi.SolveResponse{}, nil
|
return &controlapi.SolveResponse{}, nil
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
localexporter "github.com/moby/buildkit/exporter/local"
|
localexporter "github.com/moby/buildkit/exporter/local"
|
||||||
"github.com/moby/buildkit/frontend"
|
"github.com/moby/buildkit/frontend"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile"
|
"github.com/moby/buildkit/frontend/dockerfile"
|
||||||
|
"github.com/moby/buildkit/frontend/gateway"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
"github.com/moby/buildkit/snapshot/blobmapping"
|
"github.com/moby/buildkit/snapshot/blobmapping"
|
||||||
"github.com/moby/buildkit/source"
|
"github.com/moby/buildkit/source"
|
||||||
|
@ -128,6 +129,7 @@ func defaultControllerOpts(root string, pd pullDeps) (*Opt, error) {
|
||||||
|
|
||||||
frontends := map[string]frontend.Frontend{}
|
frontends := map[string]frontend.Frontend{}
|
||||||
frontends["dockerfile.v0"] = dockerfile.NewDockerfileFrontend()
|
frontends["dockerfile.v0"] = dockerfile.NewDockerfileFrontend()
|
||||||
|
frontends["gateway.v0"] = gateway.NewGatewayFrontend()
|
||||||
|
|
||||||
return &Opt{
|
return &Opt{
|
||||||
Snapshotter: snapshotter,
|
Snapshotter: snapshotter,
|
||||||
|
|
|
@ -115,7 +115,7 @@ func TestControl(t *testing.T) {
|
||||||
|
|
||||||
stderr := bytes.NewBuffer(nil)
|
stderr := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
err = w.Exec(ctx, meta, snap, nil, nil, &nopCloser{stderr})
|
err = w.Exec(ctx, meta, snap, nil, nil, nil, &nopCloser{stderr})
|
||||||
assert.Error(t, err) // Read-only root
|
assert.Error(t, err) // Read-only root
|
||||||
// typical error is like `mkdir /.../rootfs/proc: read-only file system`.
|
// typical error is like `mkdir /.../rootfs/proc: read-only file system`.
|
||||||
// make sure the error is caused before running `echo foo > /bar`.
|
// make sure the error is caused before running `echo foo > /bar`.
|
||||||
|
@ -124,7 +124,7 @@ func TestControl(t *testing.T) {
|
||||||
root, err := cm.New(ctx, snap)
|
root, err := cm.New(ctx, snap)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = w.Exec(ctx, meta, root, nil, nil, nil)
|
err = w.Exec(ctx, meta, root, nil, nil, nil, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rf, err := root.Commit(ctx)
|
rf, err := root.Commit(ctx)
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/moby/buildkit/cache"
|
"github.com/moby/buildkit/cache"
|
||||||
"github.com/moby/buildkit/cache/metadata"
|
"github.com/moby/buildkit/cache/metadata"
|
||||||
"github.com/moby/buildkit/exporter"
|
"github.com/moby/buildkit/exporter"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
|
||||||
"github.com/moby/buildkit/snapshot"
|
"github.com/moby/buildkit/snapshot"
|
||||||
"github.com/moby/buildkit/util/flightcontrol"
|
"github.com/moby/buildkit/util/flightcontrol"
|
||||||
"github.com/moby/buildkit/util/progress"
|
"github.com/moby/buildkit/util/progress"
|
||||||
|
@ -155,7 +154,7 @@ func (e *imageExporterInstance) Name() string {
|
||||||
return "exporting to image"
|
return "exporting to image"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string]interface{}) error {
|
func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string][]byte) error {
|
||||||
layersDone := oneOffProgress(ctx, "exporting layers")
|
layersDone := oneOffProgress(ctx, "exporting layers")
|
||||||
diffPairs, err := e.getBlobs(ctx, ref)
|
diffPairs, err := e.getBlobs(ctx, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -169,15 +168,10 @@ func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableR
|
||||||
}
|
}
|
||||||
|
|
||||||
var dt []byte
|
var dt []byte
|
||||||
if imgInterface, ok := opt[exporterImageConfig]; ok {
|
if config, ok := opt[exporterImageConfig]; ok {
|
||||||
img, ok := imgInterface.(*dockerfile2llb.Image)
|
dt, err = setDiffIDs(config, diffIDs)
|
||||||
if !ok {
|
|
||||||
return errors.Errorf("invalid image config")
|
|
||||||
}
|
|
||||||
setDiffIDs(img, diffIDs)
|
|
||||||
dt, err = json.Marshal(img)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to marshal image config")
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dt, err = json.Marshal(imageConfig(diffIDs))
|
dt, err = json.Marshal(imageConfig(diffIDs))
|
||||||
|
@ -270,9 +264,20 @@ func imageConfig(diffIDs []digest.Digest) ocispec.Image {
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDiffIDs(img *dockerfile2llb.Image, diffIDs []digest.Digest) {
|
func setDiffIDs(config []byte, diffIDs []digest.Digest) ([]byte, error) {
|
||||||
img.RootFS.Type = "layers"
|
mp := map[string]json.RawMessage{}
|
||||||
img.RootFS.DiffIDs = diffIDs
|
if err := json.Unmarshal(config, &mp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var rootFS ocispec.RootFS
|
||||||
|
rootFS.Type = "layers"
|
||||||
|
rootFS.DiffIDs = diffIDs
|
||||||
|
dt, err := json.Marshal(rootFS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mp["rootfs"] = dt
|
||||||
|
return json.Marshal(mp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
||||||
|
|
|
@ -11,5 +11,5 @@ type Exporter interface {
|
||||||
|
|
||||||
type ExporterInstance interface {
|
type ExporterInstance interface {
|
||||||
Name() string
|
Name() string
|
||||||
Export(context.Context, cache.ImmutableRef, map[string]interface{}) error
|
Export(context.Context, cache.ImmutableRef, map[string][]byte) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (e *localExporterInstance) Name() string {
|
||||||
return "exporting to client"
|
return "exporting to client"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *localExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string]interface{}) error {
|
func (e *localExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string][]byte) error {
|
||||||
mount, err := ref.Mount(ctx, true)
|
mount, err := ref.Mount(ctx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
FROM golang:1.8-alpine AS builder
|
||||||
|
COPY . /go/src/github.com/moby/buildkit
|
||||||
|
RUN CGO_ENABLED=0 go build -o /dockerfile-frontend --ldflags '-extldflags "-static"' github.com/moby/buildkit/frontend/dockerfile/cmd/dockerfile-frontend
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=builder /dockerfile-frontend /bin/dockerfile-frontend
|
||||||
|
ENTRYPOINT ["/bin/dockerfile-frontend"]
|
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/client/llb"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||||
|
"github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyTarget = "target"
|
||||||
|
keyFilename = "filename"
|
||||||
|
exporterImageConfig = "containerimage.config"
|
||||||
|
defaultDockerfileName = "Dockerfile"
|
||||||
|
localNameDockerfile = "dockerfile"
|
||||||
|
buildArgPrefix = "build-arg:"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := run(); err != nil {
|
||||||
|
logrus.Errorf("fatal error: %+v", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
c, err := client.Current()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
|
opts := c.Opts()
|
||||||
|
|
||||||
|
filename := opts[keyFilename]
|
||||||
|
if filename == "" {
|
||||||
|
filename = defaultDockerfileName
|
||||||
|
}
|
||||||
|
if path.Base(filename) != filename {
|
||||||
|
return errors.Errorf("invalid filename: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
src := llb.Local(localNameDockerfile,
|
||||||
|
llb.IncludePatterns([]string{filename}),
|
||||||
|
llb.SessionID(c.SessionID()),
|
||||||
|
)
|
||||||
|
def, err := src.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := c.Solve(ctx, def.ToPB(), "", nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dtDockerfile, err := ref.ReadFile(ctx, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{
|
||||||
|
Target: opts[keyTarget],
|
||||||
|
MetaResolver: c,
|
||||||
|
BuildArgs: filterBuildArgs(opts),
|
||||||
|
SessionID: c.SessionID(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
def, err = st.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := json.Marshal(img)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Solve(ctx, def.ToPB(), "", map[string][]byte{
|
||||||
|
exporterImageConfig: config,
|
||||||
|
}, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterBuildArgs(opt map[string]string) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
for k, v := range opt {
|
||||||
|
if strings.HasPrefix(k, buildArgPrefix) {
|
||||||
|
m[strings.TrimPrefix(k, buildArgPrefix)] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package dockerfile
|
package dockerfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/frontend"
|
"github.com/moby/buildkit/frontend"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
"github.com/moby/buildkit/snapshot"
|
"github.com/moby/buildkit/snapshot"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -30,7 +32,7 @@ func NewDockerfileFrontend() frontend.Frontend {
|
||||||
|
|
||||||
type dfFrontend struct{}
|
type dfFrontend struct{}
|
||||||
|
|
||||||
func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (retRef cache.ImmutableRef, exporterAttr map[string]interface{}, retErr error) {
|
func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (retRef cache.ImmutableRef, exporterAttr map[string][]byte, retErr error) {
|
||||||
|
|
||||||
filename := opts[keyFilename]
|
filename := opts[keyFilename]
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
|
@ -40,14 +42,18 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
|
||||||
return nil, nil, errors.Errorf("invalid filename %s", filename)
|
return nil, nil, errors.Errorf("invalid filename %s", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
src := llb.Local(localNameDockerfile, llb.IncludePatterns([]string{filename}))
|
sid := session.FromContext(ctx)
|
||||||
|
|
||||||
|
src := llb.Local(localNameDockerfile,
|
||||||
|
llb.IncludePatterns([]string{filename}),
|
||||||
|
llb.SessionID(sid),
|
||||||
|
)
|
||||||
def, err := src.Marshal()
|
def, err := src.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defPB := def.ToPB()
|
|
||||||
|
|
||||||
ref, err := llbBridge.Solve(ctx, defPB)
|
ref, _, err := llbBridge.Solve(ctx, def.ToPB(), "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -95,6 +101,7 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
|
||||||
Target: opts[keyTarget],
|
Target: opts[keyTarget],
|
||||||
MetaResolver: llbBridge,
|
MetaResolver: llbBridge,
|
||||||
BuildArgs: filterBuildArgs(opts),
|
BuildArgs: filterBuildArgs(opts),
|
||||||
|
SessionID: sid,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,14 +112,18 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defPB = def.ToPB()
|
retRef, _, err = llbBridge.Solve(ctx, def.ToPB(), "", nil)
|
||||||
retRef, err = llbBridge.Solve(ctx, defPB)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return retRef, map[string]interface{}{
|
config, err := json.Marshal(img)
|
||||||
exporterImageConfig: img,
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return retRef, map[string][]byte{
|
||||||
|
exporterImageConfig: config,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ type ConvertOpt struct {
|
||||||
Target string
|
Target string
|
||||||
MetaResolver llb.ImageMetaResolver
|
MetaResolver llb.ImageMetaResolver
|
||||||
BuildArgs map[string]string
|
BuildArgs map[string]string
|
||||||
|
SessionID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) {
|
func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) {
|
||||||
|
@ -157,6 +158,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
|
||||||
metaArgs: metaArgs,
|
metaArgs: metaArgs,
|
||||||
buildArgValues: opt.BuildArgs,
|
buildArgValues: opt.BuildArgs,
|
||||||
shlex: shlex,
|
shlex: shlex,
|
||||||
|
sessionID: opt.SessionID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil {
|
if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil {
|
||||||
|
@ -187,6 +189,7 @@ type dispatchOpt struct {
|
||||||
metaArgs []instructions.ArgCommand
|
metaArgs []instructions.ArgCommand
|
||||||
buildArgValues map[string]string
|
buildArgValues map[string]string
|
||||||
shlex *ShellLex
|
shlex *ShellLex
|
||||||
|
sessionID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error {
|
func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error {
|
||||||
|
@ -208,7 +211,7 @@ func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error
|
||||||
case *instructions.WorkdirCommand:
|
case *instructions.WorkdirCommand:
|
||||||
err = dispatchWorkdir(d, c)
|
err = dispatchWorkdir(d, c)
|
||||||
case *instructions.AddCommand:
|
case *instructions.AddCommand:
|
||||||
err = dispatchCopy(d, c.SourcesAndDest, llb.Local(localNameContext))
|
err = dispatchCopy(d, c.SourcesAndDest, llb.Local(localNameContext, llb.SessionID(opt.sessionID)))
|
||||||
case *instructions.LabelCommand:
|
case *instructions.LabelCommand:
|
||||||
err = dispatchLabel(d, c)
|
err = dispatchLabel(d, c)
|
||||||
case *instructions.OnbuildCommand:
|
case *instructions.OnbuildCommand:
|
||||||
|
@ -232,7 +235,7 @@ func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error
|
||||||
case *instructions.ArgCommand:
|
case *instructions.ArgCommand:
|
||||||
err = dispatchArg(d, c, opt.metaArgs, opt.buildArgValues)
|
err = dispatchArg(d, c, opt.metaArgs, opt.buildArgValues)
|
||||||
case *instructions.CopyCommand:
|
case *instructions.CopyCommand:
|
||||||
l := llb.Local(localNameContext)
|
l := llb.Local(localNameContext, llb.SessionID(opt.sessionID))
|
||||||
if c.From != "" {
|
if c.From != "" {
|
||||||
index, err := strconv.Atoi(c.From)
|
index, err := strconv.Atoi(c.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
digest "github.com/opencontainers/go-digest"
|
"io"
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/moby/buildkit/cache"
|
"github.com/moby/buildkit/cache"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/moby/buildkit/worker"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Frontend interface {
|
type Frontend interface {
|
||||||
Solve(ctx context.Context, llbBridge FrontendLLBBridge, opt map[string]string) (cache.ImmutableRef, map[string]interface{}, error)
|
Solve(ctx context.Context, llb FrontendLLBBridge, opt map[string]string) (cache.ImmutableRef, map[string][]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FrontendLLBBridge interface {
|
type FrontendLLBBridge interface {
|
||||||
Solve(ctx context.Context, def *pb.Definition) (cache.ImmutableRef, error)
|
Solve(ctx context.Context, def *pb.Definition, frontend string, opts map[string]string) (cache.ImmutableRef, map[string][]byte, error)
|
||||||
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
||||||
|
Exec(ctx context.Context, meta worker.Meta, rootfs cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
|
opspb "github.com/moby/buildkit/solver/pb"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const frontendPrefix = "BUILDKIT_FRONTEND_OPT_"
|
||||||
|
|
||||||
|
func Current() (*Client, error) {
|
||||||
|
ctx, conn, err := grpcClientConn(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := pb.NewLLBBridgeClient(conn)
|
||||||
|
|
||||||
|
_, err = c.Ping(ctx, &pb.PingRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{client: c, opts: opts(), sessionID: sessionID()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
client pb.LLBBridgeClient
|
||||||
|
opts map[string]string
|
||||||
|
sessionID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Solve(ctx context.Context, def *opspb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (*Reference, error) {
|
||||||
|
dt, err := json.Marshal(exporterAttr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req := &pb.SolveRequest{Definition: def, Frontend: frontend, Final: final, ExporterAttr: dt}
|
||||||
|
resp, err := c.client.Solve(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Reference{id: resp.Ref, c: c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) {
|
||||||
|
resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{Ref: ref})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return resp.Digest, resp.Config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Opts() map[string]string {
|
||||||
|
return c.opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SessionID() string {
|
||||||
|
return c.sessionID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reference struct {
|
||||||
|
id string
|
||||||
|
c *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reference) ReadFile(ctx context.Context, fp string) ([]byte, error) {
|
||||||
|
resp, err := r.c.client.ReadFile(ctx, &pb.ReadFileRequest{FilePath: fp, Ref: r.id})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
|
||||||
|
dialOpt := grpc.WithDialer(func(addr string, d time.Duration) (net.Conn, error) {
|
||||||
|
return stdioConn(), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cc, err := grpc.DialContext(ctx, "", dialOpt, grpc.WithInsecure())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "failed to create grpc client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
_ = cancel
|
||||||
|
// go monitorHealth(ctx, cc, cancel)
|
||||||
|
|
||||||
|
return ctx, cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stdioConn() net.Conn {
|
||||||
|
return &conn{os.Stdin, os.Stdout, os.Stdout}
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *conn) LocalAddr() net.Addr {
|
||||||
|
return dummyAddr{}
|
||||||
|
}
|
||||||
|
func (s *conn) RemoteAddr() net.Addr {
|
||||||
|
return dummyAddr{}
|
||||||
|
}
|
||||||
|
func (s *conn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *conn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyAddr struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyAddr) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyAddr) String() string {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
func opts() map[string]string {
|
||||||
|
opts := map[string]string{}
|
||||||
|
for _, env := range os.Environ() {
|
||||||
|
parts := strings.SplitN(env, "=", 2)
|
||||||
|
k := parts[0]
|
||||||
|
v := ""
|
||||||
|
if len(parts) == 2 {
|
||||||
|
v = parts[1]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(k, frontendPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = strings.SplitN(v, "=", 2)
|
||||||
|
v = ""
|
||||||
|
if len(parts) == 2 {
|
||||||
|
v = parts[1]
|
||||||
|
}
|
||||||
|
opts[parts[0]] = v
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionID() string {
|
||||||
|
return os.Getenv("BUILDKIT_SESSION_ID")
|
||||||
|
}
|
|
@ -0,0 +1,315 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/moby/buildkit/cache"
|
||||||
|
"github.com/moby/buildkit/client/llb"
|
||||||
|
"github.com/moby/buildkit/frontend"
|
||||||
|
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
|
"github.com/moby/buildkit/identity"
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/moby/buildkit/snapshot"
|
||||||
|
"github.com/moby/buildkit/worker"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/health"
|
||||||
|
"google.golang.org/grpc/health/grpc_health_v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keySource = "source"
|
||||||
|
keyDevel = "gateway-devel"
|
||||||
|
exporterImageConfig = "containerimage.config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewGatewayFrontend() frontend.Frontend {
|
||||||
|
return &gatewayFrontend{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gatewayFrontend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterPrefix(opts map[string]string, pfx string) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
for k, v := range opts {
|
||||||
|
if strings.HasPrefix(k, pfx) {
|
||||||
|
m[strings.TrimPrefix(k, pfx)] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (retRef cache.ImmutableRef, exporterAttr map[string][]byte, retErr error) {
|
||||||
|
|
||||||
|
source, ok := opts[keySource]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, errors.Errorf("no source specified for gateway")
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := session.FromContext(ctx)
|
||||||
|
|
||||||
|
_, isDevel := opts[keyDevel]
|
||||||
|
var img ocispec.Image
|
||||||
|
var rootFS cache.ImmutableRef
|
||||||
|
|
||||||
|
if isDevel {
|
||||||
|
ref, exp, err := llbBridge.Solve(session.NewContext(ctx, "gateway:"+sid), nil, source, filterPrefix(opts, "gateway-"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
rootFS = ref
|
||||||
|
config, ok := exp[exporterImageConfig]
|
||||||
|
if ok {
|
||||||
|
if err := json.Unmarshal(config, &img); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourceRef, err := reference.ParseNormalizedNamed(source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst, config, err := llbBridge.ResolveImageConfig(ctx, sourceRef.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(config, &img); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceRef, err = reference.WithDigest(sourceRef, dgst)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
src := llb.Image(sourceRef.String())
|
||||||
|
|
||||||
|
def, err := src.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, _, err := llbBridge.Solve(ctx, def.ToPB(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
rootFS = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
lbf, err := newLLBBrideForwarder(ctx, llbBridge)
|
||||||
|
defer lbf.conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"/run"}
|
||||||
|
env := []string{}
|
||||||
|
cwd := "/"
|
||||||
|
if img.Config.Env != nil {
|
||||||
|
env = img.Config.Env
|
||||||
|
}
|
||||||
|
if img.Config.Entrypoint != nil {
|
||||||
|
args = img.Config.Entrypoint
|
||||||
|
}
|
||||||
|
if img.Config.WorkingDir != "" {
|
||||||
|
cwd = img.Config.WorkingDir
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for k, v := range opts {
|
||||||
|
env = append(env, fmt.Sprintf("BUILDKIT_FRONTEND_OPT_%d", i)+"="+k+"="+v)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
env = append(env, "BUILDKIT_SESSION_ID="+sid)
|
||||||
|
|
||||||
|
err = llbBridge.Exec(ctx, worker.Meta{
|
||||||
|
Env: env,
|
||||||
|
Args: args,
|
||||||
|
Cwd: cwd,
|
||||||
|
}, rootFS, lbf.Stdin, lbf.Stdout, os.Stderr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lbf.lastRef, lbf.exporterAttr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLLBBrideForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge) (*llbBrideForwarder, error) {
|
||||||
|
lbf := &llbBrideForwarder{
|
||||||
|
llbBridge: llbBridge,
|
||||||
|
refs: map[string]cache.ImmutableRef{},
|
||||||
|
pipe: newPipe(),
|
||||||
|
}
|
||||||
|
|
||||||
|
server := grpc.NewServer()
|
||||||
|
grpc_health_v1.RegisterHealthServer(server, health.NewServer())
|
||||||
|
pb.RegisterLLBBridgeServer(server, lbf)
|
||||||
|
|
||||||
|
go serve(ctx, server, lbf.conn)
|
||||||
|
|
||||||
|
return lbf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipe struct {
|
||||||
|
Stdin io.ReadCloser
|
||||||
|
Stdout io.WriteCloser
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPipe() *pipe {
|
||||||
|
pr1, pw1, _ := os.Pipe()
|
||||||
|
pr2, pw2, _ := os.Pipe()
|
||||||
|
return &pipe{
|
||||||
|
Stdin: pr1,
|
||||||
|
Stdout: pw2,
|
||||||
|
conn: &conn{
|
||||||
|
Reader: pr2,
|
||||||
|
Writer: pw1,
|
||||||
|
Closer: pw2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *conn) LocalAddr() net.Addr {
|
||||||
|
return dummyAddr{}
|
||||||
|
}
|
||||||
|
func (s *conn) RemoteAddr() net.Addr {
|
||||||
|
return dummyAddr{}
|
||||||
|
}
|
||||||
|
func (s *conn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *conn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyAddr struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyAddr) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyAddr) String() string {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
type llbBrideForwarder struct {
|
||||||
|
llbBridge frontend.FrontendLLBBridge
|
||||||
|
refs map[string]cache.ImmutableRef
|
||||||
|
lastRef cache.ImmutableRef
|
||||||
|
exporterAttr map[string][]byte
|
||||||
|
*pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lbf *llbBrideForwarder) ResolveImageConfig(ctx context.Context, req *pb.ResolveImageConfigRequest) (*pb.ResolveImageConfigResponse, error) {
|
||||||
|
dgst, dt, err := lbf.llbBridge.ResolveImageConfig(ctx, req.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.ResolveImageConfigResponse{
|
||||||
|
Digest: dgst,
|
||||||
|
Config: dt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lbf *llbBrideForwarder) Solve(ctx context.Context, req *pb.SolveRequest) (*pb.SolveResponse, error) {
|
||||||
|
ref, expResp, err := lbf.llbBridge.Solve(ctx, req.Definition, req.Frontend, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := map[string][]byte{}
|
||||||
|
if err := json.Unmarshal(req.ExporterAttr, &exp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if expResp != nil {
|
||||||
|
for k, v := range expResp {
|
||||||
|
exp[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id := identity.NewID()
|
||||||
|
lbf.refs[id] = ref
|
||||||
|
if req.Final {
|
||||||
|
lbf.lastRef = ref
|
||||||
|
lbf.exporterAttr = exp
|
||||||
|
}
|
||||||
|
return &pb.SolveResponse{Ref: id}, nil
|
||||||
|
}
|
||||||
|
func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) {
|
||||||
|
ref, ok := lbf.refs[req.Ref]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
mount, err := ref.Mount(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := snapshot.LocalMounter(mount)
|
||||||
|
|
||||||
|
root, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if lm != nil {
|
||||||
|
lm.Unmount()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
dt, err := ioutil.ReadFile(filepath.Join(root, req.FilePath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := lm.Unmount(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lm = nil
|
||||||
|
|
||||||
|
return &pb.ReadFileResponse{Data: dt}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lbf *llbBrideForwarder) Ping(context.Context, *pb.PingRequest) (*pb.PongResponse, error) {
|
||||||
|
return &pb.PongResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serve(ctx context.Context, grpcServer *grpc.Server, conn net.Conn) {
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
logrus.Debugf("serving grpc connection")
|
||||||
|
(&http2.Server{}).ServeConn(conn, &http2.ServeConnOpts{Handler: grpcServer})
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package moby.buildkit.v1.frontend;
|
||||||
|
|
||||||
|
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||||
|
import "github.com/moby/buildkit/solver/pb/ops.proto";
|
||||||
|
|
||||||
|
option (gogoproto.sizer_all) = true;
|
||||||
|
option (gogoproto.marshaler_all) = true;
|
||||||
|
option (gogoproto.unmarshaler_all) = true;
|
||||||
|
|
||||||
|
service LLBBridge {
|
||||||
|
rpc ResolveImageConfig(ResolveImageConfigRequest) returns (ResolveImageConfigResponse);
|
||||||
|
rpc Solve(SolveRequest) returns (SolveResponse);
|
||||||
|
rpc ReadFile(ReadFileRequest) returns (ReadFileResponse);
|
||||||
|
rpc Ping(PingRequest) returns (PongResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResolveImageConfigRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResolveImageConfigResponse {
|
||||||
|
string Digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
|
||||||
|
bytes Config = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SolveRequest {
|
||||||
|
pb.Definition Definition = 1;
|
||||||
|
string Frontend = 2;
|
||||||
|
bool Final = 10;
|
||||||
|
bytes ExporterAttr = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SolveResponse {
|
||||||
|
string Ref = 1; // can be used by readfile request
|
||||||
|
bytes ExporterAttr = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReadFileRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
string FilePath = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReadFileResponse {
|
||||||
|
bytes Data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PingRequest{
|
||||||
|
}
|
||||||
|
message PongResponse{
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package moby_buildkit_v1_frontend
|
||||||
|
|
||||||
|
//go:generate protoc -I=. -I=../../../vendor/ -I=/Users/tonistiigi/gocode/src --gogo_out=plugins=grpc:. gateway.proto
|
|
@ -110,7 +110,7 @@ func (e *execOp) Run(ctx context.Context, inputs []Reference) ([]Reference, erro
|
||||||
defer stdout.Close()
|
defer stdout.Close()
|
||||||
defer stderr.Close()
|
defer stderr.Close()
|
||||||
|
|
||||||
if err := e.w.Exec(ctx, meta, root, mounts, stdout, stderr); err != nil {
|
if err := e.w.Exec(ctx, meta, root, mounts, nil, stdout, stderr); err != nil {
|
||||||
return nil, errors.Wrapf(err, "worker failed running %v", meta.Args)
|
return nil, errors.Wrapf(err, "worker failed running %v", meta.Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package solver
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ func NewLLBSolver(opt LLBOpt) *Solver {
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}, opt.InstructionCache, opt.ImageSource)
|
}, opt.InstructionCache, opt.ImageSource, opt.Worker, opt.CacheManager)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +77,15 @@ type Solver struct {
|
||||||
jobs *jobList
|
jobs *jobList
|
||||||
cache InstructionCache
|
cache InstructionCache
|
||||||
imageSource source.Source
|
imageSource source.Source
|
||||||
|
worker worker.Worker
|
||||||
|
cm cache.Manager // TODO: remove with immutableRef.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(resolve ResolveOpFunc, cache InstructionCache, imageSource source.Source) *Solver {
|
func New(resolve ResolveOpFunc, cache InstructionCache, imageSource source.Source, worker worker.Worker, cm cache.Manager) *Solver {
|
||||||
return &Solver{resolve: resolve, jobs: newJobList(), cache: cache, imageSource: imageSource}
|
return &Solver{resolve: resolve, jobs: newJobList(), cache: cache, imageSource: imageSource, worker: worker, cm: cm}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def *pb.Definition, exp exporter.ExporterInstance, frontendOpt map[string]string) error {
|
func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def *pb.Definition, exp exporter.ExporterInstance, frontendOpt map[string]string, allFrontends map[string]frontend.Frontend) error {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -96,7 +99,7 @@ func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def
|
||||||
}
|
}
|
||||||
|
|
||||||
var ref Reference
|
var ref Reference
|
||||||
var exporterOpt map[string]interface{}
|
var exporterOpt map[string][]byte
|
||||||
if def != nil {
|
if def != nil {
|
||||||
var inp *Input
|
var inp *Input
|
||||||
inp, err = j.load(def, s.resolve)
|
inp, err = j.load(def, s.resolve)
|
||||||
|
@ -107,9 +110,12 @@ func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def
|
||||||
ref, err = j.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
|
ref, err = j.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
|
||||||
} else {
|
} else {
|
||||||
ref, exporterOpt, err = f.Solve(ctx, &llbBridge{
|
ref, exporterOpt, err = f.Solve(ctx, &llbBridge{
|
||||||
|
worker: s.worker,
|
||||||
job: j,
|
job: j,
|
||||||
|
cm: s.cm,
|
||||||
resolveOp: s.resolve,
|
resolveOp: s.resolve,
|
||||||
resolveImageConfig: s.imageSource.(resolveImageConfig),
|
resolveImageConfig: s.imageSource.(resolveImageConfig),
|
||||||
|
allFrontends: allFrontends,
|
||||||
}, frontendOpt)
|
}, frontendOpt)
|
||||||
}
|
}
|
||||||
j.discard()
|
j.discard()
|
||||||
|
@ -568,27 +574,54 @@ type llbBridge struct {
|
||||||
resolveImageConfig
|
resolveImageConfig
|
||||||
job *job
|
job *job
|
||||||
resolveOp ResolveOpFunc
|
resolveOp ResolveOpFunc
|
||||||
|
worker worker.Worker
|
||||||
|
allFrontends map[string]frontend.Frontend
|
||||||
|
cm cache.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolveImageConfig interface {
|
type resolveImageConfig interface {
|
||||||
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *llbBridge) Solve(ctx context.Context, def *pb.Definition) (cache.ImmutableRef, error) {
|
func (s *llbBridge) Solve(ctx context.Context, def *pb.Definition, frontend string, opts map[string]string) (cache.ImmutableRef, map[string][]byte, error) {
|
||||||
inp, err := s.job.load(def, s.resolveOp)
|
if def == nil {
|
||||||
if err != nil {
|
f, ok := s.allFrontends[frontend]
|
||||||
return nil, err
|
if !ok {
|
||||||
|
return nil, nil, errors.Errorf("invalid frontend: %s", frontend)
|
||||||
}
|
}
|
||||||
ref, err := s.job.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
|
ref, exporterOpt, err := f.Solve(ctx, s, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
immutable, ok := toImmutableRef(ref)
|
immutable, ok := toImmutableRef(ref)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Errorf("invalid reference for exporting: %T", ref)
|
return nil, nil, errors.Errorf("invalid reference for exporting: %T", ref)
|
||||||
|
}
|
||||||
|
return immutable, exporterOpt, nil
|
||||||
|
}
|
||||||
|
inp, err := s.job.load(def, s.resolveOp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ref, err := s.job.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
immutable, ok := toImmutableRef(ref)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, errors.Errorf("invalid reference for exporting: %T", ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
return immutable, nil
|
return immutable, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *llbBridge) Exec(ctx context.Context, meta worker.Meta, rootFS cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
||||||
|
active, err := s.cm.New(ctx, rootFS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer active.Release(context.TODO())
|
||||||
|
return s.worker.Exec(ctx, meta, active, nil, stdin, stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cacheKeyForIndex(dgst digest.Digest, index Index) digest.Digest {
|
func cacheKeyForIndex(dgst digest.Digest, index Index) digest.Digest {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package solver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
@ -51,6 +52,10 @@ func (s *sourceOp) instance(ctx context.Context) (source.SourceInstance, error)
|
||||||
switch k {
|
switch k {
|
||||||
case pb.AttrLocalSessionID:
|
case pb.AttrLocalSessionID:
|
||||||
id.SessionID = v
|
id.SessionID = v
|
||||||
|
if p := strings.SplitN(v, ":", 2); len(p) == 2 {
|
||||||
|
id.Name = p[0] + "-" + id.Name
|
||||||
|
id.SessionID = p[1]
|
||||||
|
}
|
||||||
case pb.AttrIncludePatterns:
|
case pb.AttrIncludePatterns:
|
||||||
var patterns []string
|
var patterns []string
|
||||||
if err := json.Unmarshal([]byte(v), &patterns); err != nil {
|
if err := json.Unmarshal([]byte(v), &patterns); err != nil {
|
||||||
|
|
|
@ -22,7 +22,7 @@ func New(client *containerd.Client) worker.Worker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdout, stderr io.WriteCloser) error {
|
func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
||||||
id := identity.NewID()
|
id := identity.NewID()
|
||||||
|
|
||||||
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts)
|
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts)
|
||||||
|
@ -44,7 +44,11 @@ func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache
|
||||||
}
|
}
|
||||||
defer container.Delete(ctx)
|
defer container.Delete(ctx)
|
||||||
|
|
||||||
task, err := container.NewTask(ctx, containerd.Stdio, containerd.WithRootFS(rootMounts))
|
if stdin == nil {
|
||||||
|
stdin = &emptyReadCloser{}
|
||||||
|
}
|
||||||
|
|
||||||
|
task, err := container.NewTask(ctx, containerd.NewIO(stdin, stdout, stderr), containerd.WithRootFS(rootMounts))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -69,3 +73,13 @@ func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type emptyReadCloser struct{}
|
||||||
|
|
||||||
|
func (_ *emptyReadCloser) Read([]byte) (int, error) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *emptyReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ func New(root string) (worker.Worker, error) {
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdout, stderr io.WriteCloser) error {
|
func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
||||||
|
|
||||||
rootMount, err := root.Mount(ctx, false)
|
rootMount, err := root.Mount(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -107,7 +107,7 @@ func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Moun
|
||||||
logrus.Debugf("> running %s %v", id, meta.Args)
|
logrus.Debugf("> running %s %v", id, meta.Args)
|
||||||
|
|
||||||
status, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
|
status, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
|
||||||
IO: &forwardIO{stdout: stdout, stderr: stderr},
|
IO: &forwardIO{stdin: stdin, stdout: stdout, stderr: stderr},
|
||||||
})
|
})
|
||||||
logrus.Debugf("< completed %s %v %v", id, status, err)
|
logrus.Debugf("< completed %s %v %v", id, status, err)
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
|
@ -124,6 +124,7 @@ func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Moun
|
||||||
}
|
}
|
||||||
|
|
||||||
type forwardIO struct {
|
type forwardIO struct {
|
||||||
|
stdin io.ReadCloser
|
||||||
stdout, stderr io.WriteCloser
|
stdout, stderr io.WriteCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +133,7 @@ func (s *forwardIO) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *forwardIO) Set(cmd *exec.Cmd) {
|
func (s *forwardIO) Set(cmd *exec.Cmd) {
|
||||||
|
cmd.Stdin = s.stdin
|
||||||
cmd.Stdout = s.stdout
|
cmd.Stdout = s.stdout
|
||||||
cmd.Stderr = s.stderr
|
cmd.Stderr = s.stderr
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,5 @@ type Mount struct {
|
||||||
|
|
||||||
type Worker interface {
|
type Worker interface {
|
||||||
// TODO: add stdout/err
|
// TODO: add stdout/err
|
||||||
Exec(ctx context.Context, meta Meta, rootfs cache.Mountable, mounts []Mount, stdout, stderr io.WriteCloser) error
|
Exec(ctx context.Context, meta Meta, rootfs cache.Mountable, mounts []Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue