mirror of
https://github.com/moby/buildkit.git
synced 2026-08-08 08:40:45 +00:00
fileop: add dockerfile support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
_ "crypto/sha256"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
@@ -29,11 +31,12 @@ import (
|
||||
// filestate = state.File(c)
|
||||
// filestate.GetOutput(id).Exec()
|
||||
|
||||
func NewFileOp(s State, action *FileAction) *FileOp {
|
||||
func NewFileOp(s State, action *FileAction, c Constraints) *FileOp {
|
||||
action = action.bind(s)
|
||||
|
||||
f := &FileOp{
|
||||
action: action,
|
||||
action: action,
|
||||
constraints: c,
|
||||
}
|
||||
|
||||
f.output = &output{vertex: f, getIndex: func() (pb.OutputIndex, error) {
|
||||
@@ -190,9 +193,40 @@ func (mi *MkdirInfo) SetMkdirOption(mi2 *MkdirInfo) {
|
||||
}
|
||||
|
||||
func WithUser(name string) ChownOption {
|
||||
return ChownOpt{
|
||||
User: &UserOpt{Name: name},
|
||||
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 {
|
||||
@@ -479,7 +513,7 @@ type FileOp struct {
|
||||
action *FileAction
|
||||
output Output
|
||||
|
||||
// constraints Constraints
|
||||
constraints Constraints
|
||||
isValidated bool
|
||||
}
|
||||
|
||||
@@ -610,7 +644,7 @@ func (f *FileOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata,
|
||||
|
||||
pfo := &pb.FileOp{}
|
||||
|
||||
pop, md := MarshalConstraints(c, &Constraints{})
|
||||
pop, md := MarshalConstraints(c, &f.constraints)
|
||||
pop.Op = &pb.Op_File{
|
||||
File: pfo,
|
||||
}
|
||||
|
||||
@@ -229,8 +229,13 @@ func (s State) Run(ro ...RunOption) ExecState {
|
||||
}
|
||||
}
|
||||
|
||||
func (s State) File(a *FileAction) State {
|
||||
return s.WithOutput(NewFileOp(s, a).Output())
|
||||
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 {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/client/llb/imagemetaresolver"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
)
|
||||
|
||||
@@ -26,9 +27,12 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
caps := pb.Caps.CapSet(pb.Caps.All())
|
||||
|
||||
state, img, err := dockerfile2llb.Dockerfile2LLB(appcontext.Context(), df, dockerfile2llb.ConvertOpt{
|
||||
MetaResolver: imagemetaresolver.Default(),
|
||||
Target: opt.target,
|
||||
LLBCaps: &caps,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("err: %+v", err)
|
||||
|
||||
@@ -151,6 +151,10 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
|
||||
switch cmd.(type) {
|
||||
case *instructions.AddCommand, *instructions.CopyCommand, *instructions.RunCommand:
|
||||
total++
|
||||
case *instructions.WorkdirCommand:
|
||||
if useFileOp(opt.BuildArgs, opt.LLBCaps) {
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -468,7 +472,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
|
||||
case *instructions.RunCommand:
|
||||
err = dispatchRun(d, c, opt.proxyEnv, cmd.sources, opt)
|
||||
case *instructions.WorkdirCommand:
|
||||
err = dispatchWorkdir(d, c, true)
|
||||
err = dispatchWorkdir(d, c, true, &opt)
|
||||
case *instructions.AddCommand:
|
||||
err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "", opt)
|
||||
if err == nil {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
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)
|
||||
wd := c.Path
|
||||
if !path.IsAbs(c.Path) {
|
||||
@@ -656,13 +660,114 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
|
||||
}
|
||||
d.image.Config.WorkingDir = wd
|
||||
if commit {
|
||||
if 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)))
|
||||
}
|
||||
|
||||
return commitToHistory(&d.image, "WORKDIR "+wd, false, 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 {
|
||||
// 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"))
|
||||
|
||||
dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest()))
|
||||
@@ -1176,3 +1281,13 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform
|
||||
out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
|
||||
return out + str
|
||||
}
|
||||
|
||||
func useFileOp(args map[string]string, caps *apicaps.CapSet) bool {
|
||||
enabled := fileOpEnabled
|
||||
if v, ok := args["BUILDKIT_USE_FILEOP"]; ok {
|
||||
if b, err := strconv.ParseBool(v); err != nil {
|
||||
enabled = b
|
||||
}
|
||||
}
|
||||
return enabled && caps != nil && caps.Supports(pb.CapFileBase) == nil
|
||||
}
|
||||
|
||||
5
frontend/dockerfile/dockerfile2llb/convert_fileop.go
Normal file
5
frontend/dockerfile/dockerfile2llb/convert_fileop.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build fileop
|
||||
|
||||
package dockerfile2llb
|
||||
|
||||
const fileOpEnabled = true
|
||||
5
frontend/dockerfile/dockerfile2llb/convert_nofileop.go
Normal file
5
frontend/dockerfile/dockerfile2llb/convert_nofileop.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build !fileop
|
||||
|
||||
package dockerfile2llb
|
||||
|
||||
const fileOpEnabled = false
|
||||
@@ -46,15 +46,23 @@ func NewFileOp(v solver.Vertex, op *pb.Op_File, cm cache.Manager, md *metadata.S
|
||||
|
||||
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 {
|
||||
@@ -62,7 +70,7 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo
|
||||
}
|
||||
case *pb.FileAction_Mkfile:
|
||||
p := *a.Mkfile
|
||||
p.Owner = nil
|
||||
markInvalid(action.Input)
|
||||
processOwner(p.Owner, selectors)
|
||||
dt, err = json.Marshal(p)
|
||||
if err != nil {
|
||||
@@ -70,14 +78,15 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo
|
||||
}
|
||||
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)
|
||||
p.Owner = nil
|
||||
if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs {
|
||||
p.Src = path.Base(p.Src)
|
||||
addSelector(selectors, int(action.SecondaryInput), p.Src, p.AllowWildcard, p.FollowSymlink)
|
||||
@@ -111,6 +120,9 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
@@ -43,6 +43,8 @@ const (
|
||||
CapExecMountSSH apicaps.CapID = "exec.mount.ssh"
|
||||
CapExecCgroupsMounted apicaps.CapID = "exec.cgroup"
|
||||
|
||||
CapFileBase apicaps.CapID = "file.base"
|
||||
|
||||
CapConstraints apicaps.CapID = "constraints"
|
||||
CapPlatform apicaps.CapID = "platform"
|
||||
|
||||
@@ -226,6 +228,12 @@ func init() {
|
||||
Status: apicaps.CapStatusExperimental,
|
||||
})
|
||||
|
||||
Caps.Init(apicaps.Cap{
|
||||
ID: CapFileBase,
|
||||
Enabled: true,
|
||||
Status: apicaps.CapStatusPrerelease,
|
||||
})
|
||||
|
||||
Caps.Init(apicaps.Cap{
|
||||
ID: CapConstraints,
|
||||
Enabled: true,
|
||||
|
||||
Reference in New Issue
Block a user