tar.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // Copyright 2015 go-dockerclient authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package docker
  5. import (
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "strings"
  13. "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive"
  14. "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils"
  15. )
  16. func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) {
  17. excludes, err := parseDockerignore(srcPath)
  18. if err != nil {
  19. return nil, err
  20. }
  21. includes := []string{"."}
  22. // If .dockerignore mentions .dockerignore or the Dockerfile
  23. // then make sure we send both files over to the daemon
  24. // because Dockerfile is, obviously, needed no matter what, and
  25. // .dockerignore is needed to know if either one needs to be
  26. // removed. The deamon will remove them for us, if needed, after it
  27. // parses the Dockerfile.
  28. //
  29. // https://github.com/docker/docker/issues/8330
  30. //
  31. forceIncludeFiles := []string{".dockerignore", dockerfilePath}
  32. for _, includeFile := range forceIncludeFiles {
  33. if includeFile == "" {
  34. continue
  35. }
  36. keepThem, err := fileutils.Matches(includeFile, excludes)
  37. if err != nil {
  38. return nil, fmt.Errorf("cannot match .dockerfile: '%s', error: %s", includeFile, err)
  39. }
  40. if keepThem {
  41. includes = append(includes, includeFile)
  42. }
  43. }
  44. if err := validateContextDirectory(srcPath, excludes); err != nil {
  45. return nil, err
  46. }
  47. tarOpts := &archive.TarOptions{
  48. ExcludePatterns: excludes,
  49. IncludeFiles: includes,
  50. Compression: archive.Uncompressed,
  51. NoLchown: true,
  52. }
  53. return archive.TarWithOptions(srcPath, tarOpts)
  54. }
  55. // validateContextDirectory checks if all the contents of the directory
  56. // can be read and returns an error if some files can't be read.
  57. // Symlinks which point to non-existing files don't trigger an error
  58. func validateContextDirectory(srcPath string, excludes []string) error {
  59. return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
  60. // skip this directory/file if it's not in the path, it won't get added to the context
  61. if relFilePath, err := filepath.Rel(srcPath, filePath); err != nil {
  62. return err
  63. } else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
  64. return err
  65. } else if skip {
  66. if f.IsDir() {
  67. return filepath.SkipDir
  68. }
  69. return nil
  70. }
  71. if err != nil {
  72. if os.IsPermission(err) {
  73. return fmt.Errorf("can't stat '%s'", filePath)
  74. }
  75. if os.IsNotExist(err) {
  76. return nil
  77. }
  78. return err
  79. }
  80. // skip checking if symlinks point to non-existing files, such symlinks can be useful
  81. // also skip named pipes, because they hanging on open
  82. if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
  83. return nil
  84. }
  85. if !f.IsDir() {
  86. currentFile, err := os.Open(filePath)
  87. if err != nil && os.IsPermission(err) {
  88. return fmt.Errorf("no permission to read from '%s'", filePath)
  89. }
  90. currentFile.Close()
  91. }
  92. return nil
  93. })
  94. }
  95. func parseDockerignore(root string) ([]string, error) {
  96. var excludes []string
  97. ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore"))
  98. if err != nil && !os.IsNotExist(err) {
  99. return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err)
  100. }
  101. excludes = strings.Split(string(ignore), "\n")
  102. return excludes, nil
  103. }