Make sure to snapshot parent directories of specific files for add/copy

This commit is contained in:
Priya Wadhwa 2018-04-24 14:56:30 -07:00
parent 844d9ef0d9
commit a211c1ec71
No known key found for this signature in database
GPG Key ID: 0D0DAFD8F7AA73AE
5 changed files with 103 additions and 40 deletions

View File

@ -17,6 +17,7 @@ limitations under the License.
package commands package commands
import ( import (
"github.com/GoogleContainerTools/kaniko/pkg/constants"
"github.com/GoogleContainerTools/kaniko/pkg/util" "github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
"github.com/docker/docker/builder/dockerfile/instructions" "github.com/docker/docker/builder/dockerfile/instructions"
@ -57,11 +58,19 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error {
if err != nil { if err != nil {
return err return err
} }
destPath, err := util.DestinationFilepath(src, dest, config.WorkingDir) cwd := config.WorkingDir
if cwd == "" {
cwd = constants.RootDir
}
destPath, err := util.DestinationFilepath(src, dest, cwd)
if err != nil { if err != nil {
return err return err
} }
if fi.IsDir() { if fi.IsDir() {
if !filepath.IsAbs(dest) {
// we need to add '/' to the end to indicate the destination is a directory
dest = filepath.Join(cwd, dest) + "/"
}
if err := util.CopyDir(fullPath, dest); err != nil { if err := util.CopyDir(fullPath, dest); err != nil {
return err return err
} }

View File

@ -19,10 +19,8 @@ package snapshot
import ( import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"github.com/GoogleContainerTools/kaniko/pkg/util" "github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -52,12 +50,14 @@ func (s *Snapshotter) Init() error {
// TakeSnapshot takes a snapshot of the filesystem, avoiding directories in the whitelist, and creates // TakeSnapshot takes a snapshot of the filesystem, avoiding directories in the whitelist, and creates
// a tarball of the changed files. Return contents of the tarball, and whether or not any files were changed // a tarball of the changed files. Return contents of the tarball, and whether or not any files were changed
func (s *Snapshotter) TakeSnapshot(files []string) ([]byte, error) { func (s *Snapshotter) TakeSnapshot(files []string) ([]byte, error) {
if files != nil {
return s.TakeSnapshotOfFiles(files)
}
logrus.Info("Taking snapshot of full filesystem...")
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
filesAdded, err := s.snapShotFS(buf) var filesAdded bool
var err error
if files == nil {
filesAdded, err = s.snapShotFS(buf)
} else {
filesAdded, err = s.snapshotFiles(buf, files)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -68,45 +68,57 @@ func (s *Snapshotter) TakeSnapshot(files []string) ([]byte, error) {
return contents, err return contents, err
} }
// TakeSnapshotOfFiles takes a snapshot of specific files // snapshotFiles takes a snapshot of specific files
// Used for ADD/COPY commands, when we know which files have changed // Used for ADD/COPY commands, when we know which files have changed
func (s *Snapshotter) TakeSnapshotOfFiles(files []string) ([]byte, error) { func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) {
logrus.Infof("Taking snapshot of files %v...", files) s.hardlinks = map[uint64]string{}
s.l.Snapshot() s.l.Snapshot()
if len(files) == 0 { if len(files) == 0 {
logrus.Info("No files changed in this command, skipping snapshotting.") logrus.Info("No files changed in this command, skipping snapshotting.")
return nil, nil return false, nil
} }
buf := bytes.NewBuffer([]byte{}) logrus.Infof("Taking snapshot of files %v...", files)
w := tar.NewWriter(buf) snapshottedFiles := make(map[string]bool)
defer w.Close()
filesAdded := false
for _, file := range files { for _, file := range files {
info, err := os.Lstat(file) parentDirs := util.ParentDirectories(file)
if err != nil { files = append(parentDirs, files...)
return nil, err }
filesAdded := false
w := tar.NewWriter(f)
defer w.Close()
// Now create the tar.
for _, file := range files {
file = filepath.Clean(file)
if val, ok := snapshottedFiles[file]; ok && val {
continue
} }
if util.PathInWhitelist(file, s.directory) { if util.PathInWhitelist(file, s.directory) {
logrus.Debugf("Not adding %s to layer, as it is whitelisted", file) logrus.Debugf("Not adding %s to layer, as it's whitelisted", file)
continue continue
} }
snapshottedFiles[file] = true
info, err := os.Lstat(file)
if err != nil {
return false, err
}
// Only add to the tar if we add it to the layeredmap. // Only add to the tar if we add it to the layeredmap.
maybeAdd, err := s.l.MaybeAdd(file) maybeAdd, err := s.l.MaybeAdd(file)
if err != nil { if err != nil {
return nil, err return false, err
} }
if maybeAdd { if maybeAdd {
filesAdded = true filesAdded = true
util.AddToTar(file, info, s.hardlinks, w) if err := util.AddToTar(file, info, s.hardlinks, w); err != nil {
return false, err
} }
} }
if !filesAdded {
return nil, nil
} }
return ioutil.ReadAll(buf) return filesAdded, nil
} }
func (s *Snapshotter) snapShotFS(f io.Writer) (bool, error) { func (s *Snapshotter) snapShotFS(f io.Writer) (bool, error) {
logrus.Info("Taking snapshot of full filesystem...")
s.hardlinks = map[uint64]string{} s.hardlinks = map[uint64]string{}
s.l.Snapshot() s.l.Snapshot()
existingPaths := s.l.GetFlattenedPathsForWhiteOut() existingPaths := s.l.GetFlattenedPathsForWhiteOut()

View File

@ -149,30 +149,23 @@ func TestSnapshotFiles(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectedContents := map[string]string{ expectedFiles := []string{"/tmp", filepath.Join(testDir, "foo")}
filepath.Join(testDir, "foo"): "newbaz1",
}
// Check contents of the snapshot, make sure contents is equivalent to snapshotFiles // Check contents of the snapshot, make sure contents is equivalent to snapshotFiles
reader := bytes.NewReader(contents) reader := bytes.NewReader(contents)
tr := tar.NewReader(reader) tr := tar.NewReader(reader)
numFiles := 0 var actualFiles []string
for { for {
hdr, err := tr.Next() hdr, err := tr.Next()
if err == io.EOF { if err == io.EOF {
break break
} }
numFiles = numFiles + 1 if err != nil {
if _, isFile := expectedContents[hdr.Name]; !isFile { t.Fatal(err)
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
} }
contents, _ := ioutil.ReadAll(tr) actualFiles = append(actualFiles, hdr.Name)
if string(contents) != expectedContents[hdr.Name] {
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, expectedContents[hdr.Name], string(contents))
}
}
if numFiles != 1 {
t.Fatalf("%s was not added.", filepath.Join(testDir, "foo"))
} }
testutil.CheckErrorAndDeepEqual(t, false, nil, expectedFiles, actualFiles)
} }
func TestEmptySnapshot(t *testing.T) { func TestEmptySnapshot(t *testing.T) {

View File

@ -141,6 +141,23 @@ func Files(root string) ([]string, error) {
return files, err return files, err
} }
// ParentDirectories returns a list of paths to all parent directories
// Ex. /some/temp/dir -> [/some, /some/temp, /some/temp/dir]
func ParentDirectories(path string) []string {
path = filepath.Clean(path)
dirs := strings.Split(path, "/")
dirPath := constants.RootDir
var paths []string
for _, dir := range dirs {
if dir == "" {
continue
}
dirPath = filepath.Join(dirPath, dir)
paths = append(paths, dirPath)
}
return paths
}
// FilepathExists returns true if the path exists // FilepathExists returns true if the path exists
func FilepathExists(path string) bool { func FilepathExists(path string) bool {
_, err := os.Lstat(path) _, err := os.Lstat(path)

View File

@ -131,3 +131,35 @@ func Test_RelativeFiles(t *testing.T) {
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles)
} }
} }
func Test_ParentDirectories(t *testing.T) {
tests := []struct {
name string
path string
expected []string
}{
{
name: "regular path",
path: "/path/to/dir",
expected: []string{
"/path",
"/path/to",
"/path/to/dir",
},
},
{
name: "current directory",
path: ".",
expected: []string{
"/",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := ParentDirectories(tt.path)
testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual)
})
}
}