2017-07-11 17:12:12 +00:00
package local
import (
2018-01-16 22:30:10 +00:00
"context"
2017-08-25 20:08:18 +00:00
"encoding/json"
2017-07-25 19:11:52 +00:00
"fmt"
2017-07-11 17:12:12 +00:00
"time"
2019-03-20 21:54:20 +00:00
"github.com/docker/docker/pkg/idtools"
2017-07-11 17:12:12 +00:00
"github.com/moby/buildkit/cache"
2017-07-31 22:06:59 +00:00
"github.com/moby/buildkit/cache/contenthash"
2017-07-14 18:59:31 +00:00
"github.com/moby/buildkit/cache/metadata"
2018-07-26 19:07:52 +00:00
"github.com/moby/buildkit/client"
2017-07-11 17:12:12 +00:00
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/filesync"
"github.com/moby/buildkit/snapshot"
2020-05-28 20:46:33 +00:00
"github.com/moby/buildkit/solver"
2017-07-11 17:12:12 +00:00
"github.com/moby/buildkit/source"
2017-07-21 00:27:59 +00:00
"github.com/moby/buildkit/util/progress"
2017-08-25 20:08:18 +00:00
digest "github.com/opencontainers/go-digest"
2017-07-11 17:12:12 +00:00
"github.com/pkg/errors"
2017-07-19 01:05:19 +00:00
"github.com/sirupsen/logrus"
2017-07-31 22:06:59 +00:00
"github.com/tonistiigi/fsutil"
2019-03-20 21:54:20 +00:00
fstypes "github.com/tonistiigi/fsutil/types"
2018-09-18 18:18:08 +00:00
bolt "go.etcd.io/bbolt"
2017-07-21 00:27:59 +00:00
"golang.org/x/time/rate"
2018-07-21 18:49:30 +00:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2017-07-11 17:12:12 +00:00
)
2017-07-14 18:59:31 +00:00
const keySharedKey = "local.sharedKey"
2017-07-11 17:12:12 +00:00
type Opt struct {
2019-02-23 12:56:04 +00:00
CacheAccessor cache . Accessor
MetadataStore * metadata . Store
2017-07-11 17:12:12 +00:00
}
func NewSource ( opt Opt ) ( source . Source , error ) {
ls := & localSource {
cm : opt . CacheAccessor ,
2017-07-14 18:59:31 +00:00
md : opt . MetadataStore ,
2017-07-11 17:12:12 +00:00
}
return ls , nil
}
type localSource struct {
cm cache . Accessor
2017-07-14 18:59:31 +00:00
md * metadata . Store
2017-07-11 17:12:12 +00:00
}
func ( ls * localSource ) ID ( ) string {
return source . LocalScheme
}
2020-05-28 20:46:33 +00:00
func ( ls * localSource ) Resolve ( ctx context . Context , id source . Identifier , sm * session . Manager , _ solver . Vertex ) ( source . SourceInstance , error ) {
2017-07-11 17:12:12 +00:00
localIdentifier , ok := id . ( * source . LocalIdentifier )
if ! ok {
return nil , errors . Errorf ( "invalid local identifier %v" , id )
}
return & localSourceHandler {
src : * localIdentifier ,
2019-02-23 12:56:04 +00:00
sm : sm ,
2017-07-11 17:12:12 +00:00
localSource : ls ,
} , nil
}
type localSourceHandler struct {
src source . LocalIdentifier
2019-02-23 12:56:04 +00:00
sm * session . Manager
2017-07-11 17:12:12 +00:00
* localSource
}
2020-05-28 20:46:33 +00:00
func ( ls * localSourceHandler ) CacheKey ( ctx context . Context , g session . Group , index int ) ( string , solver . CacheOpts , bool , error ) {
2017-07-11 17:12:12 +00:00
sessionID := ls . src . SessionID
if sessionID == "" {
2020-06-30 01:06:02 +00:00
id := g . SessionIterator ( ) . NextSession ( )
2017-07-14 18:05:54 +00:00
if id == "" {
2020-05-28 20:46:33 +00:00
return "" , nil , false , errors . New ( "could not access local files without session" )
2017-07-11 17:12:12 +00:00
}
2017-07-14 18:05:54 +00:00
sessionID = id
2017-07-11 17:12:12 +00:00
}
2017-08-25 20:08:18 +00:00
dt , err := json . Marshal ( struct {
SessionID string
IncludePatterns [ ] string
2017-12-14 05:01:01 +00:00
ExcludePatterns [ ] string
2018-07-17 19:27:57 +00:00
FollowPaths [ ] string
} { SessionID : sessionID , IncludePatterns : ls . src . IncludePatterns , ExcludePatterns : ls . src . ExcludePatterns , FollowPaths : ls . src . FollowPaths } )
2017-08-25 20:08:18 +00:00
if err != nil {
2020-05-28 20:46:33 +00:00
return "" , nil , false , err
2017-08-25 20:08:18 +00:00
}
2020-05-28 20:46:33 +00:00
return "session:" + ls . src . Name + ":" + digest . FromBytes ( dt ) . String ( ) , nil , true , nil
2017-07-11 17:12:12 +00:00
}
2020-06-30 01:06:02 +00:00
func ( ls * localSourceHandler ) Snapshot ( ctx context . Context , g session . Group ) ( cache . ImmutableRef , error ) {
var ref cache . ImmutableRef
err := ls . sm . Any ( ctx , g , func ( ctx context . Context , _ string , c session . Caller ) error {
r , err := ls . snapshot ( ctx , c )
if err != nil {
return err
}
ref = r
return nil
} )
2017-07-11 17:12:12 +00:00
if err != nil {
return nil , err
}
2020-06-30 01:06:02 +00:00
return ref , nil
}
2017-07-11 17:12:12 +00:00
2020-06-30 01:06:02 +00:00
func ( ls * localSourceHandler ) snapshot ( ctx context . Context , caller session . Caller ) ( out cache . ImmutableRef , retErr error ) {
2017-12-14 05:01:01 +00:00
sharedKey := keySharedKey + ":" + ls . src . Name + ":" + ls . src . SharedKeyHint + ":" + caller . SharedKey ( ) // TODO: replace caller.SharedKey() with source based hint from client(absolute-path+nodeid)
2017-07-14 18:59:31 +00:00
var mutable cache . MutableRef
sis , err := ls . md . Search ( sharedKey )
2017-07-11 17:12:12 +00:00
if err != nil {
return nil , err
}
2017-07-14 18:59:31 +00:00
for _ , si := range sis {
if m , err := ls . cm . GetMutable ( ctx , si . ID ( ) ) ; err == nil {
logrus . Debugf ( "reusing ref for local: %s" , m . ID ( ) )
mutable = m
break
}
}
if mutable == nil {
2018-07-26 19:07:52 +00:00
m , err := ls . cm . New ( ctx , nil , cache . CachePolicyRetain , cache . WithRecordType ( client . UsageRecordTypeLocalSource ) , cache . WithDescription ( fmt . Sprintf ( "local source for %s" , ls . src . Name ) ) )
2017-07-14 18:59:31 +00:00
if err != nil {
return nil , err
}
mutable = m
logrus . Debugf ( "new ref for local: %s" , mutable . ID ( ) )
}
2017-07-11 17:12:12 +00:00
defer func ( ) {
if retErr != nil && mutable != nil {
2019-05-28 20:55:00 +00:00
// on error remove the record as checksum update is in undefined state
cache . CachePolicyDefault ( mutable )
if err := mutable . Metadata ( ) . Commit ( ) ; err != nil {
logrus . Errorf ( "failed to reset mutable cachepolicy: %v" , err )
}
contenthash . ClearCacheContext ( mutable . Metadata ( ) )
2017-07-14 18:59:31 +00:00
go mutable . Release ( context . TODO ( ) )
2017-07-11 17:12:12 +00:00
}
} ( )
2017-07-18 06:08:22 +00:00
mount , err := mutable . Mount ( ctx , false )
2017-07-11 17:12:12 +00:00
if err != nil {
return nil , err
}
lm := snapshot . LocalMounter ( mount )
dest , err := lm . Mount ( )
if err != nil {
return nil , err
}
defer func ( ) {
if retErr != nil && lm != nil {
lm . Unmount ( )
}
} ( )
2019-03-20 21:54:20 +00:00
cc , err := contenthash . GetCacheContext ( ctx , mutable . Metadata ( ) , mount . IdentityMapping ( ) )
2017-07-31 22:06:59 +00:00
if err != nil {
return nil , err
}
2017-07-11 17:12:12 +00:00
opt := filesync . FSSendRequestOpt {
2017-07-20 22:13:00 +00:00
Name : ls . src . Name ,
2017-08-25 20:08:18 +00:00
IncludePatterns : ls . src . IncludePatterns ,
2017-12-14 02:49:14 +00:00
ExcludePatterns : ls . src . ExcludePatterns ,
2018-06-04 21:08:29 +00:00
FollowPaths : ls . src . FollowPaths ,
2017-07-11 17:12:12 +00:00
OverrideExcludes : false ,
DestDir : dest ,
2019-03-20 21:54:20 +00:00
CacheUpdater : & cacheUpdater { cc , mount . IdentityMapping ( ) } ,
2017-07-21 00:27:59 +00:00
ProgressCb : newProgressHandler ( ctx , "transferring " + ls . src . Name + ":" ) ,
2017-07-11 17:12:12 +00:00
}
2019-03-20 21:54:20 +00:00
if idmap := mount . IdentityMapping ( ) ; idmap != nil {
2019-03-21 01:16:43 +00:00
opt . Filter = func ( p string , stat * fstypes . Stat ) bool {
identity , err := idmap . ToHost ( idtools . Identity {
2019-03-20 21:54:20 +00:00
UID : int ( stat . Uid ) ,
GID : int ( stat . Gid ) ,
} )
if err != nil {
return false
}
2019-03-21 01:16:43 +00:00
stat . Uid = uint32 ( identity . UID )
stat . Gid = uint32 ( identity . GID )
2019-03-20 21:54:20 +00:00
return true
}
}
2017-07-11 17:12:12 +00:00
if err := filesync . FSSync ( ctx , caller , opt ) ; err != nil {
2018-07-21 18:49:30 +00:00
if status . Code ( err ) == codes . NotFound {
return nil , errors . Errorf ( "local source %s not enabled from the client" , ls . src . Name )
}
2017-07-11 17:12:12 +00:00
return nil , err
}
if err := lm . Unmount ( ) ; err != nil {
return nil , err
}
lm = nil
2017-07-31 22:06:59 +00:00
if err := contenthash . SetCacheContext ( ctx , mutable . Metadata ( ) , cc ) ; err != nil {
return nil , err
}
2017-07-21 00:27:59 +00:00
// skip storing snapshot by the shared key if it already exists
2017-07-14 18:59:31 +00:00
skipStoreSharedKey := false
si , _ := ls . md . Get ( mutable . ID ( ) )
if v := si . Get ( keySharedKey ) ; v != nil {
var str string
if err := v . Unmarshal ( & str ) ; err != nil {
return nil , err
}
skipStoreSharedKey = str == sharedKey
}
if ! skipStoreSharedKey {
v , err := metadata . NewValue ( sharedKey )
if err != nil {
return nil , err
}
v . Index = sharedKey
if err := si . Update ( func ( b * bolt . Bucket ) error {
return si . SetValue ( b , sharedKey , v )
} ) ; err != nil {
return nil , err
}
logrus . Debugf ( "saved %s as %s" , mutable . ID ( ) , sharedKey )
}
snap , err := mutable . Commit ( ctx )
2017-07-11 17:12:12 +00:00
if err != nil {
return nil , err
}
2017-07-31 22:06:59 +00:00
2017-07-21 00:27:59 +00:00
mutable = nil // avoid deferred cleanup
2017-07-11 17:12:12 +00:00
2017-07-14 18:59:31 +00:00
return snap , nil
2017-07-11 17:12:12 +00:00
}
2017-07-21 00:27:59 +00:00
func newProgressHandler ( ctx context . Context , id string ) func ( int , bool ) {
limiter := rate . NewLimiter ( rate . Every ( 100 * time . Millisecond ) , 1 )
pw , _ , _ := progress . FromContext ( ctx )
now := time . Now ( )
st := progress . Status {
Started : & now ,
Action : "transferring" ,
}
pw . Write ( id , st )
return func ( s int , last bool ) {
if last || limiter . Allow ( ) {
st . Current = s
if last {
now := time . Now ( )
st . Completed = & now
}
pw . Write ( id , st )
if last {
pw . Close ( )
}
}
}
}
2017-07-31 22:06:59 +00:00
type cacheUpdater struct {
contenthash . CacheContext
2019-03-20 21:54:20 +00:00
idmap * idtools . IdentityMapping
2017-07-31 22:06:59 +00:00
}
func ( cu * cacheUpdater ) MarkSupported ( bool ) {
}
func ( cu * cacheUpdater ) ContentHasher ( ) fsutil . ContentHasher {
return contenthash . NewFromStat
}