Merge pull request #213 from tonistiigi/dockerfile-dedupe
frontend: make dockerfile.v0 and external frontend use same codedocker-18.09
commit
8fa227c801
|
@ -0,0 +1,45 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/symlink"
|
||||||
|
"github.com/moby/buildkit/snapshot"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadFile(ctx context.Context, ref ImmutableRef, p string) ([]byte, error) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fp, err := symlink.FollowSymlinkInScope(filepath.Join(root, p), root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := ioutil.ReadFile(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := lm.Unmount(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lm = nil
|
||||||
|
return dt, err
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocalNameContext = "context"
|
||||||
|
LocalNameDockerfile = "dockerfile"
|
||||||
|
keyTarget = "target"
|
||||||
|
keyFilename = "filename"
|
||||||
|
exporterImageConfig = "containerimage.config"
|
||||||
|
defaultDockerfileName = "Dockerfile"
|
||||||
|
buildArgPrefix = "build-arg:"
|
||||||
|
gitPrefix = "git://"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Build(ctx context.Context, c client.Client) error {
|
||||||
|
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()),
|
||||||
|
)
|
||||||
|
var buildContext *llb.State
|
||||||
|
if strings.HasPrefix(opts[LocalNameContext], gitPrefix) {
|
||||||
|
src = parseGitSource(opts[LocalNameContext])
|
||||||
|
buildContext = &src
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
BuildContext: buildContext,
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGitSource(ref string) llb.State {
|
||||||
|
ref = strings.TrimPrefix(ref, gitPrefix)
|
||||||
|
parts := strings.SplitN(ref, "#", 2)
|
||||||
|
branch := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
branch = parts[1]
|
||||||
|
}
|
||||||
|
return llb.Git(parts[0], branch)
|
||||||
|
}
|
|
@ -1,29 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/moby/buildkit/frontend/dockerfile/builder"
|
||||||
"path"
|
"github.com/moby/buildkit/frontend/gateway/grpcclient"
|
||||||
"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/moby/buildkit/util/appcontext"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
keyTarget = "target"
|
|
||||||
keyFilename = "filename"
|
|
||||||
exporterImageConfig = "containerimage.config"
|
|
||||||
defaultDockerfileName = "Dockerfile"
|
|
||||||
localNameDockerfile = "dockerfile"
|
|
||||||
buildArgPrefix = "build-arg:"
|
|
||||||
localNameContext = "context"
|
|
||||||
gitPrefix = "git://"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := run(); err != nil {
|
if err := run(); err != nil {
|
||||||
logrus.Errorf("fatal error: %+v", err)
|
logrus.Errorf("fatal error: %+v", err)
|
||||||
|
@ -32,94 +16,10 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
c, err := client.Current()
|
c, err := grpcclient.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to create client")
|
return errors.Wrap(err, "failed to create client")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := appcontext.Context()
|
return builder.Build(appcontext.Context(), c)
|
||||||
|
|
||||||
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()),
|
|
||||||
)
|
|
||||||
var buildContext *llb.State
|
|
||||||
if strings.HasPrefix(opts[localNameContext], gitPrefix) {
|
|
||||||
src = parseGitSource(opts[localNameContext])
|
|
||||||
buildContext = &src
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
BuildContext: buildContext,
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseGitSource(ref string) llb.State {
|
|
||||||
ref = strings.TrimPrefix(ref, gitPrefix)
|
|
||||||
parts := strings.SplitN(ref, "#", 2)
|
|
||||||
branch := ""
|
|
||||||
if len(parts) > 1 {
|
|
||||||
branch = parts[1]
|
|
||||||
}
|
|
||||||
return llb.Git(parts[0], branch)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,14 @@
|
||||||
package dockerfile
|
package dockerfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/buildkit/cache"
|
"github.com/moby/buildkit/cache"
|
||||||
"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/builder"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
"github.com/moby/buildkit/snapshot"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
keyTarget = "target"
|
|
||||||
keyFilename = "filename"
|
|
||||||
exporterImageConfig = "containerimage.config"
|
|
||||||
defaultDockerfileName = "Dockerfile"
|
|
||||||
localNameDockerfile = "dockerfile"
|
|
||||||
buildArgPrefix = "build-arg:"
|
|
||||||
localNameContext = "context"
|
|
||||||
gitPrefix = "git://"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewDockerfileFrontend() frontend.Frontend {
|
func NewDockerfileFrontend() frontend.Frontend {
|
||||||
return &dfFrontend{}
|
return &dfFrontend{}
|
||||||
}
|
}
|
||||||
|
@ -35,127 +16,27 @@ 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][]byte, 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]
|
|
||||||
if filename == "" {
|
|
||||||
filename = defaultDockerfileName
|
|
||||||
}
|
|
||||||
if path.Base(filename) != filename {
|
|
||||||
return nil, nil, errors.Errorf("invalid filename %s", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := session.FromContext(ctx)
|
c, err := llbBridgeToGatewayClient(ctx, llbBridge, opts)
|
||||||
|
|
||||||
src := llb.Local(localNameDockerfile,
|
|
||||||
llb.IncludePatterns([]string{filename}),
|
|
||||||
llb.SessionID(sid),
|
|
||||||
)
|
|
||||||
|
|
||||||
var buildContext *llb.State
|
|
||||||
if strings.HasPrefix(opts[localNameContext], gitPrefix) {
|
|
||||||
src = parseGitSource(opts[localNameContext])
|
|
||||||
buildContext = &src
|
|
||||||
}
|
|
||||||
|
|
||||||
def, err := src.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ref, _, err := llbBridge.Solve(ctx, frontend.SolveRequest{
|
|
||||||
Definition: def.ToPB(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if ref != nil {
|
for _, r := range c.refs {
|
||||||
ref.Release(context.TODO())
|
if r != nil && (c.final != r || retErr != nil) {
|
||||||
|
r.Release(context.TODO())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
mount, err := ref.Mount(ctx, false)
|
if err := builder.Build(appcontext.Context(), c); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lm := snapshot.LocalMounter(mount)
|
if c.final == nil {
|
||||||
|
return nil, nil, errors.Errorf("invalid empty return") // shouldn't happen
|
||||||
root, err := lm.Mount()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
return c.final.ImmutableRef, c.exporterAttr, nil
|
||||||
if lm != nil {
|
|
||||||
lm.Unmount()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
dtDockerfile, err := ioutil.ReadFile(filepath.Join(root, filename))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := lm.Unmount(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
lm = nil
|
|
||||||
|
|
||||||
if err := ref.Release(context.TODO()); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ref = nil
|
|
||||||
|
|
||||||
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{
|
|
||||||
Target: opts[keyTarget],
|
|
||||||
MetaResolver: llbBridge,
|
|
||||||
BuildArgs: filterBuildArgs(opts),
|
|
||||||
SessionID: sid,
|
|
||||||
BuildContext: buildContext,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
def, err = st.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
retRef, _, err = llbBridge.Solve(ctx, frontend.SolveRequest{
|
|
||||||
Definition: def.ToPB(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := json.Marshal(img)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return retRef, map[string][]byte{
|
|
||||||
exporterImageConfig: config,
|
|
||||||
}, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseGitSource(ref string) llb.State {
|
|
||||||
ref = strings.TrimPrefix(ref, gitPrefix)
|
|
||||||
parts := strings.SplitN(ref, "#", 2)
|
|
||||||
branch := ""
|
|
||||||
if len(parts) > 1 {
|
|
||||||
branch = parts[1]
|
|
||||||
}
|
|
||||||
return llb.Git(parts[0], branch)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/builder"
|
||||||
"github.com/moby/buildkit/identity"
|
"github.com/moby/buildkit/identity"
|
||||||
"github.com/moby/buildkit/util/testutil/httpserver"
|
"github.com/moby/buildkit/util/testutil/httpserver"
|
||||||
"github.com/moby/buildkit/util/testutil/integration"
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
|
@ -454,8 +455,8 @@ EXPOSE 5000
|
||||||
"name": target,
|
"name": target,
|
||||||
},
|
},
|
||||||
LocalDirs: map[string]string{
|
LocalDirs: map[string]string{
|
||||||
localNameDockerfile: dir,
|
builder.LocalNameDockerfile: dir,
|
||||||
localNameContext: dir,
|
builder.LocalNameContext: dir,
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -611,8 +612,8 @@ USER nobody
|
||||||
"output": destDir,
|
"output": destDir,
|
||||||
},
|
},
|
||||||
LocalDirs: map[string]string{
|
LocalDirs: map[string]string{
|
||||||
localNameDockerfile: dir,
|
builder.LocalNameDockerfile: dir,
|
||||||
localNameContext: dir,
|
builder.LocalNameContext: dir,
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -634,8 +635,8 @@ USER nobody
|
||||||
"name": target,
|
"name": target,
|
||||||
},
|
},
|
||||||
LocalDirs: map[string]string{
|
LocalDirs: map[string]string{
|
||||||
localNameDockerfile: dir,
|
builder.LocalNameDockerfile: dir,
|
||||||
localNameContext: dir,
|
builder.LocalNameContext: dir,
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package dockerfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/cache"
|
||||||
|
"github.com/moby/buildkit/frontend"
|
||||||
|
"github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func llbBridgeToGatewayClient(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (*bridgeClient, error) {
|
||||||
|
return &bridgeClient{opts: opts, FrontendLLBBridge: llbBridge, sid: session.FromContext(ctx)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bridgeClient struct {
|
||||||
|
frontend.FrontendLLBBridge
|
||||||
|
opts map[string]string
|
||||||
|
final *ref
|
||||||
|
sid string
|
||||||
|
exporterAttr map[string][]byte
|
||||||
|
refs []*ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bridgeClient) Solve(ctx context.Context, def *pb.Definition, f string, exporterAttr map[string][]byte, final bool) (client.Reference, error) {
|
||||||
|
r, exporterAttrRes, err := c.FrontendLLBBridge.Solve(ctx, frontend.SolveRequest{
|
||||||
|
Definition: def,
|
||||||
|
Frontend: f,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rr := &ref{r}
|
||||||
|
c.refs = append(c.refs, rr)
|
||||||
|
if final {
|
||||||
|
c.final = rr
|
||||||
|
if exporterAttr == nil {
|
||||||
|
exporterAttr = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
for k, v := range exporterAttrRes {
|
||||||
|
exporterAttr[k] = v
|
||||||
|
}
|
||||||
|
c.exporterAttr = exporterAttr
|
||||||
|
}
|
||||||
|
return rr, nil
|
||||||
|
}
|
||||||
|
func (c *bridgeClient) Opts() map[string]string {
|
||||||
|
return c.opts
|
||||||
|
}
|
||||||
|
func (c *bridgeClient) SessionID() string {
|
||||||
|
return c.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
type ref struct {
|
||||||
|
cache.ImmutableRef
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ref) ReadFile(ctx context.Context, fp string) ([]byte, error) {
|
||||||
|
if r.ImmutableRef == nil {
|
||||||
|
return nil, errors.Wrapf(os.ErrNotExist, "%s no found", fp)
|
||||||
|
}
|
||||||
|
return cache.ReadFile(ctx, r.ImmutableRef, fp)
|
||||||
|
}
|
|
@ -1,163 +1,19 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
"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"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const frontendPrefix = "BUILDKIT_FRONTEND_OPT_"
|
// TODO: make this take same options as LLBBridge. Add Return()
|
||||||
|
type Client interface {
|
||||||
func Current() (*Client, error) {
|
Solve(ctx context.Context, def *pb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (Reference, error)
|
||||||
ctx, conn, err := grpcClientConn(context.Background())
|
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
||||||
if err != nil {
|
Opts() map[string]string
|
||||||
return nil, err
|
SessionID() string
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
type Reference interface {
|
||||||
client pb.LLBBridgeClient
|
ReadFile(ctx context.Context, fp string) ([]byte, error)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -19,7 +17,6 @@ import (
|
||||||
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
"github.com/moby/buildkit/identity"
|
"github.com/moby/buildkit/identity"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
"github.com/moby/buildkit/snapshot"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -75,6 +72,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
defer ref.Release(context.TODO())
|
||||||
rootFS = ref
|
rootFS = ref
|
||||||
config, ok := exp[exporterImageConfig]
|
config, ok := exp[exporterImageConfig]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -115,6 +113,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
defer ref.Release(context.TODO())
|
||||||
rootFS = ref
|
rootFS = ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +143,14 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
|
||||||
|
|
||||||
env = append(env, "BUILDKIT_SESSION_ID="+sid)
|
env = append(env, "BUILDKIT_SESSION_ID="+sid)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, r := range lbf.refs {
|
||||||
|
if r != nil && (lbf.lastRef != r || retErr != nil) {
|
||||||
|
r.Release(context.TODO())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = llbBridge.Exec(ctx, executor.Meta{
|
err = llbBridge.Exec(ctx, executor.Meta{
|
||||||
Env: env,
|
Env: env,
|
||||||
Args: args,
|
Args: args,
|
||||||
|
@ -271,6 +278,9 @@ func (lbf *llbBrideForwarder) Solve(ctx context.Context, req *pb.SolveRequest) (
|
||||||
lbf.lastRef = ref
|
lbf.lastRef = ref
|
||||||
lbf.exporterAttr = exp
|
lbf.exporterAttr = exp
|
||||||
}
|
}
|
||||||
|
if ref == nil {
|
||||||
|
id = ""
|
||||||
|
}
|
||||||
return &pb.SolveResponse{Ref: id}, nil
|
return &pb.SolveResponse{Ref: id}, nil
|
||||||
}
|
}
|
||||||
func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) {
|
func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) {
|
||||||
|
@ -278,35 +288,14 @@ func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequ
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
||||||
}
|
}
|
||||||
|
if ref == nil {
|
||||||
mount, err := ref.Mount(ctx, false)
|
return nil, errors.Wrapf(os.ErrNotExist, "%s no found", req.FilePath)
|
||||||
|
}
|
||||||
|
dt, err := cache.ReadFile(ctx, ref, req.FilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return &pb.ReadFileResponse{Data: dt}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
package grpcclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
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.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 &grpcClient{client: c, opts: opts(), sessionID: sessionID()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type grpcClient struct {
|
||||||
|
client pb.LLBBridgeClient
|
||||||
|
opts map[string]string
|
||||||
|
sessionID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcClient) Solve(ctx context.Context, def *opspb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (client.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
|
||||||
|
}
|
||||||
|
if resp.Ref == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &reference{id: resp.Ref, c: c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcClient) 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 *grpcClient) Opts() map[string]string {
|
||||||
|
return c.opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcClient) SessionID() string {
|
||||||
|
return c.sessionID
|
||||||
|
}
|
||||||
|
|
||||||
|
type reference struct {
|
||||||
|
id string
|
||||||
|
c *grpcClient
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
|
@ -8,3 +8,4 @@ docker build -t buildkit:test --target integration-tests -f ./hack/dockerfiles/t
|
||||||
docker run --rm -v /tmp --privileged buildkit:test go test -tags standalone ${TESTFLAGS:--v} ${TESTPKGS:-./...}
|
docker run --rm -v /tmp --privileged buildkit:test go test -tags standalone ${TESTFLAGS:--v} ${TESTPKGS:-./...}
|
||||||
|
|
||||||
docker run --rm buildkit:test go build ./frontend/gateway/client
|
docker run --rm buildkit:test go build ./frontend/gateway/client
|
||||||
|
docker run --rm buildkit:test go build ./frontend/dockerfile/cmd/dockerfile-frontend
|
||||||
|
|
Loading…
Reference in New Issue