The initial `ValidateContextDirectory` implementation fails loudly when a file
lacks read permissions in the current context. However that situation is valid
if the file is included in the `.dockerignore` patterns.
Docker-DCO-1.1-Signed-off-by: Bruno ReniƩ <brutasse@gmail.com> (github: brutasse)
| ... | ... |
@@ -163,30 +163,27 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 163 | 163 |
if _, err = os.Stat(filename); os.IsNotExist(err) {
|
| 164 | 164 |
return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0))
|
| 165 | 165 |
} |
| 166 |
- if err = utils.ValidateContextDirectory(root); err != nil {
|
|
| 166 |
+ var excludes []string |
|
| 167 |
+ ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore")) |
|
| 168 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 169 |
+ return fmt.Errorf("Error reading .dockerignore: '%s'", err)
|
|
| 170 |
+ } |
|
| 171 |
+ for _, pattern := range strings.Split(string(ignore), "\n") {
|
|
| 172 |
+ ok, err := filepath.Match(pattern, "Dockerfile") |
|
| 173 |
+ if err != nil {
|
|
| 174 |
+ return fmt.Errorf("Bad .dockerignore pattern: '%s', error: %s", pattern, err)
|
|
| 175 |
+ } |
|
| 176 |
+ if ok {
|
|
| 177 |
+ return fmt.Errorf("Dockerfile was excluded by .dockerignore pattern '%s'", pattern)
|
|
| 178 |
+ } |
|
| 179 |
+ excludes = append(excludes, pattern) |
|
| 180 |
+ } |
|
| 181 |
+ if err = utils.ValidateContextDirectory(root, excludes); err != nil {
|
|
| 167 | 182 |
return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
|
| 168 | 183 |
} |
| 169 | 184 |
options := &archive.TarOptions{
|
| 170 | 185 |
Compression: archive.Uncompressed, |
| 171 |
- } |
|
| 172 |
- if ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore")); err != nil && !os.IsNotExist(err) {
|
|
| 173 |
- return fmt.Errorf("Error reading .dockerignore: '%s'", err)
|
|
| 174 |
- } else if err == nil {
|
|
| 175 |
- for _, pattern := range strings.Split(string(ignore), "\n") {
|
|
| 176 |
- if pattern == "" {
|
|
| 177 |
- continue |
|
| 178 |
- } |
|
| 179 |
- |
|
| 180 |
- ok, err := filepath.Match(pattern, "Dockerfile") |
|
| 181 |
- if err != nil {
|
|
| 182 |
- utils.Errorf("Bad .dockerignore pattern: '%s', error: %s", pattern, err)
|
|
| 183 |
- continue |
|
| 184 |
- } |
|
| 185 |
- if ok {
|
|
| 186 |
- return fmt.Errorf("Dockerfile was excluded by .dockerignore pattern '%s'", pattern)
|
|
| 187 |
- } |
|
| 188 |
- options.Excludes = append(options.Excludes, pattern) |
|
| 189 |
- } |
|
| 186 |
+ Excludes: excludes, |
|
| 190 | 187 |
} |
| 191 | 188 |
context, err = archive.TarWithOptions(root, options) |
| 192 | 189 |
if err != nil {
|
| ... | ... |
@@ -349,23 +349,16 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) |
| 349 | 349 |
return nil |
| 350 | 350 |
} |
| 351 | 351 |
|
| 352 |
- for _, exclude := range options.Excludes {
|
|
| 353 |
- matched, err := filepath.Match(exclude, relFilePath) |
|
| 354 |
- if err != nil {
|
|
| 355 |
- utils.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
|
|
| 356 |
- return err |
|
| 357 |
- } |
|
| 358 |
- if matched {
|
|
| 359 |
- if filepath.Clean(relFilePath) == "." {
|
|
| 360 |
- utils.Errorf("Can't exclude whole path, excluding pattern: %s", exclude)
|
|
| 361 |
- continue |
|
| 362 |
- } |
|
| 363 |
- utils.Debugf("Skipping excluded path: %s", relFilePath)
|
|
| 364 |
- if f.IsDir() {
|
|
| 365 |
- return filepath.SkipDir |
|
| 366 |
- } |
|
| 367 |
- return nil |
|
| 352 |
+ skip, err := utils.Matches(relFilePath, options.Excludes) |
|
| 353 |
+ if err != nil {
|
|
| 354 |
+ utils.Debugf("Error matching %s\n", relFilePath, err)
|
|
| 355 |
+ return nil |
|
| 356 |
+ } |
|
| 357 |
+ if skip {
|
|
| 358 |
+ if f.IsDir() {
|
|
| 359 |
+ return filepath.SkipDir |
|
| 368 | 360 |
} |
| 361 |
+ return nil |
|
| 369 | 362 |
} |
| 370 | 363 |
|
| 371 | 364 |
if err := addTarFile(filePath, relFilePath, tw, twBuf); err != nil {
|
| ... | ... |
@@ -475,9 +475,32 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) {
|
| 475 | 475 |
deleteImages("testlinksok")
|
| 476 | 476 |
|
| 477 | 477 |
} |
| 478 |
+ {
|
|
| 479 |
+ // This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern |
|
| 480 |
+ pathToInaccessibleDirectoryBuildDirectory := filepath.Join(buildDirectory, "ignoredinaccessible") |
|
| 481 |
+ pathToDirectoryWithoutReadAccess := filepath.Join(pathToInaccessibleDirectoryBuildDirectory, "directoryWeCantStat") |
|
| 482 |
+ pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") |
|
| 483 |
+ err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0) |
|
| 484 |
+ errorOut(err, t, fmt.Sprintf("failed to chown directory to root: %s", err))
|
|
| 485 |
+ err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444) |
|
| 486 |
+ errorOut(err, t, fmt.Sprintf("failed to chmod directory to 755: %s", err))
|
|
| 487 |
+ err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700) |
|
| 488 |
+ errorOut(err, t, fmt.Sprintf("failed to chmod file to 444: %s", err))
|
|
| 489 |
+ |
|
| 490 |
+ buildCommandStatement := fmt.Sprintf("%s build -t ignoredinaccessible .", dockerBinary)
|
|
| 491 |
+ buildCmd := exec.Command("su", "unprivilegeduser", "-c", buildCommandStatement)
|
|
| 492 |
+ buildCmd.Dir = pathToInaccessibleDirectoryBuildDirectory |
|
| 493 |
+ out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 494 |
+ if err != nil || exitCode != 0 {
|
|
| 495 |
+ t.Fatalf("build should have worked: %s %s", err, out)
|
|
| 496 |
+ } |
|
| 497 |
+ deleteImages("ignoredinaccessible")
|
|
| 498 |
+ |
|
| 499 |
+ } |
|
| 478 | 500 |
deleteImages("inaccessiblefiles")
|
| 479 | 501 |
logDone("build - ADD from context with inaccessible files must fail")
|
| 480 | 502 |
logDone("build - ADD from context with accessible links must work")
|
| 503 |
+ logDone("build - ADD from context with ignored inaccessible files must work")
|
|
| 481 | 504 |
} |
| 482 | 505 |
|
| 483 | 506 |
func TestBuildForceRm(t *testing.T) {
|
| ... | ... |
@@ -684,16 +684,27 @@ func TreeSize(dir string) (size int64, err error) {
|
| 684 | 684 |
// ValidateContextDirectory checks if all the contents of the directory |
| 685 | 685 |
// can be read and returns an error if some files can't be read |
| 686 | 686 |
// symlinks which point to non-existing files don't trigger an error |
| 687 |
-func ValidateContextDirectory(srcPath string) error {
|
|
| 687 |
+func ValidateContextDirectory(srcPath string, excludes []string) error {
|
|
| 688 | 688 |
var finalError error |
| 689 | 689 |
|
| 690 | 690 |
filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
|
| 691 | 691 |
// skip this directory/file if it's not in the path, it won't get added to the context |
| 692 |
- _, err = filepath.Rel(srcPath, filePath) |
|
| 692 |
+ relFilePath, err := filepath.Rel(srcPath, filePath) |
|
| 693 | 693 |
if err != nil && os.IsPermission(err) {
|
| 694 | 694 |
return nil |
| 695 | 695 |
} |
| 696 | 696 |
|
| 697 |
+ skip, err := Matches(relFilePath, excludes) |
|
| 698 |
+ if err != nil {
|
|
| 699 |
+ finalError = err |
|
| 700 |
+ } |
|
| 701 |
+ if skip {
|
|
| 702 |
+ if f.IsDir() {
|
|
| 703 |
+ return filepath.SkipDir |
|
| 704 |
+ } |
|
| 705 |
+ return nil |
|
| 706 |
+ } |
|
| 707 |
+ |
|
| 697 | 708 |
if _, err := os.Stat(filePath); err != nil && os.IsPermission(err) {
|
| 698 | 709 |
finalError = fmt.Errorf("can't stat '%s'", filePath)
|
| 699 | 710 |
return err |
| ... | ... |
@@ -726,3 +737,23 @@ func StringsContainsNoCase(slice []string, s string) bool {
|
| 726 | 726 |
} |
| 727 | 727 |
return false |
| 728 | 728 |
} |
| 729 |
+ |
|
| 730 |
+// Matches returns true if relFilePath matches any of the patterns |
|
| 731 |
+func Matches(relFilePath string, patterns []string) (bool, error) {
|
|
| 732 |
+ for _, exclude := range patterns {
|
|
| 733 |
+ matched, err := filepath.Match(exclude, relFilePath) |
|
| 734 |
+ if err != nil {
|
|
| 735 |
+ Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
|
|
| 736 |
+ return false, err |
|
| 737 |
+ } |
|
| 738 |
+ if matched {
|
|
| 739 |
+ if filepath.Clean(relFilePath) == "." {
|
|
| 740 |
+ Errorf("Can't exclude whole path, excluding pattern: %s", exclude)
|
|
| 741 |
+ continue |
|
| 742 |
+ } |
|
| 743 |
+ Debugf("Skipping excluded path: %s", relFilePath)
|
|
| 744 |
+ return true, nil |
|
| 745 |
+ } |
|
| 746 |
+ } |
|
| 747 |
+ return false, nil |
|
| 748 |
+} |