lazycontext.go 2.5 KB

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