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-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"
2017-07-31 22:06:59 +00:00
"github.com/moby/buildkit/cache/contenthash"
2017-07-02 19:16:09 +00:00
"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-31 22:06:59 +00:00
"golang.org/x/sync/errgroup"
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-07-06 20:15:54 +00:00
op * pb . ExecOp
2017-07-06 04:25:51 +00:00
cm cache . Manager
w worker . Worker
}
2017-07-21 17:58:24 +00:00
func newExecOp ( _ Vertex , op * pb . Op_Exec , cm cache . Manager , w worker . Worker ) ( Op , error ) {
2017-07-06 04:25:51 +00:00
return & execOp {
2017-07-06 20:15:54 +00:00
op : op . Exec ,
2017-07-06 04:25:51 +00:00
cm : cm ,
w : w ,
} , 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-07-14 18:59:31 +00:00
go o . Release ( ctx )
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-07-18 06:08:22 +00:00
if m . Readonly && ref != nil {
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-07-18 06:08:22 +00:00
mounts = append ( mounts , worker . Mount { Src : mountable , Dest : m . Dest , Readonly : m . Readonly } )
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-07-11 02:07:47 +00:00
if err := e . w . Exec ( ctx , meta , root , mounts , 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
func ( e * execOp ) ContentKeys ( ctx context . Context , inputs [ ] [ ] digest . Digest , refs [ ] Reference ) ( [ ] digest . Digest , error ) {
if len ( refs ) == 0 {
return nil , nil
}
// contentKey for exec uses content based checksum for mounts and definition
// based checksum for root
rootIndex := - 1
skip := true
srcs := make ( [ ] string , len ( refs ) )
for _ , m := range e . op . Mounts {
if m . Input != p b . Empty {
if m . Dest != pb . RootMount {
srcs [ int ( m . Input ) ] = "/" // TODO: selector
skip = false
} else {
rootIndex = int ( m . Input )
}
}
}
if skip {
return nil , nil
}
dgsts := make ( [ ] digest . Digest , len ( refs ) )
eg , ctx := errgroup . WithContext ( ctx )
for i , ref := range refs {
if srcs [ i ] == "" {
continue
}
func ( i int , ref Reference ) {
eg . Go ( func ( ) error {
ref , ok := toImmutableRef ( ref )
if ! ok {
return errors . Errorf ( "invalid reference" )
}
dgst , err := contenthash . Checksum ( ctx , ref , srcs [ i ] )
if err != nil {
return err
}
dgsts [ i ] = dgst
return nil
} )
} ( i , ref )
}
if err := eg . Wait ( ) ; err != nil {
return nil , err
}
var out [ ] digest . Digest
for _ , cacheKeys := range inputs {
dt , err := json . Marshal ( struct {
Type string
Sources [ ] digest . Digest
Root digest . Digest
Exec * pb . ExecOp
} {
Type : execCacheType ,
Sources : dgsts ,
Root : cacheKeys [ rootIndex ] ,
Exec : e . op ,
} )
if err != nil {
return nil , err
}
out = append ( out , digest . FromBytes ( dt ) )
}
return out , nil
}