fileop: add dockerfile support

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2019-03-07 18:40:44 -08:00
parent 7210bf6806
commit 8a4674bab4
8 changed files with 202 additions and 14 deletions

View File

@@ -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,
}

View File

@@ -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 {

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
// +build fileop
package dockerfile2llb
const fileOpEnabled = true

View File

@@ -0,0 +1,5 @@
// +build !fileop
package dockerfile2llb
const fileOpEnabled = false

View File

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

View File

@@ -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,