| 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) |
| 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) |
| 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 |
| } |
| } |