buildkit/exporter/tar/export.go

171 lines
3.5 KiB
Go

package local
import (
"context"
"io/ioutil"
"os"
"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/tonistiigi/fsutil"
fstypes "github.com/tonistiigi/fsutil/types"
)
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}
return li, nil
}
type localExporterInstance struct {
*localExporter
}
func (e *localExporterInstance) Name() string {
return "exporting to client"
}
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.FromContext(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
}
}