github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/builder/remotecontext/tarsum.go (about)

     1  package remotecontext // import "github.com/docker/docker/builder/remotecontext"
     2  
     3  import (
     4  	"os"
     5  	"sync"
     6  
     7  	"github.com/docker/docker/pkg/containerfs"
     8  	iradix "github.com/hashicorp/go-immutable-radix"
     9  	digest "github.com/opencontainers/go-digest"
    10  	"github.com/pkg/errors"
    11  	"github.com/tonistiigi/fsutil"
    12  )
    13  
    14  type hashed interface {
    15  	Digest() digest.Digest
    16  }
    17  
    18  // CachableSource is a source that contains cache records for its contents
    19  type CachableSource struct {
    20  	mu   sync.Mutex
    21  	root containerfs.ContainerFS
    22  	tree *iradix.Tree
    23  	txn  *iradix.Txn
    24  }
    25  
    26  // NewCachableSource creates new CachableSource
    27  func NewCachableSource(root string) *CachableSource {
    28  	ts := &CachableSource{
    29  		tree: iradix.New(),
    30  		root: containerfs.NewLocalContainerFS(root),
    31  	}
    32  	return ts
    33  }
    34  
    35  // MarshalBinary marshals current cache information to a byte array
    36  func (cs *CachableSource) MarshalBinary() ([]byte, error) {
    37  	b := TarsumBackup{Hashes: make(map[string]string)}
    38  	root := cs.getRoot()
    39  	root.Walk(func(k []byte, v interface{}) bool {
    40  		b.Hashes[string(k)] = v.(*fileInfo).sum
    41  		return false
    42  	})
    43  	return b.Marshal()
    44  }
    45  
    46  // UnmarshalBinary decodes cache information for presented byte array
    47  func (cs *CachableSource) UnmarshalBinary(data []byte) error {
    48  	var b TarsumBackup
    49  	if err := b.Unmarshal(data); err != nil {
    50  		return err
    51  	}
    52  	txn := iradix.New().Txn()
    53  	for p, v := range b.Hashes {
    54  		txn.Insert([]byte(p), &fileInfo{sum: v})
    55  	}
    56  	cs.mu.Lock()
    57  	defer cs.mu.Unlock()
    58  	cs.tree = txn.Commit()
    59  	return nil
    60  }
    61  
    62  // Scan rescans the cache information from the file system
    63  func (cs *CachableSource) Scan() error {
    64  	lc, err := NewLazySource(cs.root)
    65  	if err != nil {
    66  		return err
    67  	}
    68  	txn := iradix.New().Txn()
    69  	err = cs.root.Walk(cs.root.Path(), func(path string, info os.FileInfo, err error) error {
    70  		if err != nil {
    71  			return errors.Wrapf(err, "failed to walk %s", path)
    72  		}
    73  		rel, err := Rel(cs.root, path)
    74  		if err != nil {
    75  			return err
    76  		}
    77  		h, err := lc.Hash(rel)
    78  		if err != nil {
    79  			return err
    80  		}
    81  		txn.Insert([]byte(rel), &fileInfo{sum: h})
    82  		return nil
    83  	})
    84  	if err != nil {
    85  		return err
    86  	}
    87  	cs.mu.Lock()
    88  	defer cs.mu.Unlock()
    89  	cs.tree = txn.Commit()
    90  	return nil
    91  }
    92  
    93  // HandleChange notifies the source about a modification operation
    94  func (cs *CachableSource) HandleChange(kind fsutil.ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
    95  	cs.mu.Lock()
    96  	if cs.txn == nil {
    97  		cs.txn = cs.tree.Txn()
    98  	}
    99  	if kind == fsutil.ChangeKindDelete {
   100  		cs.txn.Delete([]byte(p))
   101  		cs.mu.Unlock()
   102  		return
   103  	}
   104  
   105  	h, ok := fi.(hashed)
   106  	if !ok {
   107  		cs.mu.Unlock()
   108  		return errors.Errorf("invalid fileinfo: %s", p)
   109  	}
   110  
   111  	hfi := &fileInfo{
   112  		sum: h.Digest().Hex(),
   113  	}
   114  	cs.txn.Insert([]byte(p), hfi)
   115  	cs.mu.Unlock()
   116  	return nil
   117  }
   118  
   119  func (cs *CachableSource) getRoot() *iradix.Node {
   120  	cs.mu.Lock()
   121  	if cs.txn != nil {
   122  		cs.tree = cs.txn.Commit()
   123  		cs.txn = nil
   124  	}
   125  	t := cs.tree
   126  	cs.mu.Unlock()
   127  	return t.Root()
   128  }
   129  
   130  // Close closes the source
   131  func (cs *CachableSource) Close() error {
   132  	return nil
   133  }
   134  
   135  // Hash returns a hash for a single file in the source
   136  func (cs *CachableSource) Hash(path string) (string, error) {
   137  	n := cs.getRoot()
   138  	// TODO: check this for symlinks
   139  	v, ok := n.Get([]byte(path))
   140  	if !ok {
   141  		return path, nil
   142  	}
   143  	return v.(*fileInfo).sum, nil
   144  }
   145  
   146  // Root returns a root directory for the source
   147  func (cs *CachableSource) Root() containerfs.ContainerFS {
   148  	return cs.root
   149  }
   150  
   151  type fileInfo struct {
   152  	sum string
   153  }
   154  
   155  func (fi *fileInfo) Hash() string {
   156  	return fi.sum
   157  }