1package remotecontext
2
3import (
4	"encoding/hex"
5	"os"
6	"strings"
7
8	"github.com/docker/docker/builder"
9	"github.com/docker/docker/pkg/containerfs"
10	"github.com/docker/docker/pkg/pools"
11	"github.com/pkg/errors"
12)
13
14// NewLazySource creates a new LazyContext. LazyContext defines a hashed build
15// context based on a root directory. Individual files are hashed first time
16// they are asked. It is not safe to call methods of LazyContext concurrently.
17func NewLazySource(root containerfs.ContainerFS) (builder.Source, error) {
18	return &lazySource{
19		root: root,
20		sums: make(map[string]string),
21	}, nil
22}
23
24type lazySource struct {
25	root containerfs.ContainerFS
26	sums map[string]string
27}
28
29func (c *lazySource) Root() containerfs.ContainerFS {
30	return c.root
31}
32
33func (c *lazySource) Close() error {
34	return nil
35}
36
37func (c *lazySource) Hash(path string) (string, error) {
38	cleanPath, fullPath, err := normalize(path, c.root)
39	if err != nil {
40		return "", err
41	}
42
43	relPath, err := Rel(c.root, fullPath)
44	if err != nil {
45		return "", errors.WithStack(convertPathError(err, cleanPath))
46	}
47
48	fi, err := os.Lstat(fullPath)
49	if err != nil {
50		// Backwards compatibility: a missing file returns a path as hash.
51		// This is reached in the case of a broken symlink.
52		return relPath, nil
53	}
54
55	sum, ok := c.sums[relPath]
56	if !ok {
57		sum, err = c.prepareHash(relPath, fi)
58		if err != nil {
59			return "", err
60		}
61	}
62
63	return sum, nil
64}
65
66func (c *lazySource) prepareHash(relPath string, fi os.FileInfo) (string, error) {
67	p := c.root.Join(c.root.Path(), relPath)
68	h, err := NewFileHash(p, relPath, fi)
69	if err != nil {
70		return "", errors.Wrapf(err, "failed to create hash for %s", relPath)
71	}
72	if fi.Mode().IsRegular() && fi.Size() > 0 {
73		f, err := c.root.Open(p)
74		if err != nil {
75			return "", errors.Wrapf(err, "failed to open %s", relPath)
76		}
77		defer f.Close()
78		if _, err := pools.Copy(h, f); err != nil {
79			return "", errors.Wrapf(err, "failed to copy file data for %s", relPath)
80		}
81	}
82	sum := hex.EncodeToString(h.Sum(nil))
83	c.sums[relPath] = sum
84	return sum, nil
85}
86
87// Rel makes a path relative to base path. Same as `filepath.Rel` but can also
88// handle UUID paths in windows.
89func Rel(basepath containerfs.ContainerFS, targpath string) (string, error) {
90	// filepath.Rel can't handle UUID paths in windows
91	if basepath.OS() == "windows" {
92		pfx := basepath.Path() + `\`
93		if strings.HasPrefix(targpath, pfx) {
94			p := strings.TrimPrefix(targpath, pfx)
95			if p == "" {
96				p = "."
97			}
98			return p, nil
99		}
100	}
101	return basepath.Rel(basepath.Path(), targpath)
102}
103