diff --git a/archive/archive.go b/archive/archive.go index ec45d85..3556652 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -30,11 +30,11 @@ type ( ArchiveReader io.Reader Compression int TarOptions struct { - Includes []string - Excludes []string - Compression Compression - NoLchown bool - Name string + IncludeFiles []string + ExcludePatterns []string + Compression Compression + NoLchown bool + Name string } // Archiver allows the reuse of most utility functions of this package @@ -378,7 +378,7 @@ func escapeName(name string) string { } // TarWithOptions creates an archive from the directory at `path`, only including files whose relative -// paths are included in `options.Includes` (if non-nil) or not in `options.Excludes`. +// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { pipeReader, pipeWriter := io.Pipe() @@ -401,12 +401,14 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) // mutating the filesystem and we can see transient errors // from this - if options.Includes == nil { - options.Includes = []string{"."} + if options.IncludeFiles == nil { + options.IncludeFiles = []string{"."} } + seen := make(map[string]bool) + var renamedRelFilePath string // For when tar.Options.Name is set - for _, include := range options.Includes { + for _, include := range options.IncludeFiles { filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error { if err != nil { log.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err) @@ -420,10 +422,19 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) return nil } - skip, err := fileutils.Matches(relFilePath, options.Excludes) - if err != nil { - log.Debugf("Error matching %s", relFilePath, err) - return err + skip := false + + // If "include" is an exact match for the current file + // then even if there's an "excludePatterns" pattern that + // matches it, don't skip it. IOW, assume an explicit 'include' + // is asking for that file no matter what - which is true + // for some files, like .dockerignore and Dockerfile (sometimes) + if include != relFilePath { + skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns) + if err != nil { + log.Debugf("Error matching %s", relFilePath, err) + return err + } } if skip { @@ -433,6 +444,11 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) return nil } + if seen[relFilePath] { + return nil + } + seen[relFilePath] = true + // Rename the base resource if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) { renamedRelFilePath = relFilePath @@ -487,7 +503,7 @@ loop: // This keeps "../" as-is, but normalizes "/../" to "/" hdr.Name = filepath.Clean(hdr.Name) - for _, exclude := range options.Excludes { + for _, exclude := range options.ExcludePatterns { if strings.HasPrefix(hdr.Name, exclude) { continue loop } @@ -563,8 +579,8 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { if options == nil { options = &TarOptions{} } - if options.Excludes == nil { - options.Excludes = []string{} + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} } decompressedArchive, err := DecompressStream(archive) if err != nil { diff --git a/archive/archive_test.go b/archive/archive_test.go index fdba6fb..6cd95d5 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -165,8 +165,8 @@ func TestTarUntar(t *testing.T) { Gzip, } { changes, err := tarUntar(t, origin, &TarOptions{ - Compression: c, - Excludes: []string{"3"}, + Compression: c, + ExcludePatterns: []string{"3"}, }) if err != nil { @@ -196,8 +196,8 @@ func TestTarWithOptions(t *testing.T) { opts *TarOptions numChanges int }{ - {&TarOptions{Includes: []string{"1"}}, 1}, - {&TarOptions{Excludes: []string{"2"}}, 1}, + {&TarOptions{IncludeFiles: []string{"1"}}, 1}, + {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, } for _, testCase := range cases { changes, err := tarUntar(t, origin, testCase.opts) diff --git a/chrootarchive/archive.go b/chrootarchive/archive.go index 66f8373..ae15a2a 100644 --- a/chrootarchive/archive.go +++ b/chrootarchive/archive.go @@ -50,8 +50,8 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error if options == nil { options = &archive.TarOptions{} } - if options.Excludes == nil { - options.Excludes = []string{} + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} } var ( diff --git a/chrootarchive/archive_test.go b/chrootarchive/archive_test.go index bb8a22d..b3f7d57 100644 --- a/chrootarchive/archive_test.go +++ b/chrootarchive/archive_test.go @@ -40,7 +40,7 @@ func TestChrootTarUntar(t *testing.T) { if err := os.MkdirAll(dest, 0700); err != nil { t.Fatal(err) } - if err := Untar(stream, dest, &archive.TarOptions{Excludes: []string{"lolo"}}); err != nil { + if err := Untar(stream, dest, &archive.TarOptions{ExcludePatterns: []string{"lolo"}}); err != nil { t.Fatal(err) } } diff --git a/tarsum/builder_context.go b/tarsum/builder_context.go new file mode 100644 index 0000000..06a4282 --- /dev/null +++ b/tarsum/builder_context.go @@ -0,0 +1,20 @@ +package tarsum + +// This interface extends TarSum by adding the Remove method. In general +// there was concern about adding this method to TarSum itself so instead +// it is being added just to "BuilderContext" which will then only be used +// during the .dockerignore file processing - see builder/evaluator.go +type BuilderContext interface { + TarSum + Remove(string) +} + +func (bc *tarSum) Remove(filename string) { + for i, fis := range bc.sums { + if fis.Name() == filename { + bc.sums = append(bc.sums[:i], bc.sums[i+1:]...) + // Note, we don't just return because there could be + // more than one with this name + } + } +}