lazycontext.go 2.4 KB

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