| package builder |
| |
| import ( |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "github.com/docker/docker/pkg/archive" |
| "github.com/docker/docker/pkg/chrootarchive" |
| "github.com/docker/docker/pkg/ioutils" |
| "github.com/docker/docker/pkg/symlink" |
| "github.com/docker/docker/pkg/tarsum" |
| ) |
| |
| type tarSumContext struct { |
| root string |
| sums tarsum.FileInfoSums |
| } |
| |
| func (c *tarSumContext) Close() error { |
| return os.RemoveAll(c.root) |
| } |
| |
| func convertPathError(err error, cleanpath string) error { |
| if err, ok := err.(*os.PathError); ok { |
| err.Path = cleanpath |
| return err |
| } |
| return err |
| } |
| |
| func (c *tarSumContext) Open(path string) (io.ReadCloser, error) { |
| cleanpath, fullpath, err := c.normalize(path) |
| if err != nil { |
| return nil, err |
| } |
| r, err := os.Open(fullpath) |
| if err != nil { |
| return nil, convertPathError(err, cleanpath) |
| } |
| return r, nil |
| } |
| |
| func (c *tarSumContext) Stat(path string) (fi FileInfo, err error) { |
| cleanpath, fullpath, err := c.normalize(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| st, err := os.Lstat(fullpath) |
| if err != nil { |
| return nil, convertPathError(err, cleanpath) |
| } |
| |
| fi = PathFileInfo{st, fullpath} |
| // we set sum to path by default for the case where GetFile returns nil. |
| // The usual case is if cleanpath is empty. |
| sum := path |
| if tsInfo := c.sums.GetFile(cleanpath); tsInfo != nil { |
| sum = tsInfo.Sum() |
| } |
| fi = &HashedFileInfo{fi, sum} |
| return fi, nil |
| } |
| |
| // MakeTarSumContext returns a build Context from a tar stream. |
| // |
| // It extracts the tar stream to a temporary folder that is deleted as soon as |
| // the Context is closed. |
| // As the extraction happens, a tarsum is calculated for every file, and the set of |
| // all those sums then becomes the source of truth for all operations on this Context. |
| // |
| // Closing tarStream has to be done by the caller. |
| func MakeTarSumContext(tarStream io.Reader) (ModifiableContext, error) { |
| root, err := ioutils.TempDir("", "docker-builder") |
| if err != nil { |
| return nil, err |
| } |
| |
| tsc := &tarSumContext{root: root} |
| |
| // Make sure we clean-up upon error. In the happy case the caller |
| // is expected to manage the clean-up |
| defer func() { |
| if err != nil { |
| tsc.Close() |
| } |
| }() |
| |
| decompressedStream, err := archive.DecompressStream(tarStream) |
| if err != nil { |
| return nil, err |
| } |
| |
| sum, err := tarsum.NewTarSum(decompressedStream, true, tarsum.Version1) |
| if err != nil { |
| return nil, err |
| } |
| |
| if err := chrootarchive.Untar(sum, root, nil); err != nil { |
| return nil, err |
| } |
| |
| tsc.sums = sum.GetSums() |
| |
| return tsc, nil |
| } |
| |
| func (c *tarSumContext) normalize(path string) (cleanpath, fullpath string, err error) { |
| cleanpath = filepath.Clean(string(os.PathSeparator) + path)[1:] |
| fullpath, err = symlink.FollowSymlinkInScope(filepath.Join(c.root, path), c.root) |
| if err != nil { |
| return "", "", fmt.Errorf("Forbidden path outside the build context: %s (%s)", path, fullpath) |
| } |
| _, err = os.Stat(fullpath) |
| if err != nil { |
| return "", "", convertPathError(err, path) |
| } |
| return |
| } |
| |
| func (c *tarSumContext) Walk(root string, walkFn WalkFunc) error { |
| for _, tsInfo := range c.sums { |
| path := tsInfo.Name() |
| path, fullpath, err := c.normalize(path) |
| if err != nil { |
| return err |
| } |
| |
| // Any file in the context that starts with the given path will be |
| // picked up and its hashcode used. However, we'll exclude the |
| // root dir itself. We do this for a coupel of reasons: |
| // 1 - ADD/COPY will not copy the dir itself, just its children |
| // so there's no reason to include it in the hash calc |
| // 2 - the metadata on the dir will change when any child file |
| // changes. This will lead to a miss in the cache check if that |
| // child file is in the .dockerignore list. |
| if rel, err := filepath.Rel(root, path); err != nil { |
| return err |
| } else if rel == "." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { |
| continue |
| } |
| |
| info, err := os.Lstat(fullpath) |
| if err != nil { |
| return convertPathError(err, path) |
| } |
| // TODO check context breakout? |
| fi := &HashedFileInfo{PathFileInfo{info, fullpath}, tsInfo.Sum()} |
| if err := walkFn(path, fi, nil); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (c *tarSumContext) Remove(path string) error { |
| _, fullpath, err := c.normalize(path) |
| if err != nil { |
| return err |
| } |
| return os.RemoveAll(fullpath) |
| } |