|
@@ -1,654 +0,0 @@
|
|
|
-package fscache // import "github.com/docker/docker/builder/fscache"
|
|
|
-
|
|
|
-import (
|
|
|
- "archive/tar"
|
|
|
- "context"
|
|
|
- "crypto/sha256"
|
|
|
- "encoding/json"
|
|
|
- "hash"
|
|
|
- "os"
|
|
|
- "path/filepath"
|
|
|
- "sort"
|
|
|
- "sync"
|
|
|
- "time"
|
|
|
-
|
|
|
- "github.com/docker/docker/builder"
|
|
|
- "github.com/docker/docker/builder/remotecontext"
|
|
|
- "github.com/docker/docker/pkg/archive"
|
|
|
- "github.com/docker/docker/pkg/directory"
|
|
|
- "github.com/docker/docker/pkg/stringid"
|
|
|
- "github.com/docker/docker/pkg/tarsum"
|
|
|
- "github.com/moby/buildkit/session/filesync"
|
|
|
- "github.com/pkg/errors"
|
|
|
- "github.com/sirupsen/logrus"
|
|
|
- "github.com/tonistiigi/fsutil"
|
|
|
- fsutiltypes "github.com/tonistiigi/fsutil/types"
|
|
|
- bolt "go.etcd.io/bbolt"
|
|
|
- "golang.org/x/sync/singleflight"
|
|
|
-)
|
|
|
-
|
|
|
-const dbFile = "fscache.db"
|
|
|
-const cacheKey = "cache"
|
|
|
-const metaKey = "meta"
|
|
|
-
|
|
|
-// Backend is a backing implementation for FSCache
|
|
|
-type Backend interface {
|
|
|
- Get(id string) (string, error)
|
|
|
- Remove(id string) error
|
|
|
-}
|
|
|
-
|
|
|
-// FSCache allows syncing remote resources to cached snapshots
|
|
|
-type FSCache struct {
|
|
|
- opt Opt
|
|
|
- transports map[string]Transport
|
|
|
- mu sync.Mutex
|
|
|
- g singleflight.Group
|
|
|
- store *fsCacheStore
|
|
|
-}
|
|
|
-
|
|
|
-// Opt defines options for initializing FSCache
|
|
|
-type Opt struct {
|
|
|
- Backend Backend
|
|
|
- Root string // for storing local metadata
|
|
|
- GCPolicy GCPolicy
|
|
|
-}
|
|
|
-
|
|
|
-// GCPolicy defines policy for garbage collection
|
|
|
-type GCPolicy struct {
|
|
|
- MaxSize uint64
|
|
|
- MaxKeepDuration time.Duration
|
|
|
-}
|
|
|
-
|
|
|
-// NewFSCache returns new FSCache object
|
|
|
-func NewFSCache(opt Opt) (*FSCache, error) {
|
|
|
- store, err := newFSCacheStore(opt)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- return &FSCache{
|
|
|
- store: store,
|
|
|
- opt: opt,
|
|
|
- transports: make(map[string]Transport),
|
|
|
- }, nil
|
|
|
-}
|
|
|
-
|
|
|
-// Transport defines a method for syncing remote data to FSCache
|
|
|
-type Transport interface {
|
|
|
- Copy(ctx context.Context, id RemoteIdentifier, dest string, cs filesync.CacheUpdater) error
|
|
|
-}
|
|
|
-
|
|
|
-// RemoteIdentifier identifies a transfer request
|
|
|
-type RemoteIdentifier interface {
|
|
|
- Key() string
|
|
|
- SharedKey() string
|
|
|
- Transport() string
|
|
|
-}
|
|
|
-
|
|
|
-// RegisterTransport registers a new transport method
|
|
|
-func (fsc *FSCache) RegisterTransport(id string, transport Transport) error {
|
|
|
- fsc.mu.Lock()
|
|
|
- defer fsc.mu.Unlock()
|
|
|
- if _, ok := fsc.transports[id]; ok {
|
|
|
- return errors.Errorf("transport %v already exists", id)
|
|
|
- }
|
|
|
- fsc.transports[id] = transport
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// SyncFrom returns a source based on a remote identifier
|
|
|
-func (fsc *FSCache) SyncFrom(ctx context.Context, id RemoteIdentifier) (builder.Source, error) { // cacheOpt
|
|
|
- trasportID := id.Transport()
|
|
|
- fsc.mu.Lock()
|
|
|
- transport, ok := fsc.transports[id.Transport()]
|
|
|
- if !ok {
|
|
|
- fsc.mu.Unlock()
|
|
|
- return nil, errors.Errorf("invalid transport %s", trasportID)
|
|
|
- }
|
|
|
-
|
|
|
- logrus.Debugf("SyncFrom %s %s", id.Key(), id.SharedKey())
|
|
|
- fsc.mu.Unlock()
|
|
|
- sourceRef, err, _ := fsc.g.Do(id.Key(), func() (interface{}, error) {
|
|
|
- var sourceRef *cachedSourceRef
|
|
|
- sourceRef, err := fsc.store.Get(id.Key())
|
|
|
- if err == nil {
|
|
|
- return sourceRef, nil
|
|
|
- }
|
|
|
-
|
|
|
- // check for unused shared cache
|
|
|
- sharedKey := id.SharedKey()
|
|
|
- if sharedKey != "" {
|
|
|
- r, err := fsc.store.Rebase(sharedKey, id.Key())
|
|
|
- if err == nil {
|
|
|
- sourceRef = r
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if sourceRef == nil {
|
|
|
- var err error
|
|
|
- sourceRef, err = fsc.store.New(id.Key(), sharedKey)
|
|
|
- if err != nil {
|
|
|
- return nil, errors.Wrap(err, "failed to create remote context")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if err := syncFrom(ctx, sourceRef, transport, id); err != nil {
|
|
|
- sourceRef.Release()
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- if err := sourceRef.resetSize(-1); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- return sourceRef, nil
|
|
|
- })
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- ref := sourceRef.(*cachedSourceRef)
|
|
|
- if ref.src == nil { // failsafe
|
|
|
- return nil, errors.Errorf("invalid empty pull")
|
|
|
- }
|
|
|
- wc := &wrappedContext{Source: ref.src, closer: func() error {
|
|
|
- ref.Release()
|
|
|
- return nil
|
|
|
- }}
|
|
|
- return wc, nil
|
|
|
-}
|
|
|
-
|
|
|
-// DiskUsage reports how much data is allocated by the cache
|
|
|
-func (fsc *FSCache) DiskUsage(ctx context.Context) (int64, error) {
|
|
|
- return fsc.store.DiskUsage(ctx)
|
|
|
-}
|
|
|
-
|
|
|
-// Prune allows manually cleaning up the cache
|
|
|
-func (fsc *FSCache) Prune(ctx context.Context) (uint64, error) {
|
|
|
- return fsc.store.Prune(ctx)
|
|
|
-}
|
|
|
-
|
|
|
-// Close stops the gc and closes the persistent db
|
|
|
-func (fsc *FSCache) Close() error {
|
|
|
- return fsc.store.Close()
|
|
|
-}
|
|
|
-
|
|
|
-func syncFrom(ctx context.Context, cs *cachedSourceRef, transport Transport, id RemoteIdentifier) (retErr error) {
|
|
|
- src := cs.src
|
|
|
- if src == nil {
|
|
|
- src = remotecontext.NewCachableSource(cs.Dir())
|
|
|
- }
|
|
|
-
|
|
|
- if !cs.cached {
|
|
|
- if err := cs.storage.db.View(func(tx *bolt.Tx) error {
|
|
|
- b := tx.Bucket([]byte(id.Key()))
|
|
|
- dt := b.Get([]byte(cacheKey))
|
|
|
- if dt != nil {
|
|
|
- if err := src.UnmarshalBinary(dt); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- } else {
|
|
|
- return errors.Wrap(src.Scan(), "failed to scan cache records")
|
|
|
- }
|
|
|
- return nil
|
|
|
- }); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- dc := &detectChanges{f: src.HandleChange}
|
|
|
-
|
|
|
- // todo: probably send a bucket to `Copy` and let it return source
|
|
|
- // but need to make sure that tx is safe
|
|
|
- if err := transport.Copy(ctx, id, cs.Dir(), dc); err != nil {
|
|
|
- return errors.Wrapf(err, "failed to copy to %s", cs.Dir())
|
|
|
- }
|
|
|
-
|
|
|
- if !dc.supported {
|
|
|
- if err := src.Scan(); err != nil {
|
|
|
- return errors.Wrap(err, "failed to scan cache records after transfer")
|
|
|
- }
|
|
|
- }
|
|
|
- cs.cached = true
|
|
|
- cs.src = src
|
|
|
- return cs.storage.db.Update(func(tx *bolt.Tx) error {
|
|
|
- dt, err := src.MarshalBinary()
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- b := tx.Bucket([]byte(id.Key()))
|
|
|
- return b.Put([]byte(cacheKey), dt)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-type fsCacheStore struct {
|
|
|
- mu sync.Mutex
|
|
|
- sources map[string]*cachedSource
|
|
|
- db *bolt.DB
|
|
|
- fs Backend
|
|
|
- gcTimer *time.Timer
|
|
|
- gcPolicy GCPolicy
|
|
|
-}
|
|
|
-
|
|
|
-// CachePolicy defines policy for keeping a resource in cache
|
|
|
-type CachePolicy struct {
|
|
|
- Priority int
|
|
|
- LastUsed time.Time
|
|
|
-}
|
|
|
-
|
|
|
-func defaultCachePolicy() CachePolicy {
|
|
|
- return CachePolicy{Priority: 10, LastUsed: time.Now()}
|
|
|
-}
|
|
|
-
|
|
|
-func newFSCacheStore(opt Opt) (*fsCacheStore, error) {
|
|
|
- if err := os.MkdirAll(opt.Root, 0700); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- p := filepath.Join(opt.Root, dbFile)
|
|
|
- db, err := bolt.Open(p, 0600, nil)
|
|
|
- if err != nil {
|
|
|
- return nil, errors.Wrap(err, "failed to open database file %s")
|
|
|
- }
|
|
|
- s := &fsCacheStore{db: db, sources: make(map[string]*cachedSource), fs: opt.Backend, gcPolicy: opt.GCPolicy}
|
|
|
- db.View(func(tx *bolt.Tx) error {
|
|
|
- return tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
|
|
- dt := b.Get([]byte(metaKey))
|
|
|
- if dt == nil {
|
|
|
- return nil
|
|
|
- }
|
|
|
- var sm sourceMeta
|
|
|
- if err := json.Unmarshal(dt, &sm); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- dir, err := s.fs.Get(sm.BackendID)
|
|
|
- if err != nil {
|
|
|
- return err // TODO: handle gracefully
|
|
|
- }
|
|
|
- source := &cachedSource{
|
|
|
- refs: make(map[*cachedSourceRef]struct{}),
|
|
|
- id: string(name),
|
|
|
- dir: dir,
|
|
|
- sourceMeta: sm,
|
|
|
- storage: s,
|
|
|
- }
|
|
|
- s.sources[string(name)] = source
|
|
|
- return nil
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- s.gcTimer = s.startPeriodicGC(5 * time.Minute)
|
|
|
- return s, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *fsCacheStore) startPeriodicGC(interval time.Duration) *time.Timer {
|
|
|
- var t *time.Timer
|
|
|
- t = time.AfterFunc(interval, func() {
|
|
|
- if err := s.GC(); err != nil {
|
|
|
- logrus.Errorf("build gc error: %v", err)
|
|
|
- }
|
|
|
- t.Reset(interval)
|
|
|
- })
|
|
|
- return t
|
|
|
-}
|
|
|
-
|
|
|
-func (s *fsCacheStore) Close() error {
|
|
|
- s.gcTimer.Stop()
|
|
|
- return s.db.Close()
|
|
|
-}
|
|
|
-
|
|
|
-func (s *fsCacheStore) New(id, sharedKey string) (*cachedSourceRef, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- var ret *cachedSource
|
|
|
- if err := s.db.Update(func(tx *bolt.Tx) error {
|
|
|
- b, err := tx.CreateBucket([]byte(id))
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- backendID := stringid.GenerateRandomID()
|
|
|
- dir, err := s.fs.Get(backendID)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- source := &cachedSource{
|
|
|
- refs: make(map[*cachedSourceRef]struct{}),
|
|
|
- id: id,
|
|
|
- dir: dir,
|
|
|
- sourceMeta: sourceMeta{
|
|
|
- BackendID: backendID,
|
|
|
- SharedKey: sharedKey,
|
|
|
- CachePolicy: defaultCachePolicy(),
|
|
|
- },
|
|
|
- storage: s,
|
|
|
- }
|
|
|
- dt, err := json.Marshal(source.sourceMeta)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if err := b.Put([]byte(metaKey), dt); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- s.sources[id] = source
|
|
|
- ret = source
|
|
|
- return nil
|
|
|
- }); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- return ret.getRef(), nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *fsCacheStore) Rebase(sharedKey, newid string) (*cachedSourceRef, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- var ret *cachedSource
|
|
|
- for id, snap := range s.sources {
|
|
|
- if snap.SharedKey == sharedKey && len(snap.refs) == 0 {
|
|
|
- if err := s.db.Update(func(tx *bolt.Tx) error {
|
|
|
- if err := tx.DeleteBucket([]byte(id)); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- b, err := tx.CreateBucket([]byte(newid))
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- snap.id = newid
|
|
|
- snap.CachePolicy = defaultCachePolicy()
|
|
|
- dt, err := json.Marshal(snap.sourceMeta)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if err := b.Put([]byte(metaKey), dt); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- delete(s.sources, id)
|
|
|
- s.sources[newid] = snap
|
|
|
- return nil
|
|
|
- }); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- ret = snap
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- if ret == nil {
|
|
|
- return nil, errors.Errorf("no candidate for rebase")
|
|
|
- }
|
|
|
- return ret.getRef(), nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *fsCacheStore) Get(id string) (*cachedSourceRef, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- src, ok := s.sources[id]
|
|
|
- if !ok {
|
|
|
- return nil, errors.Errorf("not found")
|
|
|
- }
|
|
|
- return src.getRef(), nil
|
|
|
-}
|
|
|
-
|
|
|
-// DiskUsage reports how much data is allocated by the cache
|
|
|
-func (s *fsCacheStore) DiskUsage(ctx context.Context) (int64, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- var size int64
|
|
|
-
|
|
|
- for _, snap := range s.sources {
|
|
|
- if len(snap.refs) == 0 {
|
|
|
- ss, err := snap.getSize(ctx)
|
|
|
- if err != nil {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- size += ss
|
|
|
- }
|
|
|
- }
|
|
|
- return size, nil
|
|
|
-}
|
|
|
-
|
|
|
-// Prune allows manually cleaning up the cache
|
|
|
-func (s *fsCacheStore) Prune(ctx context.Context) (uint64, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- var size uint64
|
|
|
-
|
|
|
- for id, snap := range s.sources {
|
|
|
- select {
|
|
|
- case <-ctx.Done():
|
|
|
- logrus.Debugf("Cache prune operation cancelled, pruned size: %d", size)
|
|
|
- // when the context is cancelled, only return current size and nil
|
|
|
- return size, nil
|
|
|
- default:
|
|
|
- }
|
|
|
- if len(snap.refs) == 0 {
|
|
|
- ss, err := snap.getSize(ctx)
|
|
|
- if err != nil {
|
|
|
- return size, err
|
|
|
- }
|
|
|
- if err := s.delete(id); err != nil {
|
|
|
- return size, errors.Wrapf(err, "failed to delete %s", id)
|
|
|
- }
|
|
|
- size += uint64(ss)
|
|
|
- }
|
|
|
- }
|
|
|
- return size, nil
|
|
|
-}
|
|
|
-
|
|
|
-// GC runs a garbage collector on FSCache
|
|
|
-func (s *fsCacheStore) GC() error {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- var size uint64
|
|
|
-
|
|
|
- ctx := context.Background()
|
|
|
- cutoff := time.Now().Add(-s.gcPolicy.MaxKeepDuration)
|
|
|
- var blacklist []*cachedSource
|
|
|
-
|
|
|
- for id, snap := range s.sources {
|
|
|
- if len(snap.refs) == 0 {
|
|
|
- if cutoff.After(snap.CachePolicy.LastUsed) {
|
|
|
- if err := s.delete(id); err != nil {
|
|
|
- return errors.Wrapf(err, "failed to delete %s", id)
|
|
|
- }
|
|
|
- } else {
|
|
|
- ss, err := snap.getSize(ctx)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- size += uint64(ss)
|
|
|
- blacklist = append(blacklist, snap)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- sort.Sort(sortableCacheSources(blacklist))
|
|
|
- for _, snap := range blacklist {
|
|
|
- if size <= s.gcPolicy.MaxSize {
|
|
|
- break
|
|
|
- }
|
|
|
- ss, err := snap.getSize(ctx)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if err := s.delete(snap.id); err != nil {
|
|
|
- return errors.Wrapf(err, "failed to delete %s", snap.id)
|
|
|
- }
|
|
|
- size -= uint64(ss)
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// keep mu while calling this
|
|
|
-func (s *fsCacheStore) delete(id string) error {
|
|
|
- src, ok := s.sources[id]
|
|
|
- if !ok {
|
|
|
- return nil
|
|
|
- }
|
|
|
- if len(src.refs) > 0 {
|
|
|
- return errors.Errorf("can't delete %s because it has active references", id)
|
|
|
- }
|
|
|
- delete(s.sources, id)
|
|
|
- if err := s.db.Update(func(tx *bolt.Tx) error {
|
|
|
- return tx.DeleteBucket([]byte(id))
|
|
|
- }); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- return s.fs.Remove(src.BackendID)
|
|
|
-}
|
|
|
-
|
|
|
-type sourceMeta struct {
|
|
|
- SharedKey string
|
|
|
- BackendID string
|
|
|
- CachePolicy CachePolicy
|
|
|
- Size int64
|
|
|
-}
|
|
|
-
|
|
|
-//nolint:structcheck
|
|
|
-type cachedSource struct {
|
|
|
- sourceMeta
|
|
|
- refs map[*cachedSourceRef]struct{}
|
|
|
- id string
|
|
|
- dir string
|
|
|
- src *remotecontext.CachableSource
|
|
|
- storage *fsCacheStore
|
|
|
- cached bool // keep track if cache is up to date
|
|
|
-}
|
|
|
-
|
|
|
-type cachedSourceRef struct {
|
|
|
- *cachedSource
|
|
|
-}
|
|
|
-
|
|
|
-func (cs *cachedSource) Dir() string {
|
|
|
- return cs.dir
|
|
|
-}
|
|
|
-
|
|
|
-// hold storage lock before calling
|
|
|
-func (cs *cachedSource) getRef() *cachedSourceRef {
|
|
|
- ref := &cachedSourceRef{cachedSource: cs}
|
|
|
- cs.refs[ref] = struct{}{}
|
|
|
- return ref
|
|
|
-}
|
|
|
-
|
|
|
-// hold storage lock before calling
|
|
|
-func (cs *cachedSource) getSize(ctx context.Context) (int64, error) {
|
|
|
- if cs.sourceMeta.Size < 0 {
|
|
|
- ss, err := directory.Size(ctx, cs.dir)
|
|
|
- if err != nil {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- if err := cs.resetSize(ss); err != nil {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- return ss, nil
|
|
|
- }
|
|
|
- return cs.sourceMeta.Size, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (cs *cachedSource) resetSize(val int64) error {
|
|
|
- cs.sourceMeta.Size = val
|
|
|
- return cs.saveMeta()
|
|
|
-}
|
|
|
-func (cs *cachedSource) saveMeta() error {
|
|
|
- return cs.storage.db.Update(func(tx *bolt.Tx) error {
|
|
|
- b := tx.Bucket([]byte(cs.id))
|
|
|
- dt, err := json.Marshal(cs.sourceMeta)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- return b.Put([]byte(metaKey), dt)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-func (csr *cachedSourceRef) Release() error {
|
|
|
- csr.cachedSource.storage.mu.Lock()
|
|
|
- defer csr.cachedSource.storage.mu.Unlock()
|
|
|
- delete(csr.cachedSource.refs, csr)
|
|
|
- if len(csr.cachedSource.refs) == 0 {
|
|
|
- go csr.cachedSource.storage.GC()
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-type detectChanges struct {
|
|
|
- f fsutil.ChangeFunc
|
|
|
- supported bool
|
|
|
-}
|
|
|
-
|
|
|
-func (dc *detectChanges) HandleChange(kind fsutil.ChangeKind, path string, fi os.FileInfo, err error) error {
|
|
|
- if dc == nil {
|
|
|
- return nil
|
|
|
- }
|
|
|
- return dc.f(kind, path, fi, err)
|
|
|
-}
|
|
|
-
|
|
|
-func (dc *detectChanges) MarkSupported(v bool) {
|
|
|
- if dc == nil {
|
|
|
- return
|
|
|
- }
|
|
|
- dc.supported = v
|
|
|
-}
|
|
|
-
|
|
|
-func (dc *detectChanges) ContentHasher() fsutil.ContentHasher {
|
|
|
- return newTarsumHash
|
|
|
-}
|
|
|
-
|
|
|
-type wrappedContext struct {
|
|
|
- builder.Source
|
|
|
- closer func() error
|
|
|
-}
|
|
|
-
|
|
|
-func (wc *wrappedContext) Close() error {
|
|
|
- if err := wc.Source.Close(); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- return wc.closer()
|
|
|
-}
|
|
|
-
|
|
|
-type sortableCacheSources []*cachedSource
|
|
|
-
|
|
|
-// Len is the number of elements in the collection.
|
|
|
-func (s sortableCacheSources) Len() int {
|
|
|
- return len(s)
|
|
|
-}
|
|
|
-
|
|
|
-// Less reports whether the element with
|
|
|
-// index i should sort before the element with index j.
|
|
|
-func (s sortableCacheSources) Less(i, j int) bool {
|
|
|
- return s[i].CachePolicy.LastUsed.Before(s[j].CachePolicy.LastUsed)
|
|
|
-}
|
|
|
-
|
|
|
-// Swap swaps the elements with indexes i and j.
|
|
|
-func (s sortableCacheSources) Swap(i, j int) {
|
|
|
- s[i], s[j] = s[j], s[i]
|
|
|
-}
|
|
|
-
|
|
|
-func newTarsumHash(stat *fsutiltypes.Stat) (hash.Hash, error) {
|
|
|
- fi := &fsutil.StatInfo{Stat: stat}
|
|
|
- p := stat.Path
|
|
|
- if fi.IsDir() {
|
|
|
- p += string(os.PathSeparator)
|
|
|
- }
|
|
|
- h, err := archive.FileInfoHeader(p, fi, stat.Linkname)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- h.Name = p
|
|
|
- h.Uid = int(stat.Uid)
|
|
|
- h.Gid = int(stat.Gid)
|
|
|
- h.Linkname = stat.Linkname
|
|
|
- if stat.Xattrs != nil {
|
|
|
- h.Xattrs = make(map[string]string)
|
|
|
- for k, v := range stat.Xattrs {
|
|
|
- h.Xattrs[k] = string(v)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- tsh := &tarsumHash{h: h, Hash: sha256.New()}
|
|
|
- tsh.Reset()
|
|
|
- return tsh, nil
|
|
|
-}
|
|
|
-
|
|
|
-// Reset resets the Hash to its initial state.
|
|
|
-func (tsh *tarsumHash) Reset() {
|
|
|
- tsh.Hash.Reset()
|
|
|
- tarsum.WriteV1Header(tsh.h, tsh.Hash)
|
|
|
-}
|
|
|
-
|
|
|
-type tarsumHash struct {
|
|
|
- hash.Hash
|
|
|
- h *tar.Header
|
|
|
-}
|