1package remotecontext // import "github.com/docker/docker/builder/remotecontext"
2
3import (
4	"io"
5	"os"
6	"path/filepath"
7
8	"github.com/docker/docker/builder"
9	"github.com/docker/docker/pkg/archive"
10	"github.com/docker/docker/pkg/chrootarchive"
11	"github.com/docker/docker/pkg/containerfs"
12	"github.com/docker/docker/pkg/ioutils"
13	"github.com/docker/docker/pkg/tarsum"
14	"github.com/pkg/errors"
15)
16
17type archiveContext struct {
18	root containerfs.ContainerFS
19	sums tarsum.FileInfoSums
20}
21
22func (c *archiveContext) Close() error {
23	return c.root.RemoveAll(c.root.Path())
24}
25
26func 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
34type modifiableContext interface {
35	builder.Source
36	// Remove deletes the entry specified by `path`.
37	// It is usual for directory entries to delete all its subentries.
38	Remove(path string) error
39}
40
41// FromArchive returns a build source from a tar stream.
42//
43// It extracts the tar stream to a temporary folder that is deleted as soon as
44// the Context is closed.
45// As the extraction happens, a tarsum is calculated for every file, and the set of
46// all those sums then becomes the source of truth for all operations on this Context.
47//
48// Closing tarStream has to be done by the caller.
49func FromArchive(tarStream io.Reader) (builder.Source, error) {
50	root, err := ioutils.TempDir("", "docker-builder")
51	if err != nil {
52		return nil, err
53	}
54
55	// Assume local file system. Since it's coming from a tar file.
56	tsc := &archiveContext{root: containerfs.NewLocalContainerFS(root)}
57
58	// Make sure we clean-up upon error.  In the happy case the caller
59	// is expected to manage the clean-up
60	defer func() {
61		if err != nil {
62			tsc.Close()
63		}
64	}()
65
66	decompressedStream, err := archive.DecompressStream(tarStream)
67	if err != nil {
68		return nil, err
69	}
70
71	sum, err := tarsum.NewTarSum(decompressedStream, true, tarsum.Version1)
72	if err != nil {
73		return nil, err
74	}
75
76	err = chrootarchive.Untar(sum, root, nil)
77	if err != nil {
78		return nil, err
79	}
80
81	tsc.sums = sum.GetSums()
82	return tsc, nil
83}
84
85func (c *archiveContext) Root() containerfs.ContainerFS {
86	return c.root
87}
88
89func (c *archiveContext) Remove(path string) error {
90	_, fullpath, err := normalize(path, c.root)
91	if err != nil {
92		return err
93	}
94	return c.root.RemoveAll(fullpath)
95}
96
97func (c *archiveContext) Hash(path string) (string, error) {
98	cleanpath, fullpath, err := normalize(path, c.root)
99	if err != nil {
100		return "", err
101	}
102
103	rel, err := c.root.Rel(c.root.Path(), fullpath)
104	if err != nil {
105		return "", convertPathError(err, cleanpath)
106	}
107
108	// Use the checksum of the followed path(not the possible symlink) because
109	// this is the file that is actually copied.
110	if tsInfo := c.sums.GetFile(filepath.ToSlash(rel)); tsInfo != nil {
111		return tsInfo.Sum(), nil
112	}
113	// We set sum to path by default for the case where GetFile returns nil.
114	// The usual case is if relative path is empty.
115	return path, nil // backwards compat TODO: see if really needed
116}
117
118func normalize(path string, root containerfs.ContainerFS) (cleanPath, fullPath string, err error) {
119	cleanPath = root.Clean(string(root.Separator()) + path)[1:]
120	fullPath, err = root.ResolveScopedPath(path, true)
121	if err != nil {
122		return "", "", errors.Wrapf(err, "forbidden path outside the build context: %s (%s)", path, cleanPath)
123	}
124	return
125}
126