2017-07-02 19:16:09 +00:00
package solver
import (
2017-07-06 20:15:54 +00:00
"encoding/json"
2017-07-25 19:11:52 +00:00
"fmt"
2017-08-10 01:19:40 +00:00
"path"
2017-07-11 02:07:47 +00:00
"sort"
2017-07-25 19:11:52 +00:00
"strings"
2017-07-02 19:16:09 +00:00
"github.com/moby/buildkit/cache"
"github.com/moby/buildkit/solver/pb"
2017-07-07 21:35:10 +00:00
"github.com/moby/buildkit/util/progress/logs"
2017-07-02 19:16:09 +00:00
"github.com/moby/buildkit/worker"
2017-07-06 20:15:54 +00:00
digest "github.com/opencontainers/go-digest"
2017-07-02 19:16:09 +00:00
"github.com/pkg/errors"
2017-07-06 04:25:51 +00:00
"golang.org/x/net/context"
2017-07-02 19:16:09 +00:00
)
2017-07-31 22:06:59 +00:00
const execCacheType = "buildkit.exec.v0"
2017-07-06 04:25:51 +00:00
type execOp struct {
2017-10-06 00:34:45 +00:00
op * pb . ExecOp
cm cache . Manager
w worker . Worker
numInputs int
2017-07-06 04:25:51 +00:00
}
2017-10-06 00:34:45 +00:00
func newExecOp ( v Vertex , op * pb . Op_Exec , cm cache . Manager , w worker . Worker ) ( Op , error ) {
2017-07-06 04:25:51 +00:00
return & execOp {
2017-10-06 00:34:45 +00:00
op : op . Exec ,
cm : cm ,
w : w ,
numInputs : len ( v . Inputs ( ) ) ,
2017-07-06 04:25:51 +00:00
} , nil
}
2017-07-31 22:06:59 +00:00
func ( e * execOp ) CacheKey ( ctx context . Context ) ( digest . Digest , error ) {
2017-07-06 20:15:54 +00:00
dt , err := json . Marshal ( struct {
2017-07-31 22:06:59 +00:00
Type string
Exec * pb . ExecOp
2017-07-06 20:15:54 +00:00
} {
2017-07-31 22:06:59 +00:00
Type : execCacheType ,
Exec : e . op ,
2017-07-06 20:15:54 +00:00
} )
if err != nil {
2017-07-31 22:06:59 +00:00
return "" , err
2017-07-14 18:59:31 +00:00
}
2017-07-31 22:06:59 +00:00
return digest . FromBytes ( dt ) , nil
2017-07-06 20:15:54 +00:00
}
2017-07-06 04:25:51 +00:00
func ( e * execOp ) Run ( ctx context . Context , inputs [ ] Reference ) ( [ ] Reference , error ) {
2017-07-11 02:07:47 +00:00
var mounts [ ] worker . Mount
2017-07-18 06:08:22 +00:00
var outputs [ ] Reference
2017-07-11 02:07:47 +00:00
var root cache . Mountable
2017-07-02 19:16:09 +00:00
defer func ( ) {
for _ , o := range outputs {
if o != nil {
2017-09-22 17:30:30 +00:00
go o . Release ( context . TODO ( ) )
2017-07-02 19:16:09 +00:00
}
}
} ( )
2017-07-06 20:15:54 +00:00
for _ , m := range e . op . Mounts {
2017-07-02 19:16:09 +00:00
var mountable cache . Mountable
2017-07-10 16:53:09 +00:00
var ref cache . ImmutableRef
2017-07-14 18:59:31 +00:00
if m . Input != pb . Empty {
2017-07-10 16:53:09 +00:00
if int ( m . Input ) > len ( inputs ) {
return nil , errors . Errorf ( "missing input %d" , m . Input )
}
inp := inputs [ int ( m . Input ) ]
var ok bool
2017-07-10 20:03:38 +00:00
ref , ok = toImmutableRef ( inp )
2017-07-10 16:53:09 +00:00
if ! ok {
return nil , errors . Errorf ( "invalid reference for exec %T" , inputs [ int ( m . Input ) ] )
}
mountable = ref
2017-07-06 04:25:51 +00:00
}
2017-07-14 18:59:31 +00:00
if m . Output != pb . SkipOutput {
2017-09-09 09:49:48 +00:00
if m . Readonly && ref != nil && m . Dest != pb . RootMount { // exclude read-only rootfs
2017-07-18 06:08:22 +00:00
outputs = append ( outputs , newSharedRef ( ref ) . Clone ( ) )
} else {
2017-07-25 19:11:52 +00:00
active , err := e . cm . New ( ctx , ref , cache . WithDescription ( fmt . Sprintf ( "mount %s from exec %s" , m . Dest , strings . Join ( e . op . Meta . Args , " " ) ) ) ) // TODO: should be method
2017-07-18 06:08:22 +00:00
if err != nil {
return nil , err
}
outputs = append ( outputs , active )
mountable = active
2017-07-02 19:16:09 +00:00
}
}
2017-07-11 04:54:38 +00:00
if m . Dest == pb . RootMount {
2017-07-11 02:07:47 +00:00
root = mountable
} else {
2017-08-08 01:45:31 +00:00
mounts = append ( mounts , worker . Mount { Src : mountable , Dest : m . Dest , Readonly : m . Readonly , Selector : m . Selector } )
2017-07-11 02:07:47 +00:00
}
2017-07-02 19:16:09 +00:00
}
2017-07-11 02:07:47 +00:00
sort . Slice ( mounts , func ( i , j int ) bool {
return mounts [ i ] . Dest < mounts [ j ] . Dest
} )
2017-07-02 19:16:09 +00:00
meta := worker . Meta {
2017-07-06 20:15:54 +00:00
Args : e . op . Meta . Args ,
Env : e . op . Meta . Env ,
Cwd : e . op . Meta . Cwd ,
2017-07-02 19:16:09 +00:00
}
2017-07-07 21:35:10 +00:00
stdout , stderr := logs . NewLogStreams ( ctx )
2017-07-02 19:16:09 +00:00
defer stdout . Close ( )
defer stderr . Close ( )
2017-10-01 00:58:07 +00:00
if err := e . w . Exec ( ctx , meta , root , mounts , nil , stdout , stderr ) ; err != nil {
2017-07-02 19:16:09 +00:00
return nil , errors . Wrapf ( err , "worker failed running %v" , meta . Args )
}
2017-07-06 04:25:51 +00:00
refs := [ ] Reference { }
2017-07-02 19:16:09 +00:00
for i , o := range outputs {
2017-07-18 06:08:22 +00:00
if mutable , ok := o . ( cache . MutableRef ) ; ok {
ref , err := mutable . Commit ( ctx )
if err != nil {
return nil , errors . Wrapf ( err , "error committing %s" , mutable . ID ( ) )
}
refs = append ( refs , ref )
} else {
refs = append ( refs , o )
2017-07-02 19:16:09 +00:00
}
outputs [ i ] = nil
}
return refs , nil
}
2017-07-31 22:06:59 +00:00
2017-10-06 00:34:45 +00:00
func ( e * execOp ) ContentMask ( ctx context . Context ) ( digest . Digest , [ ] [ ] string , error ) {
2017-07-31 22:06:59 +00:00
// contentKey for exec uses content based checksum for mounts and definition
// based checksum for root
2017-10-06 00:34:45 +00:00
skipped := make ( map [ int ] struct { } , 0 )
2017-08-10 01:20:33 +00:00
type src struct {
2017-10-06 00:34:45 +00:00
index int
2017-08-10 01:20:33 +00:00
selector string
}
2017-10-06 00:34:45 +00:00
srcsMap := make ( map [ src ] struct { } )
2017-07-31 22:06:59 +00:00
for _ , m := range e . op . Mounts {
if m . Input != pb . Empty {
2017-09-01 23:57:22 +00:00
if m . Dest != pb . RootMount && m . Readonly { // could also include rw if they don't have a selector, but not sure if helps performance
2017-10-06 00:34:45 +00:00
srcsMap [ src { int ( m . Input ) , path . Join ( "/" , m . Selector ) } ] = struct { } { }
2017-07-31 22:06:59 +00:00
} else {
2017-10-06 00:34:45 +00:00
skipped [ int ( m . Input ) ] = struct { } { }
2017-07-31 22:06:59 +00:00
}
}
}
2017-10-06 00:34:45 +00:00
if len ( srcsMap ) == 0 {
return "" , nil , nil
2017-07-31 22:06:59 +00:00
}
2017-10-06 00:34:45 +00:00
contentInputs := make ( [ ] [ ] string , e . numInputs )
for in := range srcsMap {
contentInputs [ in . index ] = append ( contentInputs [ in . index ] , in . selector )
2017-08-10 01:20:33 +00:00
}
2017-10-06 00:34:45 +00:00
// TODO: remove nested directories
2017-08-10 01:20:33 +00:00
2017-10-06 00:34:45 +00:00
for k := range contentInputs {
sort . Strings ( contentInputs [ k ] )
2017-07-31 22:06:59 +00:00
}
2017-10-06 00:34:45 +00:00
dt , err := json . Marshal ( struct {
Type string
Exec * pb . ExecOp
} {
Type : execCacheType ,
Exec : e . op ,
} )
if err != nil {
return "" , nil , err
2017-07-31 22:06:59 +00:00
}
2017-10-06 00:34:45 +00:00
return digest . FromBytes ( dt ) , contentInputs , nil
2017-07-31 22:06:59 +00:00
}