195 lines
4.2 KiB
Go
195 lines
4.2 KiB
Go
package local
|
|
|
|
import (
|
|
"context"
|
|
"io/ioutil"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/moby/buildkit/cache"
|
|
"github.com/moby/buildkit/exporter"
|
|
"github.com/moby/buildkit/session"
|
|
"github.com/moby/buildkit/session/filesync"
|
|
"github.com/moby/buildkit/snapshot"
|
|
"github.com/moby/buildkit/util/progress"
|
|
"github.com/pkg/errors"
|
|
"github.com/tonistiigi/fsutil"
|
|
fstypes "github.com/tonistiigi/fsutil/types"
|
|
)
|
|
|
|
const (
|
|
// preferNondistLayersKey is an exporter option which can be used to mark a layer as non-distributable if the layer reference was
|
|
// already found to use a non-distributable media type.
|
|
// When this option is not set, the exporter will change the media type of the layer to a distributable one.
|
|
preferNondistLayersKey = "prefer-nondist-layers"
|
|
)
|
|
|
|
type Opt struct {
|
|
SessionManager *session.Manager
|
|
}
|
|
|
|
type localExporter struct {
|
|
opt Opt
|
|
// session manager
|
|
}
|
|
|
|
func New(opt Opt) (exporter.Exporter, error) {
|
|
le := &localExporter{opt: opt}
|
|
return le, nil
|
|
}
|
|
|
|
func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
|
|
li := &localExporterInstance{localExporter: e}
|
|
|
|
v, ok := opt[preferNondistLayersKey]
|
|
if ok {
|
|
b, err := strconv.ParseBool(v)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "non-bool value for %s: %s", preferNondistLayersKey, v)
|
|
}
|
|
li.preferNonDist = b
|
|
}
|
|
|
|
return li, nil
|
|
}
|
|
|
|
type localExporterInstance struct {
|
|
*localExporter
|
|
preferNonDist bool
|
|
}
|
|
|
|
func (e *localExporterInstance) Name() string {
|
|
return "exporting to client"
|
|
}
|
|
|
|
func (e *localExporterInstance) Config() exporter.Config {
|
|
return exporter.Config{}
|
|
}
|
|
|
|
func (e *localExporterInstance) Export(ctx context.Context, inp exporter.Source, sessionID string) (map[string]string, error) {
|
|
var defers []func()
|
|
|
|
defer func() {
|
|
for i := len(defers) - 1; i >= 0; i-- {
|
|
defers[i]()
|
|
}
|
|
}()
|
|
|
|
getDir := func(ctx context.Context, k string, ref cache.ImmutableRef) (*fsutil.Dir, error) {
|
|
var src string
|
|
var err error
|
|
var idmap *idtools.IdentityMapping
|
|
if ref == nil {
|
|
src, err = ioutil.TempDir("", "buildkit")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defers = append(defers, func() { os.RemoveAll(src) })
|
|
} else {
|
|
mount, err := ref.Mount(ctx, true, session.NewGroup(sessionID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lm := snapshot.LocalMounter(mount)
|
|
|
|
src, err = lm.Mount()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
idmap = mount.IdentityMapping()
|
|
|
|
defers = append(defers, func() { lm.Unmount() })
|
|
}
|
|
|
|
walkOpt := &fsutil.WalkOpt{}
|
|
|
|
if idmap != nil {
|
|
walkOpt.Map = func(p string, st *fstypes.Stat) bool {
|
|
uid, gid, err := idmap.ToContainer(idtools.Identity{
|
|
UID: int(st.Uid),
|
|
GID: int(st.Gid),
|
|
})
|
|
if err != nil {
|
|
return false
|
|
}
|
|
st.Uid = uint32(uid)
|
|
st.Gid = uint32(gid)
|
|
return true
|
|
}
|
|
}
|
|
|
|
return &fsutil.Dir{
|
|
FS: fsutil.NewFS(src, walkOpt),
|
|
Stat: fstypes.Stat{
|
|
Mode: uint32(os.ModeDir | 0755),
|
|
Path: strings.Replace(k, "/", "_", -1),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var fs fsutil.FS
|
|
|
|
if len(inp.Refs) > 0 {
|
|
dirs := make([]fsutil.Dir, 0, len(inp.Refs))
|
|
for k, ref := range inp.Refs {
|
|
d, err := getDir(ctx, k, ref)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dirs = append(dirs, *d)
|
|
}
|
|
var err error
|
|
fs, err = fsutil.SubDirFS(dirs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
d, err := getDir(ctx, "", inp.Ref)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fs = d.FS
|
|
}
|
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
caller, err := e.opt.SessionManager.Get(timeoutCtx, sessionID, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w, err := filesync.CopyFileWriter(ctx, nil, caller)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
report := oneOffProgress(ctx, "sending tarball")
|
|
if err := fsutil.WriteTar(ctx, fs, w); err != nil {
|
|
w.Close()
|
|
return nil, report(err)
|
|
}
|
|
return nil, report(w.Close())
|
|
}
|
|
|
|
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
|
pw, _, _ := progress.NewFromContext(ctx)
|
|
now := time.Now()
|
|
st := progress.Status{
|
|
Started: &now,
|
|
}
|
|
pw.Write(id, st)
|
|
return func(err error) error {
|
|
// TODO: set error on status
|
|
now := time.Now()
|
|
st.Completed = &now
|
|
pw.Write(id, st)
|
|
pw.Close()
|
|
return err
|
|
}
|
|
}
|