github.com/damirazo/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 }