| package remotecontext // import "github.com/docker/docker/builder/remotecontext" | |
| import ( | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "github.com/docker/docker/builder" | |
| "github.com/docker/docker/pkg/archive" | |
| "github.com/docker/docker/pkg/chrootarchive" | |
| "github.com/docker/docker/pkg/containerfs" | |
| "github.com/docker/docker/pkg/longpath" | |
| "github.com/docker/docker/pkg/tarsum" | |
| "github.com/pkg/errors" | |
| ) | |
| type archiveContext struct { | |
| root string | |
| sums tarsum.FileInfoSums | |
| } | |
| func (c *archiveContext) 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 | |
| } | |
| type modifiableContext interface { | |
| builder.Source | |
| // Remove deletes the entry specified by `path`. | |
| // It is usual for directory entries to delete all its subentries. | |
| Remove(path string) error | |
| } | |
| // FromArchive returns a build source 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 FromArchive(tarStream io.Reader) (builder.Source, error) { | |
| root, err := longpath.MkdirTemp("", "docker-builder") | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Assume local file system. Since it's coming from a tar file. | |
| tsc := &archiveContext{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 | |
| } | |
| err = chrootarchive.Untar(sum, root, nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| tsc.sums = sum.GetSums() | |
| return tsc, nil | |
| } | |
| func (c *archiveContext) Root() string { | |
| return c.root | |
| } | |
| func (c *archiveContext) Remove(path string) error { | |
| _, fullpath, err := normalize(path, c.root) | |
| if err != nil { | |
| return err | |
| } | |
| return os.RemoveAll(fullpath) | |
| } | |
| func (c *archiveContext) Hash(path string) (string, error) { | |
| cleanpath, fullpath, err := normalize(path, c.root) | |
| if err != nil { | |
| return "", err | |
| } | |
| rel, err := filepath.Rel(c.root, fullpath) | |
| if err != nil { | |
| return "", convertPathError(err, cleanpath) | |
| } | |
| // Use the checksum of the followed path(not the possible symlink) because | |
| // this is the file that is actually copied. | |
| if tsInfo := c.sums.GetFile(filepath.ToSlash(rel)); tsInfo != nil { | |
| return tsInfo.Sum(), nil | |
| } | |
| // We set sum to path by default for the case where GetFile returns nil. | |
| // The usual case is if relative path is empty. | |
| return path, nil // backwards compat TODO: see if really needed | |
| } | |
| func normalize(path string, root string) (cleanPath, fullPath string, err error) { | |
| cleanPath = filepath.Clean(string(filepath.Separator) + path)[1:] | |
| fullPath, err = containerfs.ResolveScopedPath(root, path) | |
| if err != nil { | |
| return "", "", errors.Wrapf(err, "forbidden path outside the build context: %s (%s)", path, cleanPath) | |
| } | |
| return | |
| } |