package 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/ioutils" "github.com/docker/docker/pkg/tarsum" "github.com/pkg/errors" ) type archiveContext struct { root containerfs.ContainerFS sums tarsum.FileInfoSums } func (c *archiveContext) Close() error { return c.root.RemoveAll(c.root.Path()) } 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 := ioutils.TempDir("", "docker-builder") if err != nil { return nil, err } // Assume local file system. Since it's coming from a tar file. tsc := &archiveContext{root: containerfs.NewLocalContainerFS(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() containerfs.ContainerFS { return c.root } func (c *archiveContext) Remove(path string) error { _, fullpath, err := normalize(path, c.root) if err != nil { return err } return c.root.RemoveAll(fullpath) } func (c *archiveContext) Hash(path string) (string, error) { cleanpath, fullpath, err := normalize(path, c.root) if err != nil { return "", err } rel, err := c.root.Rel(c.root.Path(), 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 containerfs.ContainerFS) (cleanPath, fullPath string, err error) { cleanPath = root.Clean(string(root.Separator()) + path)[1:] fullPath, err = root.ResolveScopedPath(path, true) if err != nil { return "", "", errors.Wrapf(err, "forbidden path outside the build context: %s (%s)", path, cleanPath) } if _, err := root.Lstat(fullPath); err != nil { return "", "", errors.WithStack(convertPathError(err, path)) } return }