github.com/yamamoto-febc/docker@v1.9.0/builder/tarsum.go (about)

     1  package builder
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/pkg/archive"
    11  	"github.com/docker/docker/pkg/chrootarchive"
    12  	"github.com/docker/docker/pkg/ioutils"
    13  	"github.com/docker/docker/pkg/symlink"
    14  	"github.com/docker/docker/pkg/tarsum"
    15  )
    16  
    17  type tarSumContext struct {
    18  	root string
    19  	sums tarsum.FileInfoSums
    20  }
    21  
    22  func (c *tarSumContext) Close() error {
    23  	return os.RemoveAll(c.root)
    24  }
    25  
    26  func convertPathError(err error, cleanpath string) error {
    27  	if err, ok := err.(*os.PathError); ok {
    28  		err.Path = cleanpath
    29  		return err
    30  	}
    31  	return err
    32  }
    33  
    34  func (c *tarSumContext) Open(path string) (io.ReadCloser, error) {
    35  	cleanpath, fullpath, err := c.normalize(path)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	r, err := os.Open(fullpath)
    40  	if err != nil {
    41  		return nil, convertPathError(err, cleanpath)
    42  	}
    43  	return r, nil
    44  }
    45  
    46  func (c *tarSumContext) Stat(path string) (fi FileInfo, err error) {
    47  	cleanpath, fullpath, err := c.normalize(path)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	st, err := os.Lstat(fullpath)
    53  	if err != nil {
    54  		return nil, convertPathError(err, cleanpath)
    55  	}
    56  
    57  	fi = PathFileInfo{st, fullpath}
    58  	// we set sum to path by default for the case where GetFile returns nil.
    59  	// The usual case is if cleanpath is empty.
    60  	sum := path
    61  	if tsInfo := c.sums.GetFile(cleanpath); tsInfo != nil {
    62  		sum = tsInfo.Sum()
    63  	}
    64  	fi = &HashedFileInfo{fi, sum}
    65  	return fi, nil
    66  }
    67  
    68  // MakeTarSumContext returns a build Context from a tar stream.
    69  //
    70  // It extracts the tar stream to a temporary folder that is deleted as soon as
    71  // the Context is closed.
    72  // As the extraction happens, a tarsum is calculated for every file, and the set of
    73  // all those sums then becomes the source of truth for all operations on this Context.
    74  //
    75  // Closing tarStream has to be done by the caller.
    76  func MakeTarSumContext(tarStream io.Reader) (ModifiableContext, error) {
    77  	root, err := ioutils.TempDir("", "docker-builder")
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	tsc := &tarSumContext{root: root}
    83  
    84  	// Make sure we clean-up upon error.  In the happy case the caller
    85  	// is expected to manage the clean-up
    86  	defer func() {
    87  		if err != nil {
    88  			tsc.Close()
    89  		}
    90  	}()
    91  
    92  	decompressedStream, err := archive.DecompressStream(tarStream)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	sum, err := tarsum.NewTarSum(decompressedStream, true, tarsum.Version1)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	if err := chrootarchive.Untar(sum, root, nil); err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	tsc.sums = sum.GetSums()
   107  
   108  	return tsc, nil
   109  }
   110  
   111  func (c *tarSumContext) normalize(path string) (cleanpath, fullpath string, err error) {
   112  	cleanpath = filepath.Clean(string(os.PathSeparator) + path)[1:]
   113  	fullpath, err = symlink.FollowSymlinkInScope(filepath.Join(c.root, path), c.root)
   114  	if err != nil {
   115  		return "", "", fmt.Errorf("Forbidden path outside the build context: %s (%s)", path, fullpath)
   116  	}
   117  	_, err = os.Stat(fullpath)
   118  	if err != nil {
   119  		return "", "", convertPathError(err, path)
   120  	}
   121  	return
   122  }
   123  
   124  func (c *tarSumContext) Walk(root string, walkFn WalkFunc) error {
   125  	for _, tsInfo := range c.sums {
   126  		path := tsInfo.Name()
   127  		path, fullpath, err := c.normalize(path)
   128  		if err != nil {
   129  			return err
   130  		}
   131  
   132  		// Any file in the context that starts with the given path will be
   133  		// picked up and its hashcode used.  However, we'll exclude the
   134  		// root dir itself.  We do this for a coupel of reasons:
   135  		// 1 - ADD/COPY will not copy the dir itself, just its children
   136  		//     so there's no reason to include it in the hash calc
   137  		// 2 - the metadata on the dir will change when any child file
   138  		//     changes.  This will lead to a miss in the cache check if that
   139  		//     child file is in the .dockerignore list.
   140  		if rel, err := filepath.Rel(root, path); err != nil {
   141  			return err
   142  		} else if rel == "." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
   143  			continue
   144  		}
   145  
   146  		info, err := os.Lstat(fullpath)
   147  		if err != nil {
   148  			return convertPathError(err, path)
   149  		}
   150  		// TODO check context breakout?
   151  		fi := &HashedFileInfo{PathFileInfo{info, fullpath}, tsInfo.Sum()}
   152  		if err := walkFn(path, fi, nil); err != nil {
   153  			return err
   154  		}
   155  	}
   156  	return nil
   157  }
   158  
   159  func (c *tarSumContext) Remove(path string) error {
   160  	_, fullpath, err := c.normalize(path)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	return os.RemoveAll(fullpath)
   165  }