vendor: update buildkit to e9aca5be
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
8ad6dcf2a5
commit
e96d1c3754
|
@ -165,6 +165,8 @@ func (w *Worker) ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge, sm *se
|
||||||
return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, sm, w)
|
return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, sm, w)
|
||||||
case *pb.Op_Exec:
|
case *pb.Op_Exec:
|
||||||
return ops.NewExecOp(v, op, baseOp.Platform, w.CacheManager, sm, w.MetadataStore, w.Executor, w)
|
return ops.NewExecOp(v, op, baseOp.Platform, w.CacheManager, sm, w.MetadataStore, w.Executor, w)
|
||||||
|
case *pb.Op_File:
|
||||||
|
return ops.NewFileOp(v, op, w.CacheManager, w.MetadataStore, w)
|
||||||
case *pb.Op_Build:
|
case *pb.Op_Build:
|
||||||
return ops.NewBuildOp(v, op, s, w)
|
return ops.NewBuildOp(v, op, s, w)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ github.com/imdario/mergo v0.3.6
|
||||||
golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
|
golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
|
||||||
|
|
||||||
# buildkit
|
# buildkit
|
||||||
github.com/moby/buildkit c35410878ab9070498c66f6c67d3e8bc3b92241f
|
github.com/moby/buildkit e9aca5bef87e19173b99d8668db0338dcaaa5f33
|
||||||
github.com/tonistiigi/fsutil 1ec1983587cde7e8ac2978e354ff5360af622464
|
github.com/tonistiigi/fsutil 1bdbf124ad494a771e99e0cdcd16326375f8b2c9
|
||||||
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
||||||
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
|
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
|
||||||
github.com/google/shlex 6f45313302b9c56850fc17f99e40caebce98c716
|
github.com/google/shlex 6f45313302b9c56850fc17f99e40caebce98c716
|
||||||
|
|
|
@ -47,6 +47,10 @@ func Checksum(ctx context.Context, ref cache.ImmutableRef, path string, followLi
|
||||||
return getDefaultManager().Checksum(ctx, ref, path, followLinks)
|
return getDefaultManager().Checksum(ctx, ref, path, followLinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ChecksumWildcard(ctx context.Context, ref cache.ImmutableRef, path string, followLinks bool) (digest.Digest, error) {
|
||||||
|
return getDefaultManager().ChecksumWildcard(ctx, ref, path, followLinks)
|
||||||
|
}
|
||||||
|
|
||||||
func GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
|
func GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
|
||||||
return getDefaultManager().GetCacheContext(ctx, md)
|
return getDefaultManager().GetCacheContext(ctx, md)
|
||||||
}
|
}
|
||||||
|
@ -84,6 +88,14 @@ func (cm *cacheManager) Checksum(ctx context.Context, ref cache.ImmutableRef, p
|
||||||
return cc.Checksum(ctx, ref, p, followLinks)
|
return cc.Checksum(ctx, ref, p, followLinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cm *cacheManager) ChecksumWildcard(ctx context.Context, ref cache.ImmutableRef, p string, followLinks bool) (digest.Digest, error) {
|
||||||
|
cc, err := cm.GetCacheContext(ctx, ensureOriginMetadata(ref.Metadata()))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return cc.ChecksumWildcard(ctx, ref, p, followLinks)
|
||||||
|
}
|
||||||
|
|
||||||
func (cm *cacheManager) GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
|
func (cm *cacheManager) GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
|
||||||
cm.locker.Lock(md.ID())
|
cm.locker.Lock(md.ID())
|
||||||
cm.lruMu.Lock()
|
cm.lruMu.Lock()
|
||||||
|
@ -91,6 +103,7 @@ func (cm *cacheManager) GetCacheContext(ctx context.Context, md *metadata.Storag
|
||||||
cm.lruMu.Unlock()
|
cm.lruMu.Unlock()
|
||||||
if ok {
|
if ok {
|
||||||
cm.locker.Unlock(md.ID())
|
cm.locker.Unlock(md.ID())
|
||||||
|
v.(*cacheContext).linkMap = map[string][][]byte{}
|
||||||
return v.(*cacheContext), nil
|
return v.(*cacheContext), nil
|
||||||
}
|
}
|
||||||
cc, err := newCacheContext(md)
|
cc, err := newCacheContext(md)
|
||||||
|
@ -115,6 +128,7 @@ func (cm *cacheManager) SetCacheContext(ctx context.Context, md *metadata.Storag
|
||||||
md: md,
|
md: md,
|
||||||
tree: cci.(*cacheContext).tree,
|
tree: cci.(*cacheContext).tree,
|
||||||
dirtyMap: map[string]struct{}{},
|
dirtyMap: map[string]struct{}{},
|
||||||
|
linkMap: map[string][][]byte{},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := cc.save(); err != nil {
|
if err := cc.save(); err != nil {
|
||||||
|
@ -137,6 +151,7 @@ type cacheContext struct {
|
||||||
txn *iradix.Txn
|
txn *iradix.Txn
|
||||||
node *iradix.Node
|
node *iradix.Node
|
||||||
dirtyMap map[string]struct{}
|
dirtyMap map[string]struct{}
|
||||||
|
linkMap map[string][][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type mount struct {
|
type mount struct {
|
||||||
|
@ -181,6 +196,7 @@ func newCacheContext(md *metadata.StorageItem) (*cacheContext, error) {
|
||||||
md: md,
|
md: md,
|
||||||
tree: iradix.New(),
|
tree: iradix.New(),
|
||||||
dirtyMap: map[string]struct{}{},
|
dirtyMap: map[string]struct{}{},
|
||||||
|
linkMap: map[string][][]byte{},
|
||||||
}
|
}
|
||||||
if err := cc.load(); err != nil {
|
if err := cc.load(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -313,7 +329,35 @@ func (cc *cacheContext) HandleChange(kind fsutil.ChangeKind, p string, fi os.Fil
|
||||||
p += "/"
|
p += "/"
|
||||||
}
|
}
|
||||||
cr.Digest = h.Digest()
|
cr.Digest = h.Digest()
|
||||||
|
|
||||||
|
// if we receive a hardlink just use the digest of the source
|
||||||
|
// note that the source may be called later because data writing is async
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 && stat.Linkname != "" {
|
||||||
|
ln := path.Join("/", filepath.ToSlash(stat.Linkname))
|
||||||
|
v, ok := cc.txn.Get(convertPathToKey([]byte(ln)))
|
||||||
|
if ok {
|
||||||
|
cp := *v.(*CacheRecord)
|
||||||
|
cr = &cp
|
||||||
|
}
|
||||||
|
cc.linkMap[ln] = append(cc.linkMap[ln], k)
|
||||||
|
}
|
||||||
|
|
||||||
cc.txn.Insert(k, cr)
|
cc.txn.Insert(k, cr)
|
||||||
|
if !fi.IsDir() {
|
||||||
|
if links, ok := cc.linkMap[p]; ok {
|
||||||
|
for _, l := range links {
|
||||||
|
pp := convertKeyToPath(l)
|
||||||
|
cc.txn.Insert(l, cr)
|
||||||
|
d := path.Dir(string(pp))
|
||||||
|
if d == "/" {
|
||||||
|
d = ""
|
||||||
|
}
|
||||||
|
cc.dirtyMap[d] = struct{}{}
|
||||||
|
}
|
||||||
|
delete(cc.linkMap, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d := path.Dir(p)
|
d := path.Dir(p)
|
||||||
if d == "/" {
|
if d == "/" {
|
||||||
d = ""
|
d = ""
|
||||||
|
@ -343,6 +387,9 @@ func (cc *cacheContext) ChecksumWildcard(ctx context.Context, mountable cache.Mo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(wildcards) == 0 {
|
||||||
|
return digest.FromBytes([]byte{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(wildcards) > 1 {
|
if len(wildcards) > 1 {
|
||||||
digester := digest.Canonical.Digester()
|
digester := digest.Canonical.Digester()
|
||||||
|
@ -543,12 +590,13 @@ func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
|
func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
|
||||||
|
origk := k
|
||||||
k, cr, err := getFollowLinks(root, k, follow)
|
k, cr, err := getFollowLinks(root, k, follow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if cr == nil {
|
if cr == nil {
|
||||||
return nil, false, errors.Wrapf(errNotFound, "%s not found", convertKeyToPath(k))
|
return nil, false, errors.Wrapf(errNotFound, "%q not found", convertKeyToPath(origk))
|
||||||
}
|
}
|
||||||
if cr.Digest != "" {
|
if cr.Digest != "" {
|
||||||
return cr, false, nil
|
return cr, false, nil
|
||||||
|
|
|
@ -311,7 +311,7 @@ func (sr *mutableRef) updateLastUsed() bool {
|
||||||
|
|
||||||
func (sr *mutableRef) commit(ctx context.Context) (ImmutableRef, error) {
|
func (sr *mutableRef) commit(ctx context.Context) (ImmutableRef, error) {
|
||||||
if !sr.mutable || len(sr.refs) == 0 {
|
if !sr.mutable || len(sr.refs) == 0 {
|
||||||
return nil, errors.Wrapf(errInvalid, "invalid mutable ref")
|
return nil, errors.Wrapf(errInvalid, "invalid mutable ref %p", sr)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := identity.NewID()
|
id := identity.NewID()
|
||||||
|
|
|
@ -0,0 +1,727 @@
|
||||||
|
package llb
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "crypto/sha256"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Examples:
|
||||||
|
// local := llb.Local(...)
|
||||||
|
// llb.Image().Dir("/abc").File(Mkdir("./foo").Mkfile("/abc/foo/bar", []byte("data")))
|
||||||
|
// llb.Image().File(Mkdir("/foo").Mkfile("/foo/bar", []byte("data")))
|
||||||
|
// llb.Image().File(Copy(local, "/foo", "/bar")).File(Copy(local, "/foo2", "/bar2"))
|
||||||
|
//
|
||||||
|
// a := Mkdir("./foo") // *FileAction /ced/foo
|
||||||
|
// b := Mkdir("./bar") // /abc/bar
|
||||||
|
// c := b.Copy(a.WithState(llb.Scratch().Dir("/ced")), "./foo", "./baz") // /abc/baz
|
||||||
|
// llb.Image().Dir("/abc").File(c)
|
||||||
|
//
|
||||||
|
// In future this can be extended to multiple outputs with:
|
||||||
|
// a := Mkdir("./foo")
|
||||||
|
// b, id := a.GetSelector()
|
||||||
|
// c := b.Mkdir("./bar")
|
||||||
|
// filestate = state.File(c)
|
||||||
|
// filestate.GetOutput(id).Exec()
|
||||||
|
|
||||||
|
func NewFileOp(s State, action *FileAction, c Constraints) *FileOp {
|
||||||
|
action = action.bind(s)
|
||||||
|
|
||||||
|
f := &FileOp{
|
||||||
|
action: action,
|
||||||
|
constraints: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
f.output = &output{vertex: f, getIndex: func() (pb.OutputIndex, error) {
|
||||||
|
return pb.OutputIndex(0), nil
|
||||||
|
}}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyInput is either llb.State or *FileActionWithState
|
||||||
|
type CopyInput interface {
|
||||||
|
isFileOpCopyInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
type subAction interface {
|
||||||
|
toProtoAction(string, pb.InputIndex) pb.IsFileAction
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileAction struct {
|
||||||
|
state *State
|
||||||
|
prev *FileAction
|
||||||
|
action subAction
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
|
||||||
|
a := Mkdir(p, m, opt...)
|
||||||
|
a.prev = fa
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) Mkfile(p string, m os.FileMode, dt []byte, opt ...MkfileOption) *FileAction {
|
||||||
|
a := Mkfile(p, m, dt, opt...)
|
||||||
|
a.prev = fa
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) Rm(p string, opt ...RmOption) *FileAction {
|
||||||
|
a := Rm(p, opt...)
|
||||||
|
a.prev = fa
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) Copy(input CopyInput, src, dest string, opt ...CopyOption) *FileAction {
|
||||||
|
a := Copy(input, src, dest, opt...)
|
||||||
|
a.prev = fa
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) allOutputs(m map[Output]struct{}) {
|
||||||
|
if fa == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fa.state != nil && fa.state.Output() != nil {
|
||||||
|
m[fa.state.Output()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, ok := fa.action.(*fileActionCopy); ok {
|
||||||
|
if a.state != nil {
|
||||||
|
if out := a.state.Output(); out != nil {
|
||||||
|
m[out] = struct{}{}
|
||||||
|
}
|
||||||
|
} else if a.fas != nil {
|
||||||
|
a.fas.allOutputs(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fa.prev.allOutputs(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) bind(s State) *FileAction {
|
||||||
|
if fa == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fa2 := *fa
|
||||||
|
fa2.prev = fa.prev.bind(s)
|
||||||
|
fa2.state = &s
|
||||||
|
return &fa2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa *FileAction) WithState(s State) CopyInput {
|
||||||
|
return &fileActionWithState{FileAction: fa.bind(s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileActionWithState struct {
|
||||||
|
*FileAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fas *fileActionWithState) isFileOpCopyInput() {}
|
||||||
|
|
||||||
|
func Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
|
||||||
|
var mi MkdirInfo
|
||||||
|
for _, o := range opt {
|
||||||
|
o.SetMkdirOption(&mi)
|
||||||
|
}
|
||||||
|
return &FileAction{
|
||||||
|
action: &fileActionMkdir{
|
||||||
|
file: p,
|
||||||
|
mode: m,
|
||||||
|
info: mi,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileActionMkdir struct {
|
||||||
|
file string
|
||||||
|
mode os.FileMode
|
||||||
|
info MkdirInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *fileActionMkdir) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
|
return &pb.FileAction_Mkdir{
|
||||||
|
Mkdir: &pb.FileActionMkDir{
|
||||||
|
Path: normalizePath(parent, a.file, false),
|
||||||
|
Mode: int32(a.mode & 0777),
|
||||||
|
MakeParents: a.info.MakeParents,
|
||||||
|
Owner: a.info.ChownOpt.marshal(base),
|
||||||
|
Timestamp: marshalTime(a.info.CreatedTime),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkdirOption interface {
|
||||||
|
SetMkdirOption(*MkdirInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChownOption interface {
|
||||||
|
MkdirOption
|
||||||
|
MkfileOption
|
||||||
|
CopyOption
|
||||||
|
}
|
||||||
|
|
||||||
|
type mkdirOptionFunc func(*MkdirInfo)
|
||||||
|
|
||||||
|
func (fn mkdirOptionFunc) SetMkdirOption(mi *MkdirInfo) {
|
||||||
|
fn(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MkdirOption = &MkdirInfo{}
|
||||||
|
|
||||||
|
func WithParents(b bool) MkdirOption {
|
||||||
|
return mkdirOptionFunc(func(mi *MkdirInfo) {
|
||||||
|
mi.MakeParents = b
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkdirInfo struct {
|
||||||
|
MakeParents bool
|
||||||
|
ChownOpt *ChownOpt
|
||||||
|
CreatedTime *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *MkdirInfo) SetMkdirOption(mi2 *MkdirInfo) {
|
||||||
|
*mi2 = *mi
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithUser(name string) ChownOption {
|
||||||
|
opt := ChownOpt{}
|
||||||
|
|
||||||
|
parts := strings.SplitN(name, ":", 2)
|
||||||
|
for i, v := range parts {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
uid, err := parseUID(v)
|
||||||
|
if err != nil {
|
||||||
|
opt.User = &UserOpt{Name: v}
|
||||||
|
} else {
|
||||||
|
opt.User = &UserOpt{UID: uid}
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
gid, err := parseUID(v)
|
||||||
|
if err != nil {
|
||||||
|
opt.Group = &UserOpt{Name: v}
|
||||||
|
} else {
|
||||||
|
opt.Group = &UserOpt{UID: gid}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUID(str string) (int, error) {
|
||||||
|
if str == "root" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
uid, err := strconv.ParseInt(str, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(uid), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithUIDGID(uid, gid int) ChownOption {
|
||||||
|
return ChownOpt{
|
||||||
|
User: &UserOpt{UID: uid},
|
||||||
|
Group: &UserOpt{UID: gid},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChownOpt struct {
|
||||||
|
User *UserOpt
|
||||||
|
Group *UserOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (co ChownOpt) SetMkdirOption(mi *MkdirInfo) {
|
||||||
|
mi.ChownOpt = &co
|
||||||
|
}
|
||||||
|
func (co ChownOpt) SetMkfileOption(mi *MkfileInfo) {
|
||||||
|
mi.ChownOpt = &co
|
||||||
|
}
|
||||||
|
func (co ChownOpt) SetCopyOption(mi *CopyInfo) {
|
||||||
|
mi.ChownOpt = &co
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *ChownOpt) marshal(base pb.InputIndex) *pb.ChownOpt {
|
||||||
|
if cp == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &pb.ChownOpt{
|
||||||
|
User: cp.User.marshal(base),
|
||||||
|
Group: cp.Group.marshal(base),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserOpt struct {
|
||||||
|
UID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (up *UserOpt) marshal(base pb.InputIndex) *pb.UserOpt {
|
||||||
|
if up == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if up.Name != "" {
|
||||||
|
return &pb.UserOpt{User: &pb.UserOpt_ByName{ByName: &pb.NamedUserOpt{
|
||||||
|
Name: up.Name, Input: base}}}
|
||||||
|
}
|
||||||
|
return &pb.UserOpt{User: &pb.UserOpt_ByID{ByID: uint32(up.UID)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mkfile(p string, m os.FileMode, dt []byte, opts ...MkfileOption) *FileAction {
|
||||||
|
var mi MkfileInfo
|
||||||
|
for _, o := range opts {
|
||||||
|
o.SetMkfileOption(&mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileAction{
|
||||||
|
action: &fileActionMkfile{
|
||||||
|
file: p,
|
||||||
|
mode: m,
|
||||||
|
dt: dt,
|
||||||
|
info: mi,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkfileOption interface {
|
||||||
|
SetMkfileOption(*MkfileInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkfileInfo struct {
|
||||||
|
ChownOpt *ChownOpt
|
||||||
|
CreatedTime *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *MkfileInfo) SetMkfileOption(mi2 *MkfileInfo) {
|
||||||
|
*mi2 = *mi
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MkfileOption = &MkfileInfo{}
|
||||||
|
|
||||||
|
type fileActionMkfile struct {
|
||||||
|
file string
|
||||||
|
mode os.FileMode
|
||||||
|
dt []byte
|
||||||
|
info MkfileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *fileActionMkfile) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
|
return &pb.FileAction_Mkfile{
|
||||||
|
Mkfile: &pb.FileActionMkFile{
|
||||||
|
Path: normalizePath(parent, a.file, false),
|
||||||
|
Mode: int32(a.mode & 0777),
|
||||||
|
Data: a.dt,
|
||||||
|
Owner: a.info.ChownOpt.marshal(base),
|
||||||
|
Timestamp: marshalTime(a.info.CreatedTime),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rm(p string, opts ...RmOption) *FileAction {
|
||||||
|
var mi RmInfo
|
||||||
|
for _, o := range opts {
|
||||||
|
o.SetRmOption(&mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileAction{
|
||||||
|
action: &fileActionRm{
|
||||||
|
file: p,
|
||||||
|
info: mi,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RmOption interface {
|
||||||
|
SetRmOption(*RmInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rmOptionFunc func(*RmInfo)
|
||||||
|
|
||||||
|
func (fn rmOptionFunc) SetRmOption(mi *RmInfo) {
|
||||||
|
fn(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RmInfo struct {
|
||||||
|
AllowNotFound bool
|
||||||
|
AllowWildcard bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *RmInfo) SetRmOption(mi2 *RmInfo) {
|
||||||
|
*mi2 = *mi
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RmOption = &RmInfo{}
|
||||||
|
|
||||||
|
func WithAllowNotFound(b bool) RmOption {
|
||||||
|
return rmOptionFunc(func(mi *RmInfo) {
|
||||||
|
mi.AllowNotFound = b
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAllowWildcard(b bool) RmOption {
|
||||||
|
return rmOptionFunc(func(mi *RmInfo) {
|
||||||
|
mi.AllowWildcard = b
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileActionRm struct {
|
||||||
|
file string
|
||||||
|
info RmInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *fileActionRm) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
|
return &pb.FileAction_Rm{
|
||||||
|
Rm: &pb.FileActionRm{
|
||||||
|
Path: normalizePath(parent, a.file, false),
|
||||||
|
AllowNotFound: a.info.AllowNotFound,
|
||||||
|
AllowWildcard: a.info.AllowWildcard,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Copy(input CopyInput, src, dest string, opts ...CopyOption) *FileAction {
|
||||||
|
var state *State
|
||||||
|
var fas *fileActionWithState
|
||||||
|
var err error
|
||||||
|
if st, ok := input.(State); ok {
|
||||||
|
state = &st
|
||||||
|
} else if v, ok := input.(*fileActionWithState); ok {
|
||||||
|
fas = v
|
||||||
|
} else {
|
||||||
|
err = errors.Errorf("invalid input type %T for copy", input)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mi CopyInfo
|
||||||
|
for _, o := range opts {
|
||||||
|
o.SetCopyOption(&mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileAction{
|
||||||
|
action: &fileActionCopy{
|
||||||
|
state: state,
|
||||||
|
fas: fas,
|
||||||
|
src: src,
|
||||||
|
dest: dest,
|
||||||
|
info: mi,
|
||||||
|
},
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CopyOption interface {
|
||||||
|
SetCopyOption(*CopyInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CopyInfo struct {
|
||||||
|
Mode *os.FileMode
|
||||||
|
FollowSymlinks bool
|
||||||
|
CopyDirContentsOnly bool
|
||||||
|
AttemptUnpack bool
|
||||||
|
CreateDestPath bool
|
||||||
|
AllowWildcard bool
|
||||||
|
AllowEmptyWildcard bool
|
||||||
|
ChownOpt *ChownOpt
|
||||||
|
CreatedTime *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *CopyInfo) SetCopyOption(mi2 *CopyInfo) {
|
||||||
|
*mi2 = *mi
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ CopyOption = &CopyInfo{}
|
||||||
|
|
||||||
|
type fileActionCopy struct {
|
||||||
|
state *State
|
||||||
|
fas *fileActionWithState
|
||||||
|
src string
|
||||||
|
dest string
|
||||||
|
info CopyInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *fileActionCopy) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
|
c := &pb.FileActionCopy{
|
||||||
|
Src: a.sourcePath(),
|
||||||
|
Dest: normalizePath(parent, a.dest, true),
|
||||||
|
Owner: a.info.ChownOpt.marshal(base),
|
||||||
|
AllowWildcard: a.info.AllowWildcard,
|
||||||
|
AllowEmptyWildcard: a.info.AllowEmptyWildcard,
|
||||||
|
FollowSymlink: a.info.FollowSymlinks,
|
||||||
|
DirCopyContents: a.info.CopyDirContentsOnly,
|
||||||
|
AttemptUnpackDockerCompatibility: a.info.AttemptUnpack,
|
||||||
|
CreateDestPath: a.info.CreateDestPath,
|
||||||
|
Timestamp: marshalTime(a.info.CreatedTime),
|
||||||
|
}
|
||||||
|
if a.info.Mode != nil {
|
||||||
|
c.Mode = int32(*a.info.Mode)
|
||||||
|
} else {
|
||||||
|
c.Mode = -1
|
||||||
|
}
|
||||||
|
return &pb.FileAction_Copy{
|
||||||
|
Copy: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileActionCopy) sourcePath() string {
|
||||||
|
p := path.Clean(c.src)
|
||||||
|
if !path.IsAbs(p) {
|
||||||
|
if c.state != nil {
|
||||||
|
p = path.Join("/", c.state.GetDir(), p)
|
||||||
|
} else if c.fas != nil {
|
||||||
|
p = path.Join("/", c.fas.state.GetDir(), p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatedTime time.Time
|
||||||
|
|
||||||
|
func WithCreatedTime(t time.Time) CreatedTime {
|
||||||
|
return CreatedTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CreatedTime) SetMkdirOption(mi *MkdirInfo) {
|
||||||
|
mi.CreatedTime = (*time.Time)(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CreatedTime) SetMkfileOption(mi *MkfileInfo) {
|
||||||
|
mi.CreatedTime = (*time.Time)(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CreatedTime) SetCopyOption(mi *CopyInfo) {
|
||||||
|
mi.CreatedTime = (*time.Time)(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalTime(t *time.Time) int64 {
|
||||||
|
if t == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return t.UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileOp struct {
|
||||||
|
MarshalCache
|
||||||
|
action *FileAction
|
||||||
|
output Output
|
||||||
|
|
||||||
|
constraints Constraints
|
||||||
|
isValidated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileOp) Validate() error {
|
||||||
|
if f.isValidated {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if f.action == nil {
|
||||||
|
return errors.Errorf("action is required")
|
||||||
|
}
|
||||||
|
f.isValidated = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type marshalState struct {
|
||||||
|
visited map[*FileAction]*fileActionState
|
||||||
|
inputs []*pb.Input
|
||||||
|
actions []*fileActionState
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMarshalState() *marshalState {
|
||||||
|
return &marshalState{
|
||||||
|
visited: map[*FileAction]*fileActionState{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileActionState struct {
|
||||||
|
base pb.InputIndex
|
||||||
|
input pb.InputIndex
|
||||||
|
inputRelative *int
|
||||||
|
input2 pb.InputIndex
|
||||||
|
input2Relative *int
|
||||||
|
target int
|
||||||
|
action subAction
|
||||||
|
fa *FileAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *marshalState) addInput(st *fileActionState, c *Constraints, o Output) (pb.InputIndex, error) {
|
||||||
|
inp, err := o.ToInput(c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i, inp2 := range ms.inputs {
|
||||||
|
if *inp == *inp2 {
|
||||||
|
return pb.InputIndex(i), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i := pb.InputIndex(len(ms.inputs))
|
||||||
|
ms.inputs = append(ms.inputs, inp)
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *marshalState) add(fa *FileAction, c *Constraints) (*fileActionState, error) {
|
||||||
|
if st, ok := ms.visited[fa]; ok {
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if fa.err != nil {
|
||||||
|
return nil, fa.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevState *fileActionState
|
||||||
|
if parent := fa.prev; parent != nil {
|
||||||
|
var err error
|
||||||
|
prevState, err = ms.add(parent, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st := &fileActionState{
|
||||||
|
action: fa.action,
|
||||||
|
input: -1,
|
||||||
|
input2: -1,
|
||||||
|
base: -1,
|
||||||
|
fa: fa,
|
||||||
|
}
|
||||||
|
|
||||||
|
if source := fa.state.Output(); source != nil {
|
||||||
|
inp, err := ms.addInput(st, c, source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
st.base = inp
|
||||||
|
}
|
||||||
|
|
||||||
|
if fa.prev == nil {
|
||||||
|
st.input = st.base
|
||||||
|
} else {
|
||||||
|
st.inputRelative = &prevState.target
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, ok := fa.action.(*fileActionCopy); ok {
|
||||||
|
if a.state != nil {
|
||||||
|
if out := a.state.Output(); out != nil {
|
||||||
|
inp, err := ms.addInput(st, c, out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
st.input2 = inp
|
||||||
|
}
|
||||||
|
} else if a.fas != nil {
|
||||||
|
src, err := ms.add(a.fas.FileAction, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
st.input2Relative = &src.target
|
||||||
|
} else {
|
||||||
|
return nil, errors.Errorf("invalid empty source for copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st.target = len(ms.actions)
|
||||||
|
|
||||||
|
ms.visited[fa] = st
|
||||||
|
ms.actions = append(ms.actions, st)
|
||||||
|
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, error) {
|
||||||
|
if f.Cached(c) {
|
||||||
|
return f.Load()
|
||||||
|
}
|
||||||
|
if err := f.Validate(); err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addCap(&f.constraints, pb.CapFileBase)
|
||||||
|
|
||||||
|
pfo := &pb.FileOp{}
|
||||||
|
|
||||||
|
pop, md := MarshalConstraints(c, &f.constraints)
|
||||||
|
pop.Op = &pb.Op_File{
|
||||||
|
File: pfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
state := newMarshalState()
|
||||||
|
_, err := state.add(f.action, c)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
pop.Inputs = state.inputs
|
||||||
|
|
||||||
|
for i, st := range state.actions {
|
||||||
|
output := pb.OutputIndex(-1)
|
||||||
|
if i+1 == len(state.actions) {
|
||||||
|
output = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent string
|
||||||
|
if st.fa.state != nil {
|
||||||
|
parent = st.fa.state.GetDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
pfo.Actions = append(pfo.Actions, &pb.FileAction{
|
||||||
|
Input: getIndex(st.input, len(state.inputs), st.inputRelative),
|
||||||
|
SecondaryInput: getIndex(st.input2, len(state.inputs), st.input2Relative),
|
||||||
|
Output: output,
|
||||||
|
Action: st.action.toProtoAction(parent, st.base),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := pop.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
f.Store(dt, md, c)
|
||||||
|
return f.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePath(parent, p string, keepSlash bool) string {
|
||||||
|
origPath := p
|
||||||
|
p = path.Clean(p)
|
||||||
|
if !path.IsAbs(p) {
|
||||||
|
p = path.Join("/", parent, p)
|
||||||
|
}
|
||||||
|
if keepSlash {
|
||||||
|
if strings.HasSuffix(origPath, "/") && !strings.HasSuffix(p, "/") {
|
||||||
|
p += "/"
|
||||||
|
} else if strings.HasSuffix(origPath, "/.") {
|
||||||
|
if p != "/" {
|
||||||
|
p += "/"
|
||||||
|
}
|
||||||
|
p += "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileOp) Output() Output {
|
||||||
|
return f.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileOp) Inputs() (inputs []Output) {
|
||||||
|
mm := map[Output]struct{}{}
|
||||||
|
|
||||||
|
f.action.allOutputs(mm)
|
||||||
|
|
||||||
|
for o := range mm {
|
||||||
|
inputs = append(inputs, o)
|
||||||
|
}
|
||||||
|
return inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndex(input pb.InputIndex, len int, relative *int) pb.InputIndex {
|
||||||
|
if relative != nil {
|
||||||
|
return pb.InputIndex(len + *relative)
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
|
@ -229,6 +229,15 @@ func (s State) Run(ro ...RunOption) ExecState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s State) File(a *FileAction, opts ...ConstraintsOpt) State {
|
||||||
|
var c Constraints
|
||||||
|
for _, o := range opts {
|
||||||
|
o.SetConstraintsOption(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.WithOutput(NewFileOp(s, a, c).Output())
|
||||||
|
}
|
||||||
|
|
||||||
func (s State) AddEnv(key, value string) State {
|
func (s State) AddEnv(key, value string) State {
|
||||||
return s.AddEnvf(key, value)
|
return s.AddEnvf(key, value)
|
||||||
}
|
}
|
||||||
|
@ -295,6 +304,8 @@ func (s State) AddExtraHost(host string, ip net.IP) State {
|
||||||
return extraHost(host, ip)(s)
|
return extraHost(host, ip)(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s State) isFileOpCopyInput() {}
|
||||||
|
|
||||||
type output struct {
|
type output struct {
|
||||||
vertex Vertex
|
vertex Vertex
|
||||||
getIndex func() (pb.OutputIndex, error)
|
getIndex func() (pb.OutputIndex, error)
|
||||||
|
|
|
@ -298,7 +298,7 @@ func prepareSyncedDirs(def *llb.Definition, localDirs map[string]string) ([]file
|
||||||
return nil, errors.Errorf("%s not a directory", d)
|
return nil, errors.Errorf("%s not a directory", d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetUIDAndGID := func(st *fstypes.Stat) bool {
|
resetUIDAndGID := func(p string, st *fstypes.Stat) bool {
|
||||||
st.Uid = 0
|
st.Uid = 0
|
||||||
st.Gid = 0
|
st.Gid = 0
|
||||||
return true
|
return true
|
||||||
|
|
128
vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go
generated
vendored
128
vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go
generated
vendored
|
@ -151,6 +151,10 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
|
||||||
switch cmd.(type) {
|
switch cmd.(type) {
|
||||||
case *instructions.AddCommand, *instructions.CopyCommand, *instructions.RunCommand:
|
case *instructions.AddCommand, *instructions.CopyCommand, *instructions.RunCommand:
|
||||||
total++
|
total++
|
||||||
|
case *instructions.WorkdirCommand:
|
||||||
|
if useFileOp(opt.BuildArgs, opt.LLBCaps) {
|
||||||
|
total++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ds.cmdTotal = total
|
ds.cmdTotal = total
|
||||||
|
@ -307,7 +311,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
|
||||||
d.state = d.state.AddEnv(k, v)
|
d.state = d.state.AddEnv(k, v)
|
||||||
}
|
}
|
||||||
if d.image.Config.WorkingDir != "" {
|
if d.image.Config.WorkingDir != "" {
|
||||||
if err = dispatchWorkdir(d, &instructions.WorkdirCommand{Path: d.image.Config.WorkingDir}, false); err != nil {
|
if err = dispatchWorkdir(d, &instructions.WorkdirCommand{Path: d.image.Config.WorkingDir}, false, nil); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,9 +472,9 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
|
||||||
case *instructions.RunCommand:
|
case *instructions.RunCommand:
|
||||||
err = dispatchRun(d, c, opt.proxyEnv, cmd.sources, opt)
|
err = dispatchRun(d, c, opt.proxyEnv, cmd.sources, opt)
|
||||||
case *instructions.WorkdirCommand:
|
case *instructions.WorkdirCommand:
|
||||||
err = dispatchWorkdir(d, c, true)
|
err = dispatchWorkdir(d, c, true, &opt)
|
||||||
case *instructions.AddCommand:
|
case *instructions.AddCommand:
|
||||||
err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "", opt)
|
err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, c.Chown, opt)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, src := range c.Sources() {
|
for _, src := range c.Sources() {
|
||||||
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
|
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
|
||||||
|
@ -648,7 +652,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
|
||||||
return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs), true, &d.state)
|
return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs), true, &d.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bool) error {
|
func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bool, opt *dispatchOpt) error {
|
||||||
d.state = d.state.Dir(c.Path)
|
d.state = d.state.Dir(c.Path)
|
||||||
wd := c.Path
|
wd := c.Path
|
||||||
if !path.IsAbs(c.Path) {
|
if !path.IsAbs(c.Path) {
|
||||||
|
@ -656,13 +660,115 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
|
||||||
}
|
}
|
||||||
d.image.Config.WorkingDir = wd
|
d.image.Config.WorkingDir = wd
|
||||||
if commit {
|
if commit {
|
||||||
return commitToHistory(&d.image, "WORKDIR "+wd, false, nil)
|
withLayer := false
|
||||||
|
if wd != "/" && opt != nil && useFileOp(opt.buildArgValues, opt.llbCaps) {
|
||||||
|
mkdirOpt := []llb.MkdirOption{llb.WithParents(true)}
|
||||||
|
if user := d.image.Config.User; user != "" {
|
||||||
|
mkdirOpt = append(mkdirOpt, llb.WithUser(user))
|
||||||
|
}
|
||||||
|
platform := opt.targetPlatform
|
||||||
|
if d.platform != nil {
|
||||||
|
platform = *d.platform
|
||||||
|
}
|
||||||
|
d.state = d.state.File(llb.Mkdir(wd, 0755, mkdirOpt...), llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, c.String(), d.state.Env())), d.prefixPlatform, &platform)))
|
||||||
|
withLayer = true
|
||||||
|
}
|
||||||
|
return commitToHistory(&d.image, "WORKDIR "+wd, withLayer, nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dispatchCopyFileOp(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
|
||||||
|
dest := path.Join("/", pathRelativeToWorkingDir(d.state, c.Dest()))
|
||||||
|
if c.Dest() == "." || c.Dest() == "" || c.Dest()[len(c.Dest())-1] == filepath.Separator {
|
||||||
|
dest += string(filepath.Separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
var copyOpt []llb.CopyOption
|
||||||
|
|
||||||
|
if chown != "" {
|
||||||
|
copyOpt = append(copyOpt, llb.WithUser(chown))
|
||||||
|
}
|
||||||
|
|
||||||
|
commitMessage := bytes.NewBufferString("")
|
||||||
|
if isAddCommand {
|
||||||
|
commitMessage.WriteString("ADD")
|
||||||
|
} else {
|
||||||
|
commitMessage.WriteString("COPY")
|
||||||
|
}
|
||||||
|
|
||||||
|
var a *llb.FileAction
|
||||||
|
|
||||||
|
for _, src := range c.Sources() {
|
||||||
|
commitMessage.WriteString(" " + src)
|
||||||
|
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
|
||||||
|
if !isAddCommand {
|
||||||
|
return errors.New("source can't be a URL for COPY")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resources from remote URLs are not decompressed.
|
||||||
|
// https://docs.docker.com/engine/reference/builder/#add
|
||||||
|
//
|
||||||
|
// Note: mixing up remote archives and local archives in a single ADD instruction
|
||||||
|
// would result in undefined behavior: https://github.com/moby/buildkit/pull/387#discussion_r189494717
|
||||||
|
u, err := url.Parse(src)
|
||||||
|
f := "__unnamed__"
|
||||||
|
if err == nil {
|
||||||
|
if base := path.Base(u.Path); base != "." && base != "/" {
|
||||||
|
f = base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st := llb.HTTP(src, llb.Filename(f), dfCmd(c))
|
||||||
|
|
||||||
|
opts := append([]llb.CopyOption{&llb.CopyInfo{
|
||||||
|
CreateDestPath: true,
|
||||||
|
}}, copyOpt...)
|
||||||
|
|
||||||
|
if a == nil {
|
||||||
|
a = llb.Copy(st, f, dest, opts...)
|
||||||
|
} else {
|
||||||
|
a = a.Copy(st, f, dest, opts...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts := append([]llb.CopyOption{&llb.CopyInfo{
|
||||||
|
FollowSymlinks: true,
|
||||||
|
CopyDirContentsOnly: true,
|
||||||
|
AttemptUnpack: isAddCommand,
|
||||||
|
CreateDestPath: true,
|
||||||
|
AllowWildcard: true,
|
||||||
|
AllowEmptyWildcard: true,
|
||||||
|
}}, copyOpt...)
|
||||||
|
|
||||||
|
if a == nil {
|
||||||
|
a = llb.Copy(sourceState, src, dest, opts...)
|
||||||
|
} else {
|
||||||
|
a = a.Copy(sourceState, src, dest, opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commitMessage.WriteString(" " + c.Dest())
|
||||||
|
|
||||||
|
platform := opt.targetPlatform
|
||||||
|
if d.platform != nil {
|
||||||
|
platform = *d.platform
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOpt := []llb.ConstraintsOpt{llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, cmdToPrint.String(), d.state.Env())), d.prefixPlatform, &platform))}
|
||||||
|
if d.ignoreCache {
|
||||||
|
fileOpt = append(fileOpt, llb.IgnoreCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.state = d.state.File(a, fileOpt...)
|
||||||
|
return commitToHistory(&d.image, commitMessage.String(), true, &d.state)
|
||||||
|
}
|
||||||
|
|
||||||
func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
|
func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
|
||||||
// TODO: this should use CopyOp instead. Current implementation is inefficient
|
if useFileOp(opt.buildArgValues, opt.llbCaps) {
|
||||||
|
return dispatchCopyFileOp(d, c, sourceState, isAddCommand, cmdToPrint, chown, opt)
|
||||||
|
}
|
||||||
|
|
||||||
img := llb.Image(opt.copyImage, llb.MarkImageInternal, llb.Platform(opt.buildPlatforms[0]), WithInternalName("helper image for file operations"))
|
img := llb.Image(opt.copyImage, llb.MarkImageInternal, llb.Platform(opt.buildPlatforms[0]), WithInternalName("helper image for file operations"))
|
||||||
|
|
||||||
dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest()))
|
dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest()))
|
||||||
|
@ -1176,3 +1282,13 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform
|
||||||
out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
|
out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
|
||||||
return out + str
|
return out + str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func useFileOp(args map[string]string, caps *apicaps.CapSet) bool {
|
||||||
|
enabled := true
|
||||||
|
if v, ok := args["BUILDKIT_DISABLE_FILEOP"]; ok {
|
||||||
|
if b, err := strconv.ParseBool(v); err == nil {
|
||||||
|
enabled = !b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return enabled && caps != nil && caps.Supports(pb.CapFileBase) == nil
|
||||||
|
}
|
||||||
|
|
|
@ -82,10 +82,10 @@ func syncTargetDiffCopy(ds grpc.Stream, dest string) error {
|
||||||
}
|
}
|
||||||
return fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
|
return fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
|
||||||
Merge: true,
|
Merge: true,
|
||||||
Filter: func() func(*fstypes.Stat) bool {
|
Filter: func() func(string, *fstypes.Stat) bool {
|
||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
gid := os.Getgid()
|
gid := os.Getgid()
|
||||||
return func(st *fstypes.Stat) bool {
|
return func(p string, st *fstypes.Stat) bool {
|
||||||
st.Uid = uint32(uid)
|
st.Uid = uint32(uid)
|
||||||
st.Gid = uint32(gid)
|
st.Gid = uint32(gid)
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -35,7 +35,7 @@ type SyncedDir struct {
|
||||||
Name string
|
Name string
|
||||||
Dir string
|
Dir string
|
||||||
Excludes []string
|
Excludes []string
|
||||||
Map func(*fstypes.Stat) bool
|
Map func(string, *fstypes.Stat) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFSSyncProvider creates a new provider for sending files from client
|
// NewFSSyncProvider creates a new provider for sending files from client
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs"
|
||||||
|
"github.com/moby/buildkit/snapshot"
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func timestampToTime(ts int64) *time.Time {
|
||||||
|
if ts == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tm := time.Unix(ts/1e9, ts%1e9)
|
||||||
|
return &tm
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *copy.ChownOpt) error {
|
||||||
|
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if action.MakeParents {
|
||||||
|
if err := copy.MkdirAll(p, os.FileMode(action.Mode)&0777, user, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := os.Mkdir(p, os.FileMode(action.Mode)&0777); err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := copy.Chown(p, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *copy.ChownOpt) error {
|
||||||
|
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(p, action.Data, os.FileMode(action.Mode)&0777); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copy.Chown(p, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rm(ctx context.Context, d string, action pb.FileActionRm) error {
|
||||||
|
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(p); err != nil {
|
||||||
|
if os.IsNotExist(errors.Cause(err)) && action.AllowNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *copy.ChownOpt) error {
|
||||||
|
srcPath := cleanPath(action.Src)
|
||||||
|
destPath := cleanPath(action.Dest)
|
||||||
|
|
||||||
|
if !action.CreateDestPath {
|
||||||
|
p, err := fs.RootPath(dest, filepath.Join(filepath.Join("/", action.Dest)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(filepath.Dir(p)); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to stat %s", action.Dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xattrErrorHandler := func(dst, src, key string, err error) error {
|
||||||
|
log.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := []copy.Opt{
|
||||||
|
func(ci *copy.CopyInfo) {
|
||||||
|
ci.Chown = u
|
||||||
|
ci.Utime = timestampToTime(action.Timestamp)
|
||||||
|
if m := int(action.Mode); m != -1 {
|
||||||
|
ci.Mode = &m
|
||||||
|
}
|
||||||
|
ci.CopyDirContents = action.DirCopyContents
|
||||||
|
ci.FollowLinks = action.FollowSymlink
|
||||||
|
},
|
||||||
|
copy.WithXAttrErrorHandler(xattrErrorHandler),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !action.AllowWildcard {
|
||||||
|
if action.AttemptUnpackDockerCompatibility {
|
||||||
|
if ok, err := unpack(ctx, src, srcPath, dest, destPath, u, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy.Copy(ctx, src, srcPath, dest, destPath, opt...)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := copy.ResolveWildcards(src, srcPath, action.FollowSymlink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) == 0 {
|
||||||
|
if action.AllowEmptyWildcard {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Errorf("%s not found", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range m {
|
||||||
|
if action.AttemptUnpackDockerCompatibility {
|
||||||
|
if ok, err := unpack(ctx, src, s, dest, destPath, u, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := copy.Copy(ctx, src, s, dest, destPath, opt...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanPath(s string) string {
|
||||||
|
s2 := filepath.Join("/", s)
|
||||||
|
if strings.HasSuffix(s, "/.") {
|
||||||
|
if s2 != "/" {
|
||||||
|
s2 += "/"
|
||||||
|
}
|
||||||
|
s2 += "."
|
||||||
|
} else if strings.HasSuffix(s, "/") && s2 != "/" {
|
||||||
|
s2 += "/"
|
||||||
|
}
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
|
||||||
|
type Backend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fb *Backend) Mkdir(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkDir) error {
|
||||||
|
mnt, ok := m.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("invalid mount type %T", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := snapshot.LocalMounter(mnt.m)
|
||||||
|
dir, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lm.Unmount()
|
||||||
|
|
||||||
|
u, err := readUser(action.Owner, user, group)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mkdir(ctx, dir, action, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fb *Backend) Mkfile(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkFile) error {
|
||||||
|
mnt, ok := m.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("invalid mount type %T", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := snapshot.LocalMounter(mnt.m)
|
||||||
|
dir, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lm.Unmount()
|
||||||
|
|
||||||
|
u, err := readUser(action.Owner, user, group)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mkfile(ctx, dir, action, u)
|
||||||
|
}
|
||||||
|
func (fb *Backend) Rm(ctx context.Context, m fileoptypes.Mount, action pb.FileActionRm) error {
|
||||||
|
mnt, ok := m.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("invalid mount type %T", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := snapshot.LocalMounter(mnt.m)
|
||||||
|
dir, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lm.Unmount()
|
||||||
|
|
||||||
|
return rm(ctx, dir, action)
|
||||||
|
}
|
||||||
|
func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mount, action pb.FileActionCopy) error {
|
||||||
|
mnt1, ok := m1.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("invalid mount type %T", m1)
|
||||||
|
}
|
||||||
|
mnt2, ok := m2.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("invalid mount type %T", m2)
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := snapshot.LocalMounter(mnt1.m)
|
||||||
|
src, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lm.Unmount()
|
||||||
|
|
||||||
|
lm2 := snapshot.LocalMounter(mnt2.m)
|
||||||
|
dest, err := lm2.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lm2.Unmount()
|
||||||
|
|
||||||
|
u, err := readUser(action.Owner, user, group)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return docopy(ctx, src, dest, action, u)
|
||||||
|
}
|
71
vendor/github.com/moby/buildkit/solver/llbsolver/file/refmanager.go
generated
vendored
Normal file
71
vendor/github.com/moby/buildkit/solver/llbsolver/file/refmanager.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/cache"
|
||||||
|
"github.com/moby/buildkit/snapshot"
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRefManager(cm cache.Manager) *RefManager {
|
||||||
|
return &RefManager{cm: cm}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefManager struct {
|
||||||
|
cm cache.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RefManager) Prepare(ctx context.Context, ref fileoptypes.Ref, readonly bool) (fileoptypes.Mount, error) {
|
||||||
|
ir, ok := ref.(cache.ImmutableRef)
|
||||||
|
if !ok && ref != nil {
|
||||||
|
return nil, errors.Errorf("invalid ref type: %T", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ir != nil && readonly {
|
||||||
|
m, err := ir.Mount(ctx, readonly)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Mount{m: m}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mr, err := rm.cm.New(ctx, ir, cache.WithDescription("fileop target"), cache.CachePolicyRetain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, err := mr.Mount(ctx, readonly)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Mount{m: m, mr: mr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RefManager) Commit(ctx context.Context, mount fileoptypes.Mount) (fileoptypes.Ref, error) {
|
||||||
|
m, ok := mount.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid mount type %T", mount)
|
||||||
|
}
|
||||||
|
if err := m.m.Release(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if m.mr == nil {
|
||||||
|
return nil, errors.Errorf("invalid mount without active ref for commit")
|
||||||
|
}
|
||||||
|
return m.mr.Commit(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mount struct {
|
||||||
|
m snapshot.Mountable
|
||||||
|
mr cache.MutableRef
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mount) Release(ctx context.Context) error {
|
||||||
|
m.m.Release()
|
||||||
|
if m.mr != nil {
|
||||||
|
return m.mr.Release(ctx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *Mount) IsFileOpMount() {}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unpack(ctx context.Context, srcRoot string, src string, destRoot string, dest string, user *copy.ChownOpt, tm *time.Time) (bool, error) {
|
||||||
|
src, err := fs.RootPath(srcRoot, src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !isArchivePath(src) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dest, err = fs.RootPath(destRoot, dest)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := copy.MkdirAll(dest, 0755, user, tm); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return true, chrootarchive.Untar(file, dest, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isArchivePath(path string) bool {
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeType != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
rdr, err := archive.DecompressStream(file)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r := tar.NewReader(rdr)
|
||||||
|
_, err = r.Next()
|
||||||
|
return err == nil
|
||||||
|
}
|
119
vendor/github.com/moby/buildkit/solver/llbsolver/file/user_linux.go
generated
vendored
Normal file
119
vendor/github.com/moby/buildkit/solver/llbsolver/file/user_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs"
|
||||||
|
"github.com/moby/buildkit/snapshot"
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.ChownOpt, error) {
|
||||||
|
if chopt == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var us copy.ChownOpt
|
||||||
|
if chopt.User != nil {
|
||||||
|
switch u := chopt.User.User.(type) {
|
||||||
|
case *pb.UserOpt_ByName:
|
||||||
|
if mu == nil {
|
||||||
|
return nil, errors.Errorf("invalid missing user mount")
|
||||||
|
}
|
||||||
|
mmu, ok := mu.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid mount type %T", mu)
|
||||||
|
}
|
||||||
|
lm := snapshot.LocalMounter(mmu.m)
|
||||||
|
dir, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer lm.Unmount()
|
||||||
|
|
||||||
|
passwdPath, err := user.GetPasswdPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
passwdPath, err = fs.RootPath(dir, passwdPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ufile, err := os.Open(passwdPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ufile.Close()
|
||||||
|
|
||||||
|
users, err := user.ParsePasswdFilter(ufile, func(uu user.User) bool {
|
||||||
|
return uu.Name == u.ByName.Name
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) > 0 {
|
||||||
|
us.Uid = users[0].Uid
|
||||||
|
us.Gid = users[0].Gid
|
||||||
|
}
|
||||||
|
case *pb.UserOpt_ByID:
|
||||||
|
us.Uid = int(u.ByID)
|
||||||
|
us.Gid = int(u.ByID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if chopt.Group != nil {
|
||||||
|
switch u := chopt.Group.User.(type) {
|
||||||
|
case *pb.UserOpt_ByName:
|
||||||
|
if mg == nil {
|
||||||
|
return nil, errors.Errorf("invalid missing group mount")
|
||||||
|
}
|
||||||
|
mmg, ok := mg.(*Mount)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid mount type %T", mg)
|
||||||
|
}
|
||||||
|
lm := snapshot.LocalMounter(mmg.m)
|
||||||
|
dir, err := lm.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer lm.Unmount()
|
||||||
|
|
||||||
|
groupPath, err := user.GetGroupPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groupPath, err = fs.RootPath(dir, groupPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gfile, err := os.Open(groupPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer gfile.Close()
|
||||||
|
|
||||||
|
groups, err := user.ParseGroupFilter(gfile, func(g user.Group) bool {
|
||||||
|
return g.Name == u.ByName.Name
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(groups) > 0 {
|
||||||
|
us.Gid = groups[0].Gid
|
||||||
|
}
|
||||||
|
case *pb.UserOpt_ByID:
|
||||||
|
us.Gid = int(u.ByID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &us, nil
|
||||||
|
}
|
14
vendor/github.com/moby/buildkit/solver/llbsolver/file/user_nolinux.go
generated
vendored
Normal file
14
vendor/github.com/moby/buildkit/solver/llbsolver/file/user_nolinux.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.ChownOpt, error) {
|
||||||
|
return nil, errors.New("only implemented in linux")
|
||||||
|
}
|
|
@ -149,7 +149,7 @@ func (e *execOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo
|
||||||
cm.Deps[i].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
|
cm.Deps[i].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
|
||||||
}
|
}
|
||||||
if !dep.NoContentBasedHash {
|
if !dep.NoContentBasedHash {
|
||||||
cm.Deps[i].ComputeDigestFunc = llbsolver.NewContentHashFunc(dedupePaths(dep.Selectors))
|
cm.Deps[i].ComputeDigestFunc = llbsolver.NewContentHashFunc(toSelectors(dedupePaths(dep.Selectors)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +180,14 @@ func dedupePaths(inp []string) []string {
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toSelectors(p []string) []llbsolver.Selector {
|
||||||
|
sel := make([]llbsolver.Selector, 0, len(p))
|
||||||
|
for _, p := range p {
|
||||||
|
sel = append(sel, llbsolver.Selector{Path: p, FollowLinks: true})
|
||||||
|
}
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
|
||||||
type dep struct {
|
type dep struct {
|
||||||
Selectors []string
|
Selectors []string
|
||||||
NoContentBasedHash bool
|
NoContentBasedHash bool
|
||||||
|
|
|
@ -0,0 +1,580 @@
|
||||||
|
package ops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/cache"
|
||||||
|
"github.com/moby/buildkit/cache/metadata"
|
||||||
|
"github.com/moby/buildkit/solver"
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver"
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver/file"
|
||||||
|
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/moby/buildkit/util/flightcontrol"
|
||||||
|
"github.com/moby/buildkit/worker"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileCacheType = "buildkit.file.v0"
|
||||||
|
|
||||||
|
type fileOp struct {
|
||||||
|
op *pb.FileOp
|
||||||
|
md *metadata.Store
|
||||||
|
w worker.Worker
|
||||||
|
solver *FileOpSolver
|
||||||
|
numInputs int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileOp(v solver.Vertex, op *pb.Op_File, cm cache.Manager, md *metadata.Store, w worker.Worker) (solver.Op, error) {
|
||||||
|
return &fileOp{
|
||||||
|
op: op.File,
|
||||||
|
md: md,
|
||||||
|
numInputs: len(v.Inputs()),
|
||||||
|
w: w,
|
||||||
|
solver: NewFileOpSolver(&file.Backend{}, file.NewRefManager(cm)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, bool, error) {
|
||||||
|
selectors := map[int]map[llbsolver.Selector]struct{}{}
|
||||||
|
invalidSelectors := map[int]struct{}{}
|
||||||
|
|
||||||
|
actions := make([][]byte, 0, len(f.op.Actions))
|
||||||
|
|
||||||
|
markInvalid := func(idx pb.InputIndex) {
|
||||||
|
if idx != -1 {
|
||||||
|
invalidSelectors[int(idx)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, action := range f.op.Actions {
|
||||||
|
var dt []byte
|
||||||
|
var err error
|
||||||
|
switch a := action.Action.(type) {
|
||||||
|
case *pb.FileAction_Mkdir:
|
||||||
|
p := *a.Mkdir
|
||||||
|
markInvalid(action.Input)
|
||||||
|
processOwner(p.Owner, selectors)
|
||||||
|
dt, err = json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
case *pb.FileAction_Mkfile:
|
||||||
|
p := *a.Mkfile
|
||||||
|
markInvalid(action.Input)
|
||||||
|
processOwner(p.Owner, selectors)
|
||||||
|
dt, err = json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
case *pb.FileAction_Rm:
|
||||||
|
p := *a.Rm
|
||||||
|
markInvalid(action.Input)
|
||||||
|
dt, err = json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
case *pb.FileAction_Copy:
|
||||||
|
p := *a.Copy
|
||||||
|
markInvalid(action.Input)
|
||||||
|
processOwner(p.Owner, selectors)
|
||||||
|
if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs {
|
||||||
|
addSelector(selectors, int(action.SecondaryInput), p.Src, p.AllowWildcard, p.FollowSymlink)
|
||||||
|
p.Src = path.Base(p.Src)
|
||||||
|
}
|
||||||
|
dt, err = json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = append(actions, dt)
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := json.Marshal(struct {
|
||||||
|
Type string
|
||||||
|
Actions [][]byte
|
||||||
|
}{
|
||||||
|
Type: fileCacheType,
|
||||||
|
Actions: actions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := &solver.CacheMap{
|
||||||
|
Digest: digest.FromBytes(dt),
|
||||||
|
Deps: make([]struct {
|
||||||
|
Selector digest.Digest
|
||||||
|
ComputeDigestFunc solver.ResultBasedCacheFunc
|
||||||
|
}, f.numInputs),
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, m := range selectors {
|
||||||
|
if _, ok := invalidSelectors[idx]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dgsts := make([][]byte, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
dgsts = append(dgsts, []byte(k.Path))
|
||||||
|
}
|
||||||
|
sort.Slice(dgsts, func(i, j int) bool {
|
||||||
|
return bytes.Compare(dgsts[i], dgsts[j]) > 0
|
||||||
|
})
|
||||||
|
cm.Deps[idx].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
|
||||||
|
|
||||||
|
cm.Deps[idx].ComputeDigestFunc = llbsolver.NewContentHashFunc(dedupeSelectors(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cm, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileOp) Exec(ctx context.Context, inputs []solver.Result) ([]solver.Result, error) {
|
||||||
|
inpRefs := make([]fileoptypes.Ref, 0, len(inputs))
|
||||||
|
for _, inp := range inputs {
|
||||||
|
workerRef, ok := inp.Sys().(*worker.WorkerRef)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid reference for exec %T", inp.Sys())
|
||||||
|
}
|
||||||
|
inpRefs = append(inpRefs, workerRef.ImmutableRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
outs, err := f.solver.Solve(ctx, inpRefs, f.op.Actions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outResults := make([]solver.Result, 0, len(outs))
|
||||||
|
for _, out := range outs {
|
||||||
|
outResults = append(outResults, worker.NewWorkerRefResult(out.(cache.ImmutableRef), f.w))
|
||||||
|
}
|
||||||
|
|
||||||
|
return outResults, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSelector(m map[int]map[llbsolver.Selector]struct{}, idx int, sel string, wildcard, followLinks bool) {
|
||||||
|
mm, ok := m[idx]
|
||||||
|
if !ok {
|
||||||
|
mm = map[llbsolver.Selector]struct{}{}
|
||||||
|
m[idx] = mm
|
||||||
|
}
|
||||||
|
s := llbsolver.Selector{Path: sel}
|
||||||
|
|
||||||
|
if wildcard && containsWildcards(sel) {
|
||||||
|
s.Wildcard = true
|
||||||
|
}
|
||||||
|
if followLinks {
|
||||||
|
s.FollowLinks = true
|
||||||
|
}
|
||||||
|
mm[s] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsWildcards(name string) bool {
|
||||||
|
isWindows := runtime.GOOS == "windows"
|
||||||
|
for i := 0; i < len(name); i++ {
|
||||||
|
ch := name[i]
|
||||||
|
if ch == '\\' && !isWindows {
|
||||||
|
i++
|
||||||
|
} else if ch == '*' || ch == '?' || ch == '[' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func dedupeSelectors(m map[llbsolver.Selector]struct{}) []llbsolver.Selector {
|
||||||
|
paths := make([]string, 0, len(m))
|
||||||
|
pathsFollow := make([]string, 0, len(m))
|
||||||
|
for sel := range m {
|
||||||
|
if !sel.Wildcard {
|
||||||
|
if sel.FollowLinks {
|
||||||
|
pathsFollow = append(pathsFollow, sel.Path)
|
||||||
|
} else {
|
||||||
|
paths = append(paths, sel.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paths = dedupePaths(paths)
|
||||||
|
pathsFollow = dedupePaths(pathsFollow)
|
||||||
|
selectors := make([]llbsolver.Selector, 0, len(m))
|
||||||
|
|
||||||
|
for _, p := range paths {
|
||||||
|
selectors = append(selectors, llbsolver.Selector{Path: p})
|
||||||
|
}
|
||||||
|
for _, p := range pathsFollow {
|
||||||
|
selectors = append(selectors, llbsolver.Selector{Path: p, FollowLinks: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
for sel := range m {
|
||||||
|
if sel.Wildcard {
|
||||||
|
selectors = append(selectors, sel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(selectors, func(i, j int) bool {
|
||||||
|
return selectors[i].Path < selectors[j].Path
|
||||||
|
})
|
||||||
|
|
||||||
|
return selectors
|
||||||
|
}
|
||||||
|
|
||||||
|
func processOwner(chopt *pb.ChownOpt, selectors map[int]map[llbsolver.Selector]struct{}) error {
|
||||||
|
if chopt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if chopt.User != nil {
|
||||||
|
if u, ok := chopt.User.User.(*pb.UserOpt_ByName); ok {
|
||||||
|
if u.ByName.Input < 0 {
|
||||||
|
return errors.Errorf("invalid user index %d", u.ByName.Input)
|
||||||
|
}
|
||||||
|
addSelector(selectors, int(u.ByName.Input), "/etc/passwd", false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chopt.Group != nil {
|
||||||
|
if u, ok := chopt.Group.User.(*pb.UserOpt_ByName); ok {
|
||||||
|
if u.ByName.Input < 0 {
|
||||||
|
return errors.Errorf("invalid user index %d", u.ByName.Input)
|
||||||
|
}
|
||||||
|
addSelector(selectors, int(u.ByName.Input), "/etc/group", false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileOpSolver(b fileoptypes.Backend, r fileoptypes.RefManager) *FileOpSolver {
|
||||||
|
return &FileOpSolver{
|
||||||
|
b: b,
|
||||||
|
r: r,
|
||||||
|
outs: map[int]int{},
|
||||||
|
ins: map[int]input{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileOpSolver struct {
|
||||||
|
b fileoptypes.Backend
|
||||||
|
r fileoptypes.RefManager
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
outs map[int]int
|
||||||
|
ins map[int]input
|
||||||
|
g flightcontrol.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
requiresCommit bool
|
||||||
|
mount fileoptypes.Mount
|
||||||
|
ref fileoptypes.Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileOpSolver) Solve(ctx context.Context, inputs []fileoptypes.Ref, actions []*pb.FileAction) ([]fileoptypes.Ref, error) {
|
||||||
|
for i, a := range actions {
|
||||||
|
if int(a.Input) < -1 || int(a.Input) >= len(inputs)+len(actions) {
|
||||||
|
return nil, errors.Errorf("invalid input index %d, %d provided", a.Input, len(inputs)+len(actions))
|
||||||
|
}
|
||||||
|
if int(a.SecondaryInput) < -1 || int(a.SecondaryInput) >= len(inputs)+len(actions) {
|
||||||
|
return nil, errors.Errorf("invalid secondary input index %d, %d provided", a.Input, len(inputs))
|
||||||
|
}
|
||||||
|
|
||||||
|
inp, ok := s.ins[int(a.Input)]
|
||||||
|
if ok {
|
||||||
|
inp.requiresCommit = true
|
||||||
|
}
|
||||||
|
s.ins[int(a.Input)] = inp
|
||||||
|
|
||||||
|
inp, ok = s.ins[int(a.SecondaryInput)]
|
||||||
|
if ok {
|
||||||
|
inp.requiresCommit = true
|
||||||
|
}
|
||||||
|
s.ins[int(a.SecondaryInput)] = inp
|
||||||
|
|
||||||
|
if a.Output != -1 {
|
||||||
|
if _, ok := s.outs[int(a.Output)]; ok {
|
||||||
|
return nil, errors.Errorf("duplicate output %d", a.Output)
|
||||||
|
}
|
||||||
|
idx := len(inputs) + i
|
||||||
|
s.outs[int(a.Output)] = idx
|
||||||
|
s.ins[idx] = input{requiresCommit: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.outs) == 0 {
|
||||||
|
return nil, errors.Errorf("no outputs specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s.outs); i++ {
|
||||||
|
if _, ok := s.outs[i]; !ok {
|
||||||
|
return nil, errors.Errorf("missing output index %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, in := range s.ins {
|
||||||
|
if in.ref == nil && in.mount != nil {
|
||||||
|
in.mount.Release(context.TODO())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
outs := make([]fileoptypes.Ref, len(s.outs))
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
for i, idx := range s.outs {
|
||||||
|
func(i, idx int) {
|
||||||
|
eg.Go(func() error {
|
||||||
|
if err := s.validate(idx, inputs, actions, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inp, err := s.getInput(ctx, idx, inputs, actions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outs[i] = inp.ref
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(i, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
for _, r := range outs {
|
||||||
|
if r != nil {
|
||||||
|
r.Release(context.TODO())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return outs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileOpSolver) validate(idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction, loaded []int) error {
|
||||||
|
for _, check := range loaded {
|
||||||
|
if idx == check {
|
||||||
|
return errors.Errorf("loop from index %d", idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx < len(inputs) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
loaded = append(loaded, idx)
|
||||||
|
action := actions[idx-len(inputs)]
|
||||||
|
for _, inp := range []int{int(action.Input), int(action.SecondaryInput)} {
|
||||||
|
if err := s.validate(inp, inputs, actions, loaded); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction) (input, error) {
|
||||||
|
inp, err := s.g.Do(ctx, fmt.Sprintf("inp-%d", idx), func(ctx context.Context) (_ interface{}, err error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
inp := s.ins[idx]
|
||||||
|
s.mu.Unlock()
|
||||||
|
if inp.mount != nil || inp.ref != nil {
|
||||||
|
return inp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx < len(inputs) {
|
||||||
|
inp.ref = inputs[idx]
|
||||||
|
s.mu.Lock()
|
||||||
|
s.ins[idx] = inp
|
||||||
|
s.mu.Unlock()
|
||||||
|
return inp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inpMount, inpMountSecondary fileoptypes.Mount
|
||||||
|
var toRelease []fileoptypes.Mount
|
||||||
|
var inpMountPrepared bool
|
||||||
|
defer func() {
|
||||||
|
for _, m := range toRelease {
|
||||||
|
m.Release(context.TODO())
|
||||||
|
}
|
||||||
|
if err != nil && inpMount != nil && inpMountPrepared {
|
||||||
|
inpMount.Release(context.TODO())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
action := actions[idx-len(inputs)]
|
||||||
|
|
||||||
|
loadInput := func(ctx context.Context) func() error {
|
||||||
|
return func() error {
|
||||||
|
inp, err := s.getInput(ctx, int(action.Input), inputs, actions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inp.ref != nil {
|
||||||
|
m, err := s.r.Prepare(ctx, inp.ref, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inpMount = m
|
||||||
|
inpMountPrepared = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inpMount = inp.mount
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSecondaryInput := func(ctx context.Context) func() error {
|
||||||
|
return func() error {
|
||||||
|
inp, err := s.getInput(ctx, int(action.SecondaryInput), inputs, actions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inp.ref != nil {
|
||||||
|
m, err := s.r.Prepare(ctx, inp.ref, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inpMountSecondary = m
|
||||||
|
toRelease = append(toRelease, m)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inpMountSecondary = inp.mount
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUser := func(ctx context.Context, uopt *pb.UserOpt) (fileoptypes.Mount, error) {
|
||||||
|
if uopt == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
switch u := uopt.User.(type) {
|
||||||
|
case *pb.UserOpt_ByName:
|
||||||
|
var m fileoptypes.Mount
|
||||||
|
if u.ByName.Input < 0 {
|
||||||
|
return nil, errors.Errorf("invalid user index: %d", u.ByName.Input)
|
||||||
|
}
|
||||||
|
inp, err := s.getInput(ctx, int(u.ByName.Input), inputs, actions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if inp.ref != nil {
|
||||||
|
mm, err := s.r.Prepare(ctx, inp.ref, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
toRelease = append(toRelease, mm)
|
||||||
|
m = mm
|
||||||
|
} else {
|
||||||
|
m = inp.mount
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOwner := func(ctx context.Context, chopt *pb.ChownOpt) (fileoptypes.Mount, fileoptypes.Mount, error) {
|
||||||
|
if chopt == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
um, err := loadUser(ctx, chopt.User)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
gm, err := loadUser(ctx, chopt.Group)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return um, gm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if action.Input != -1 && action.SecondaryInput != -1 {
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
eg.Go(loadInput(ctx))
|
||||||
|
eg.Go(loadSecondaryInput(ctx))
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if action.Input != -1 {
|
||||||
|
if err := loadInput(ctx)(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if action.SecondaryInput != -1 {
|
||||||
|
if err := loadSecondaryInput(ctx)(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inpMount == nil {
|
||||||
|
m, err := s.r.Prepare(ctx, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inpMount = m
|
||||||
|
inpMountPrepared = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a := action.Action.(type) {
|
||||||
|
case *pb.FileAction_Mkdir:
|
||||||
|
user, group, err := loadOwner(ctx, a.Mkdir.Owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.b.Mkdir(ctx, inpMount, user, group, *a.Mkdir); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *pb.FileAction_Mkfile:
|
||||||
|
user, group, err := loadOwner(ctx, a.Mkfile.Owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.b.Mkfile(ctx, inpMount, user, group, *a.Mkfile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *pb.FileAction_Rm:
|
||||||
|
if err := s.b.Rm(ctx, inpMount, *a.Rm); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *pb.FileAction_Copy:
|
||||||
|
if inpMountSecondary == nil {
|
||||||
|
m, err := s.r.Prepare(ctx, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inpMountSecondary = m
|
||||||
|
}
|
||||||
|
user, group, err := loadOwner(ctx, a.Copy.Owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.b.Copy(ctx, inpMountSecondary, inpMount, user, group, *a.Copy); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("invalid action type %T", action.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inp.requiresCommit {
|
||||||
|
ref, err := s.r.Commit(ctx, inpMount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inp.ref = ref
|
||||||
|
} else {
|
||||||
|
inp.mount = inpMount
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
s.ins[idx] = inp
|
||||||
|
s.mu.Unlock()
|
||||||
|
return inp, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return input{}, err
|
||||||
|
}
|
||||||
|
return inp.(input), err
|
||||||
|
}
|
28
vendor/github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes/types.go
generated
vendored
Normal file
28
vendor/github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes/types.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package fileoptypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ref interface {
|
||||||
|
Release(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mount interface {
|
||||||
|
IsFileOpMount()
|
||||||
|
Release(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Backend interface {
|
||||||
|
Mkdir(context.Context, Mount, Mount, Mount, pb.FileActionMkDir) error
|
||||||
|
Mkfile(context.Context, Mount, Mount, Mount, pb.FileActionMkFile) error
|
||||||
|
Rm(context.Context, Mount, pb.FileActionRm) error
|
||||||
|
Copy(context.Context, Mount, Mount, Mount, Mount, pb.FileActionCopy) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefManager interface {
|
||||||
|
Prepare(ctx context.Context, ref Ref, readonly bool) (Mount, error)
|
||||||
|
Commit(ctx context.Context, mount Mount) (Ref, error)
|
||||||
|
}
|
|
@ -13,7 +13,13 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewContentHashFunc(selectors []string) solver.ResultBasedCacheFunc {
|
type Selector struct {
|
||||||
|
Path string
|
||||||
|
Wildcard bool
|
||||||
|
FollowLinks bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContentHashFunc(selectors []Selector) solver.ResultBasedCacheFunc {
|
||||||
return func(ctx context.Context, res solver.Result) (digest.Digest, error) {
|
return func(ctx context.Context, res solver.Result) (digest.Digest, error) {
|
||||||
ref, ok := res.Sys().(*worker.WorkerRef)
|
ref, ok := res.Sys().(*worker.WorkerRef)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -21,7 +27,7 @@ func NewContentHashFunc(selectors []string) solver.ResultBasedCacheFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(selectors) == 0 {
|
if len(selectors) == 0 {
|
||||||
selectors = []string{""}
|
selectors = []Selector{{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
dgsts := make([][]byte, len(selectors))
|
dgsts := make([][]byte, len(selectors))
|
||||||
|
@ -32,11 +38,19 @@ func NewContentHashFunc(selectors []string) solver.ResultBasedCacheFunc {
|
||||||
// FIXME(tonistiigi): enabling this parallelization seems to create wrong results for some big inputs(like gobuild)
|
// FIXME(tonistiigi): enabling this parallelization seems to create wrong results for some big inputs(like gobuild)
|
||||||
// func(i int) {
|
// func(i int) {
|
||||||
// eg.Go(func() error {
|
// eg.Go(func() error {
|
||||||
dgst, err := contenthash.Checksum(ctx, ref.ImmutableRef, path.Join("/", sel), true)
|
if !sel.Wildcard {
|
||||||
|
dgst, err := contenthash.Checksum(ctx, ref.ImmutableRef, path.Join("/", sel.Path), sel.FollowLinks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
dgsts[i] = []byte(dgst)
|
dgsts[i] = []byte(dgst)
|
||||||
|
} else {
|
||||||
|
dgst, err := contenthash.ChecksumWildcard(ctx, ref.ImmutableRef, path.Join("/", sel.Path), sel.FollowLinks)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
dgsts[i] = []byte(dgst)
|
||||||
|
}
|
||||||
// return nil
|
// return nil
|
||||||
// })
|
// })
|
||||||
// }(i)
|
// }(i)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package llbsolver
|
package llbsolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
|
@ -228,9 +229,29 @@ func llbOpName(op *pb.Op) string {
|
||||||
return op.Source.Identifier
|
return op.Source.Identifier
|
||||||
case *pb.Op_Exec:
|
case *pb.Op_Exec:
|
||||||
return strings.Join(op.Exec.Meta.Args, " ")
|
return strings.Join(op.Exec.Meta.Args, " ")
|
||||||
|
case *pb.Op_File:
|
||||||
|
return fileOpName(op.File.Actions)
|
||||||
case *pb.Op_Build:
|
case *pb.Op_Build:
|
||||||
return "build"
|
return "build"
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fileOpName(actions []*pb.FileAction) string {
|
||||||
|
names := make([]string, 0, len(actions))
|
||||||
|
for _, action := range actions {
|
||||||
|
switch a := action.Action.(type) {
|
||||||
|
case *pb.FileAction_Mkdir:
|
||||||
|
names = append(names, fmt.Sprintf("mkdir %s", a.Mkdir.Path))
|
||||||
|
case *pb.FileAction_Mkfile:
|
||||||
|
names = append(names, fmt.Sprintf("mkfile %s", a.Mkfile.Path))
|
||||||
|
case *pb.FileAction_Rm:
|
||||||
|
names = append(names, fmt.Sprintf("rm %s", a.Rm.Path))
|
||||||
|
case *pb.FileAction_Copy:
|
||||||
|
names = append(names, fmt.Sprintf("copy %s %s", a.Copy.Src, a.Copy.Dest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(names, ", ")
|
||||||
|
}
|
||||||
|
|
|
@ -21,3 +21,5 @@ const AttrImageResolveModeDefault = "default"
|
||||||
const AttrImageResolveModeForcePull = "pull"
|
const AttrImageResolveModeForcePull = "pull"
|
||||||
const AttrImageResolveModePreferLocal = "local"
|
const AttrImageResolveModePreferLocal = "local"
|
||||||
const AttrImageRecordType = "image.recordtype"
|
const AttrImageRecordType = "image.recordtype"
|
||||||
|
|
||||||
|
type IsFileAction = isFileAction_Action
|
||||||
|
|
|
@ -43,6 +43,8 @@ const (
|
||||||
CapExecMountSSH apicaps.CapID = "exec.mount.ssh"
|
CapExecMountSSH apicaps.CapID = "exec.mount.ssh"
|
||||||
CapExecCgroupsMounted apicaps.CapID = "exec.cgroup"
|
CapExecCgroupsMounted apicaps.CapID = "exec.cgroup"
|
||||||
|
|
||||||
|
CapFileBase apicaps.CapID = "file.base"
|
||||||
|
|
||||||
CapConstraints apicaps.CapID = "constraints"
|
CapConstraints apicaps.CapID = "constraints"
|
||||||
CapPlatform apicaps.CapID = "platform"
|
CapPlatform apicaps.CapID = "platform"
|
||||||
|
|
||||||
|
@ -226,6 +228,16 @@ func init() {
|
||||||
Status: apicaps.CapStatusExperimental,
|
Status: apicaps.CapStatusExperimental,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Caps.Init(apicaps.Cap{
|
||||||
|
ID: CapFileBase,
|
||||||
|
Enabled: true,
|
||||||
|
Status: apicaps.CapStatusPrerelease,
|
||||||
|
SupportedHint: map[string]string{
|
||||||
|
"docker": "Docker v19.03",
|
||||||
|
"buildkit": "BuildKit v0.5.0",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
Caps.Init(apicaps.Cap{
|
Caps.Init(apicaps.Cap{
|
||||||
ID: CapConstraints,
|
ID: CapConstraints,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,7 @@ message Op {
|
||||||
oneof op {
|
oneof op {
|
||||||
ExecOp exec = 2;
|
ExecOp exec = 2;
|
||||||
SourceOp source = 3;
|
SourceOp source = 3;
|
||||||
CopyOp copy = 4;
|
FileOp file = 4;
|
||||||
BuildOp build = 5;
|
BuildOp build = 5;
|
||||||
}
|
}
|
||||||
Platform platform = 10;
|
Platform platform = 10;
|
||||||
|
@ -134,18 +134,6 @@ message SSHOpt {
|
||||||
bool optional = 5;
|
bool optional = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyOp copies files across Ops.
|
|
||||||
message CopyOp {
|
|
||||||
repeated CopySource src = 1;
|
|
||||||
string dest = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopySource specifies a source for CopyOp.
|
|
||||||
message CopySource {
|
|
||||||
int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
|
|
||||||
string selector = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SourceOp specifies a source such as build contexts and images.
|
// SourceOp specifies a source such as build contexts and images.
|
||||||
message SourceOp {
|
message SourceOp {
|
||||||
// TODO: use source type or any type instead of URL protocol.
|
// TODO: use source type or any type instead of URL protocol.
|
||||||
|
@ -212,3 +200,100 @@ message HostIP {
|
||||||
string Host = 1;
|
string Host = 1;
|
||||||
string IP = 2;
|
string IP = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FileOp {
|
||||||
|
repeated FileAction actions = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileAction {
|
||||||
|
int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // could be real input or target (target index + max input index)
|
||||||
|
int64 secondaryInput = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // --//--
|
||||||
|
int64 output = 3 [(gogoproto.customtype) = "OutputIndex", (gogoproto.nullable) = false];
|
||||||
|
oneof action {
|
||||||
|
// FileActionCopy copies files from secondaryInput on top of input
|
||||||
|
FileActionCopy copy = 4;
|
||||||
|
// FileActionMkFile creates a new file
|
||||||
|
FileActionMkFile mkfile = 5;
|
||||||
|
// FileActionMkDir creates a new directory
|
||||||
|
FileActionMkDir mkdir = 6;
|
||||||
|
// FileActionRm removes a file
|
||||||
|
FileActionRm rm = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileActionCopy {
|
||||||
|
// src is the source path
|
||||||
|
string src = 1;
|
||||||
|
// dest path
|
||||||
|
string dest = 2;
|
||||||
|
// optional owner override
|
||||||
|
ChownOpt owner = 3;
|
||||||
|
// optional permission bits override
|
||||||
|
int32 mode = 4;
|
||||||
|
// followSymlink resolves symlinks in src
|
||||||
|
bool followSymlink = 5;
|
||||||
|
// dirCopyContents only copies contents if src is a directory
|
||||||
|
bool dirCopyContents = 6;
|
||||||
|
// attemptUnpackDockerCompatibility detects if src is an archive to unpack it instead
|
||||||
|
bool attemptUnpackDockerCompatibility = 7;
|
||||||
|
// createDestPath creates dest path directories if needed
|
||||||
|
bool createDestPath = 8;
|
||||||
|
// allowWildcard allows filepath.Match wildcards in src path
|
||||||
|
bool allowWildcard = 9;
|
||||||
|
// allowEmptyWildcard doesn't fail the whole copy if wildcard doesn't resolve to files
|
||||||
|
bool allowEmptyWildcard = 10;
|
||||||
|
// optional created time override
|
||||||
|
int64 timestamp = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileActionMkFile {
|
||||||
|
// path for the new file
|
||||||
|
string path = 1;
|
||||||
|
// permission bits
|
||||||
|
int32 mode = 2;
|
||||||
|
// data is the new file contents
|
||||||
|
bytes data = 3;
|
||||||
|
// optional owner for the new file
|
||||||
|
ChownOpt owner = 4;
|
||||||
|
// optional created time override
|
||||||
|
int64 timestamp = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileActionMkDir {
|
||||||
|
// path for the new directory
|
||||||
|
string path = 1;
|
||||||
|
// permission bits
|
||||||
|
int32 mode = 2;
|
||||||
|
// makeParents creates parent directories as well if needed
|
||||||
|
bool makeParents = 3;
|
||||||
|
// optional owner for the new directory
|
||||||
|
ChownOpt owner = 4;
|
||||||
|
// optional created time override
|
||||||
|
int64 timestamp = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileActionRm {
|
||||||
|
// path to remove
|
||||||
|
string path = 1;
|
||||||
|
// allowNotFound doesn't fail the rm if file is not found
|
||||||
|
bool allowNotFound = 2;
|
||||||
|
// allowWildcard allows filepath.Match wildcards in path
|
||||||
|
bool allowWildcard = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChownOpt {
|
||||||
|
UserOpt user = 1;
|
||||||
|
UserOpt group = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserOpt {
|
||||||
|
oneof user {
|
||||||
|
NamedUserOpt byName = 1;
|
||||||
|
uint32 byID = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message NamedUserOpt {
|
||||||
|
string name = 1;
|
||||||
|
int64 input = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ type contextKeyT string
|
||||||
|
|
||||||
var contextKey = contextKeyT("buildkit/util/flightcontrol.progress")
|
var contextKey = contextKeyT("buildkit/util/flightcontrol.progress")
|
||||||
|
|
||||||
// Group is a flightcontrol syncronization group
|
// Group is a flightcontrol synchronization group
|
||||||
type Group struct {
|
type Group struct {
|
||||||
mu sync.Mutex // protects m
|
mu sync.Mutex // protects m
|
||||||
m map[string]*call // lazily initialized
|
m map[string]*call // lazily initialized
|
||||||
|
|
|
@ -63,7 +63,7 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers := []images.Handler{
|
handlers := []images.Handler{
|
||||||
remotes.FetchHandler(cache, fetcher),
|
fetchWithoutRoot(remotes.FetchHandler(cache, fetcher)),
|
||||||
childrenConfigHandler(cache, platform),
|
childrenConfigHandler(cache, platform),
|
||||||
}
|
}
|
||||||
if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
|
if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
|
||||||
|
@ -82,6 +82,16 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
|
||||||
return desc.Digest, dt, nil
|
return desc.Digest, dt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchWithoutRoot(fetch images.HandlerFunc) images.HandlerFunc {
|
||||||
|
return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
|
||||||
|
if desc.Annotations == nil {
|
||||||
|
desc.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
desc.Annotations["buildkit/noroot"] = "true"
|
||||||
|
return fetch(ctx, desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc {
|
func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc {
|
||||||
return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
|
return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
|
||||||
var descs []specs.Descriptor
|
var descs []specs.Descriptor
|
||||||
|
|
|
@ -0,0 +1,423 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
buffer := make([]byte, 32*1024)
|
||||||
|
return &buffer
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootPath(root, p string, followLinks bool) (string, error) {
|
||||||
|
p = filepath.Join("/", p)
|
||||||
|
if p == "/" {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
if followLinks {
|
||||||
|
return fs.RootPath(root, p)
|
||||||
|
}
|
||||||
|
d, f := filepath.Split(p)
|
||||||
|
ppath, err := fs.RootPath(root, d)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(ppath, f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveWildcards(root, src string, followLinks bool) ([]string, error) {
|
||||||
|
d1, d2 := splitWildcards(src)
|
||||||
|
if d2 != "" {
|
||||||
|
p, err := rootPath(root, d1, followLinks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
matches, err := resolveWildcards(p, d2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, m := range matches {
|
||||||
|
p, err := rel(root, m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
matches[i] = p
|
||||||
|
}
|
||||||
|
return matches, nil
|
||||||
|
}
|
||||||
|
return []string{d1}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies files using `cp -a` semantics.
|
||||||
|
// Copy is likely unsafe to be used in non-containerized environments.
|
||||||
|
func Copy(ctx context.Context, srcRoot, src, dstRoot, dst string, opts ...Opt) error {
|
||||||
|
var ci CopyInfo
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&ci)
|
||||||
|
}
|
||||||
|
ensureDstPath := dst
|
||||||
|
if d, f := filepath.Split(dst); f != "" && f != "." {
|
||||||
|
ensureDstPath = d
|
||||||
|
}
|
||||||
|
if ensureDstPath != "" {
|
||||||
|
ensureDstPath, err := fs.RootPath(dstRoot, ensureDstPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := MkdirAll(ensureDstPath, 0755, ci.Chown, ci.Utime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst, err := fs.RootPath(dstRoot, filepath.Clean(dst))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newCopier(ci.Chown, ci.Utime, ci.Mode, ci.XAttrErrorHandler)
|
||||||
|
srcs := []string{src}
|
||||||
|
|
||||||
|
if ci.AllowWildcards {
|
||||||
|
matches, err := ResolveWildcards(srcRoot, src, ci.FollowLinks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return errors.Errorf("no matches found: %s", src)
|
||||||
|
}
|
||||||
|
srcs = matches
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, src := range srcs {
|
||||||
|
srcFollowed, err := rootPath(srcRoot, src, ci.FollowLinks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst, err := c.prepareTargetDir(srcFollowed, src, dst, ci.CopyDirContents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.copy(ctx, srcFollowed, dst, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) prepareTargetDir(srcFollowed, src, destPath string, copyDirContents bool) (string, error) {
|
||||||
|
fiSrc, err := os.Lstat(srcFollowed)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fiDest, err := os.Stat(destPath)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return "", errors.Wrap(err, "failed to lstat destination path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!copyDirContents && fiSrc.IsDir() && fiDest != nil) || (!fiSrc.IsDir() && fiDest != nil && fiDest.IsDir()) {
|
||||||
|
destPath = filepath.Join(destPath, filepath.Base(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
target := filepath.Dir(destPath)
|
||||||
|
|
||||||
|
if copyDirContents && fiSrc.IsDir() && fiDest == nil {
|
||||||
|
target = destPath
|
||||||
|
}
|
||||||
|
if err := MkdirAll(target, 0755, c.chown, c.utime); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return destPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChownOpt struct {
|
||||||
|
Uid, Gid int
|
||||||
|
}
|
||||||
|
|
||||||
|
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
|
||||||
|
|
||||||
|
type CopyInfo struct {
|
||||||
|
Chown *ChownOpt
|
||||||
|
Utime *time.Time
|
||||||
|
AllowWildcards bool
|
||||||
|
Mode *int
|
||||||
|
XAttrErrorHandler XAttrErrorHandler
|
||||||
|
CopyDirContents bool
|
||||||
|
FollowLinks bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Opt func(*CopyInfo)
|
||||||
|
|
||||||
|
func WithCopyInfo(ci CopyInfo) func(*CopyInfo) {
|
||||||
|
return func(c *CopyInfo) {
|
||||||
|
*c = ci
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithChown(uid, gid int) Opt {
|
||||||
|
return func(ci *CopyInfo) {
|
||||||
|
ci.Chown = &ChownOpt{Uid: uid, Gid: gid}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllowWildcards(ci *CopyInfo) {
|
||||||
|
ci.AllowWildcards = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithXAttrErrorHandler(h XAttrErrorHandler) Opt {
|
||||||
|
return func(ci *CopyInfo) {
|
||||||
|
ci.XAttrErrorHandler = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllowXAttrErrors(ci *CopyInfo) {
|
||||||
|
h := func(string, string, string, error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
WithXAttrErrorHandler(h)(ci)
|
||||||
|
}
|
||||||
|
|
||||||
|
type copier struct {
|
||||||
|
chown *ChownOpt
|
||||||
|
utime *time.Time
|
||||||
|
mode *int
|
||||||
|
inodes map[uint64]string
|
||||||
|
xattrErrorHandler XAttrErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCopier(chown *ChownOpt, tm *time.Time, mode *int, xeh XAttrErrorHandler) *copier {
|
||||||
|
if xeh == nil {
|
||||||
|
xeh = func(dst, src, key string, err error) error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &copier{inodes: map[uint64]string{}, chown: chown, utime: tm, xattrErrorHandler: xeh, mode: mode}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dest is always clean
|
||||||
|
func (c *copier) copy(ctx context.Context, src, target string, overwriteTargetMetadata bool) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
fi, err := os.Lstat(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to stat %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
if err := ensureEmptyFileTarget(target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyFileInfo := true
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.IsDir():
|
||||||
|
if created, err := c.copyDirectory(ctx, src, target, fi, overwriteTargetMetadata); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !overwriteTargetMetadata {
|
||||||
|
copyFileInfo = created
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeType) == 0:
|
||||||
|
link, err := getLinkSource(target, fi, c.inodes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get hardlink")
|
||||||
|
}
|
||||||
|
if link != "" {
|
||||||
|
if err := os.Link(link, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create hard link")
|
||||||
|
}
|
||||||
|
} else if err := copyFile(src, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy files")
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
||||||
|
link, err := os.Readlink(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read link: %s", src)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(link, target); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create symlink: %s", target)
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
|
||||||
|
if err := copyDevice(target, fi); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create device")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Support pipes and sockets
|
||||||
|
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if copyFileInfo {
|
||||||
|
if err := c.copyFileInfo(fi, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy file info")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyXAttrs(target, src, c.xattrErrorHandler); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy xattrs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) copyDirectory(ctx context.Context, src, dst string, stat os.FileInfo, overwriteTargetMetadata bool) (bool, error) {
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return false, errors.Errorf("source is not directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
created := false
|
||||||
|
|
||||||
|
if st, err := os.Lstat(dst); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
created = true
|
||||||
|
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
||||||
|
return created, errors.Wrapf(err, "failed to mkdir %s", dst)
|
||||||
|
}
|
||||||
|
} else if !st.IsDir() {
|
||||||
|
return false, errors.Errorf("cannot copy to non-directory: %s", dst)
|
||||||
|
} else if overwriteTargetMetadata {
|
||||||
|
if err := os.Chmod(dst, stat.Mode()); err != nil {
|
||||||
|
return false, errors.Wrapf(err, "failed to chmod on %s", dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fis, err := ioutil.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrapf(err, "failed to read %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
if err := c.copy(ctx, filepath.Join(src, fi.Name()), filepath.Join(dst, fi.Name()), true); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return created, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureEmptyFileTarget(dst string) error {
|
||||||
|
fi, err := os.Lstat(dst)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "failed to lstat file target")
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
return errors.Errorf("cannot replace to directory %s with file", dst)
|
||||||
|
}
|
||||||
|
return os.Remove(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(source, target string) error {
|
||||||
|
src, err := os.Open(source)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open source %s", source)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
tgt, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open target %s", target)
|
||||||
|
}
|
||||||
|
defer tgt.Close()
|
||||||
|
|
||||||
|
return copyFileContent(tgt, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsWildcards(name string) bool {
|
||||||
|
isWindows := runtime.GOOS == "windows"
|
||||||
|
for i := 0; i < len(name); i++ {
|
||||||
|
ch := name[i]
|
||||||
|
if ch == '\\' && !isWindows {
|
||||||
|
i++
|
||||||
|
} else if ch == '*' || ch == '?' || ch == '[' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitWildcards(p string) (d1, d2 string) {
|
||||||
|
parts := strings.Split(filepath.Join(p), string(filepath.Separator))
|
||||||
|
var p1, p2 []string
|
||||||
|
var found bool
|
||||||
|
for _, p := range parts {
|
||||||
|
if !found && containsWildcards(p) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = "/"
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
p1 = append(p1, p)
|
||||||
|
} else {
|
||||||
|
p2 = append(p2, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filepath.Join(p1...), filepath.Join(p2...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveWildcards(basePath, comp string) ([]string, error) {
|
||||||
|
var out []string
|
||||||
|
err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rel, err := rel(basePath, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rel == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if match, _ := filepath.Match(comp, rel); !match {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out = append(out, path)
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rel makes a path relative to base path. Same as `filepath.Rel` but can also
|
||||||
|
// handle UUID paths in windows.
|
||||||
|
func rel(basepath, targpath string) (string, error) {
|
||||||
|
// filepath.Rel can't handle UUID paths in windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
pfx := basepath + `\`
|
||||||
|
if strings.HasPrefix(targpath, pfx) {
|
||||||
|
p := strings.TrimPrefix(targpath, pfx)
|
||||||
|
if p == "" {
|
||||||
|
p = "."
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filepath.Rel(basepath, targpath)
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/sys"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUidGid(fi os.FileInfo) (uid, gid int) {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
return int(st.Uid), int(st.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
|
chown := c.chown
|
||||||
|
if chown == nil {
|
||||||
|
uid, gid := getUidGid(fi)
|
||||||
|
chown = &ChownOpt{Uid: uid, Gid: gid}
|
||||||
|
}
|
||||||
|
if err := Chown(name, chown); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chown %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := fi.Mode()
|
||||||
|
if c.mode != nil {
|
||||||
|
m = (m & ^os.FileMode(0777)) | os.FileMode(*c.mode&0777)
|
||||||
|
}
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||||
|
if err := os.Chmod(name, m); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.utime != nil {
|
||||||
|
if err := Utimes(name, c.utime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timespec := []unix.Timespec{unix.Timespec(sys.StatAtime(st)), unix.Timespec(sys.StatMtime(st))}
|
||||||
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
st, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to stat source")
|
||||||
|
}
|
||||||
|
|
||||||
|
var written int64
|
||||||
|
size := st.Size()
|
||||||
|
first := true
|
||||||
|
|
||||||
|
for written < size {
|
||||||
|
var desired int
|
||||||
|
if size-written > math.MaxInt32 {
|
||||||
|
desired = int(math.MaxInt32)
|
||||||
|
} else {
|
||||||
|
desired = int(size - written)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, desired, 0)
|
||||||
|
if err != nil {
|
||||||
|
if (err != unix.ENOSYS && err != unix.EXDEV && err != unix.EPERM) || !first {
|
||||||
|
return errors.Wrap(err, "copy file range failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err = io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
return errors.Wrap(err, "userspace copy failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
first = false
|
||||||
|
written += int64(n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unsupported stat type")
|
||||||
|
}
|
||||||
|
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// copyXAttrs requires xeh to be non-nil
|
||||||
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
|
if err != nil {
|
||||||
|
return xeh(dst, src, "", errors.Wrapf(err, "failed to list xattrs on %s", src))
|
||||||
|
}
|
||||||
|
for _, xattr := range xattrKeys {
|
||||||
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
|
if err != nil {
|
||||||
|
return xeh(dst, src, xattr, errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src))
|
||||||
|
}
|
||||||
|
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||||
|
return xeh(dst, src, xattr, errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
// +build solaris darwin freebsd
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/sys"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUidGid(fi os.FileInfo) (uid, gid int) {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
return int(st.Uid), int(st.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
chown := c.chown
|
||||||
|
if chown == nil {
|
||||||
|
uid, gid := getUidGid(fi)
|
||||||
|
chown = &ChownOpt{Uid: uid, Gid: gid}
|
||||||
|
}
|
||||||
|
if err := Chown(name, chown); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chown %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := fi.Mode()
|
||||||
|
if c.mode != nil {
|
||||||
|
m = (m & ^os.FileMode(0777)) | os.FileMode(*c.mode&0777)
|
||||||
|
}
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||||
|
if err := os.Chmod(name, m); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.utime != nil {
|
||||||
|
if err := Utimes(name, c.utime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timespec := []unix.Timespec{unix.Timespec(sys.StatAtime(st)), unix.Timespec(sys.StatMtime(st))}
|
||||||
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err := io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unsupported stat type")
|
||||||
|
}
|
||||||
|
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *copier) copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: copy windows specific metadata
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err := io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
return errors.New("device copy not supported")
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// GetLinkInfo returns an identifier representing the node a hardlink is pointing
|
||||||
|
// to. If the file is not hard linked then 0 will be returned.
|
||||||
|
func GetLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||||
|
return getLinkInfo(fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLinkSource returns a path for the given name and
|
||||||
|
// file info to its link source in the provided inode
|
||||||
|
// map. If the given file name is not in the map and
|
||||||
|
// has other links, it is added to the inode map
|
||||||
|
// to be a source for other link locations.
|
||||||
|
func getLinkSource(name string, fi os.FileInfo, inodes map[uint64]string) (string, error) {
|
||||||
|
inode, isHardlink := getLinkInfo(fi)
|
||||||
|
if !isHardlink {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, ok := inodes[inode]
|
||||||
|
if !ok {
|
||||||
|
inodes[inode] = name
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||||
|
s, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Chown(p string, user *ChownOpt) error {
|
||||||
|
if user != nil {
|
||||||
|
if err := os.Lchown(p, user.Uid, user.Gid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll is forked os.MkdirAll
|
||||||
|
func MkdirAll(path string, perm os.FileMode, user *ChownOpt, tm *time.Time) error {
|
||||||
|
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||||
|
dir, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
if dir.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||||
|
i := len(path)
|
||||||
|
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
|
||||||
|
j := i
|
||||||
|
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
|
||||||
|
if j > 1 {
|
||||||
|
// Create parent.
|
||||||
|
err = MkdirAll(fixRootDirectory(path[:j-1]), perm, user, tm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err1 := os.Lstat(path)
|
||||||
|
if err1 == nil && dir.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent now exists; invoke Mkdir and use its result.
|
||||||
|
err = os.Mkdir(path, perm)
|
||||||
|
if err != nil {
|
||||||
|
// Handle arguments like "foo/." by
|
||||||
|
// double-checking that directory doesn't exist.
|
||||||
|
dir, err1 := os.Lstat(path)
|
||||||
|
if err1 == nil && dir.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Chown(path, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Utimes(path, tm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fixRootDirectory(p string) string {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func Utimes(p string, tm *time.Time) error {
|
||||||
|
if tm == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := unix.TimeToTimespec(*tm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timespec := []unix.Timespec{ts, ts}
|
||||||
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fixRootDirectory(p string) string {
|
||||||
|
if len(p) == len(`\\?\c:`) {
|
||||||
|
if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' {
|
||||||
|
return p + `\`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func Utimes(p string, tm *time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ type DiskWriterOpt struct {
|
||||||
Filter FilterFunc
|
Filter FilterFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterFunc func(*types.Stat) bool
|
type FilterFunc func(string, *types.Stat) bool
|
||||||
|
|
||||||
type DiskWriter struct {
|
type DiskWriter struct {
|
||||||
opt DiskWriterOpt
|
opt DiskWriterOpt
|
||||||
|
@ -84,6 +84,12 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
|
||||||
destPath := filepath.Join(dw.dest, filepath.FromSlash(p))
|
destPath := filepath.Join(dw.dest, filepath.FromSlash(p))
|
||||||
|
|
||||||
if kind == ChangeKindDelete {
|
if kind == ChangeKindDelete {
|
||||||
|
if dw.filter != nil {
|
||||||
|
var empty types.Stat
|
||||||
|
if ok := dw.filter(p, &empty); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
// todo: no need to validate if diff is trusted but is it always?
|
// todo: no need to validate if diff is trusted but is it always?
|
||||||
if err := os.RemoveAll(destPath); err != nil {
|
if err := os.RemoveAll(destPath); err != nil {
|
||||||
return errors.Wrapf(err, "failed to remove: %s", destPath)
|
return errors.Wrapf(err, "failed to remove: %s", destPath)
|
||||||
|
@ -104,7 +110,7 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
|
||||||
statCopy := *stat
|
statCopy := *stat
|
||||||
|
|
||||||
if dw.filter != nil {
|
if dw.filter != nil {
|
||||||
if ok := dw.filter(&statCopy); !ok {
|
if ok := dw.filter(p, &statCopy); !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,17 +46,21 @@ func setUnixOpt(fi os.FileInfo, stat *types.Stat, path string, seenFiles map[uin
|
||||||
}
|
}
|
||||||
|
|
||||||
ino := s.Ino
|
ino := s.Ino
|
||||||
|
linked := false
|
||||||
if seenFiles != nil {
|
if seenFiles != nil {
|
||||||
if s.Nlink > 1 {
|
if s.Nlink > 1 {
|
||||||
if oldpath, ok := seenFiles[ino]; ok {
|
if oldpath, ok := seenFiles[ino]; ok {
|
||||||
stat.Linkname = oldpath
|
stat.Linkname = oldpath
|
||||||
stat.Size_ = 0
|
stat.Size_ = 0
|
||||||
|
linked = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !linked {
|
||||||
seenFiles[ino] = path
|
seenFiles[ino] = path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func major(device uint64) uint64 {
|
func major(device uint64) uint64 {
|
||||||
return (device >> 8) & 0xfff
|
return (device >> 8) & 0xfff
|
||||||
|
|
|
@ -19,7 +19,7 @@ type WalkOpt struct {
|
||||||
// FollowPaths contains symlinks that are resolved into include patterns
|
// FollowPaths contains symlinks that are resolved into include patterns
|
||||||
// before performing the fs walk
|
// before performing the fs walk
|
||||||
FollowPaths []string
|
FollowPaths []string
|
||||||
Map func(*types.Stat) bool
|
Map FilterFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) error {
|
func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) error {
|
||||||
|
@ -157,7 +157,7 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
default:
|
default:
|
||||||
if opt != nil && opt.Map != nil {
|
if opt != nil && opt.Map != nil {
|
||||||
if allowed := opt.Map(stat); !allowed {
|
if allowed := opt.Map(stat.Path, stat); !allowed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue