Bladeren bron

Filesystem: Changes API

Andrea Luzzardi 12 jaren geleden
bovenliggende
commit
09502e4faa
2 gewijzigde bestanden met toevoegingen van 197 en 4 verwijderingen
  1. 87 0
      filesystem.go
  2. 110 4
      filesystem_test.go

+ 87 - 0
filesystem.go

@@ -4,6 +4,8 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path/filepath"
+	"strings"
 	"syscall"
 )
 
@@ -65,6 +67,91 @@ func (fs *Filesystem) IsMounted() bool {
 	return false
 }
 
+type ChangeType int
+
+const (
+	ChangeModify = iota
+	ChangeAdd
+	ChangeDelete
+)
+
+type Change struct {
+	Path string
+	Kind ChangeType
+}
+
+func (fs *Filesystem) Changes() ([]Change, error) {
+	var changes []Change
+	err := filepath.Walk(fs.RWPath, func(path string, f os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		// Rebase path
+		path, err = filepath.Rel(fs.RWPath, path)
+		if err != nil {
+			return err
+		}
+		path = filepath.Join("/", path)
+
+		// Skip root
+		if path == "/" {
+			return nil
+		}
+
+		// Skip AUFS metadata
+		if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
+			return err
+		}
+
+		change := Change{
+			Path: path,
+		}
+
+		// Find out what kind of modification happened
+		file := filepath.Base(path)
+		// If there is a whiteout, then the file was removed
+		if strings.HasPrefix(file, ".wh.") {
+			originalFile := strings.TrimLeft(file, ".wh.")
+			change.Path = filepath.Join(filepath.Dir(path), originalFile)
+			change.Kind = ChangeDelete
+		} else {
+			// Otherwise, the file was added
+			change.Kind = ChangeAdd
+
+			// ...Unless it already existed in a top layer, in which case, it's a modification
+			for _, layer := range fs.Layers {
+				stat, err := os.Stat(filepath.Join(layer, path))
+				if err != nil && !os.IsNotExist(err) {
+					return err
+				}
+				if err == nil {
+					// The file existed in the top layer, so that's a modification
+
+					// However, if it's a directory, maybe it wasn't actually modified.
+					// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
+					if stat.IsDir() && f.IsDir() {
+						if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
+							// Both directories are the same, don't record the change
+							return nil
+						}
+					}
+					change.Kind = ChangeModify
+					break
+				}
+			}
+		}
+
+		// Record change
+		changes = append(changes, change)
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	return changes, nil
+}
+
 func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {
 	return &Filesystem{
 		RootFS: rootfs,

+ 110 - 4
filesystem_test.go

@@ -8,12 +8,12 @@ import (
 	"testing"
 )
 
-func newTestFilesystem(t *testing.T, layers []string) (rootfs string, rwpath string, fs *Filesystem) {
+func newTestFilesystem(t *testing.T, layers []string) (rootfs string, fs *Filesystem) {
 	rootfs, err := ioutil.TempDir("", "docker-test-root")
 	if err != nil {
 		t.Fatal(err)
 	}
-	rwpath, err = ioutil.TempDir("", "docker-test-rw")
+	rwpath, err := ioutil.TempDir("", "docker-test-rw")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -22,7 +22,7 @@ func newTestFilesystem(t *testing.T, layers []string) (rootfs string, rwpath str
 }
 
 func TestFilesystem(t *testing.T) {
-	_, _, filesystem := newTestFilesystem(t, []string{"/var/lib/docker/images/ubuntu"})
+	_, filesystem := newTestFilesystem(t, []string{"/var/lib/docker/images/ubuntu"})
 	if err := filesystem.Umount(); err == nil {
 		t.Errorf("Umount succeeded even though the filesystem was not mounted")
 	}
@@ -56,7 +56,7 @@ func TestFilesystemMultiLayer(t *testing.T) {
 	}
 
 	// Create the layered filesystem and add our fake layer on top
-	rootfs, _, filesystem := newTestFilesystem(t, []string{"/var/lib/docker/images/ubuntu", fakeLayer})
+	rootfs, filesystem := newTestFilesystem(t, []string{"/var/lib/docker/images/ubuntu", fakeLayer})
 
 	// Mount it
 	if err := filesystem.Mount(); err != nil {
@@ -80,3 +80,109 @@ func TestFilesystemMultiLayer(t *testing.T) {
 		t.Error(string(fsdata))
 	}
 }
+
+func TestChanges(t *testing.T) {
+	rootfs, filesystem := newTestFilesystem(t, []string{"/var/lib/docker/images/ubuntu"})
+	// Mount it
+	if err := filesystem.Mount(); err != nil {
+		t.Fatal(err)
+	}
+	defer filesystem.Umount()
+
+	var changes []Change
+	var err error
+
+	// Test without changes
+	changes, err = filesystem.Changes()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(changes) != 0 {
+		t.Errorf("Unexpected changes :%v", changes)
+	}
+
+	// Test simple change
+	file, err := os.Create(path.Join(rootfs, "test_change"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	file.Close()
+
+	changes, err = filesystem.Changes()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(changes) != 1 {
+		t.Errorf("Unexpected changes :%v", changes)
+	}
+	if changes[0].Path != "/test_change" || changes[0].Kind != ChangeAdd {
+		t.Errorf("Unexpected changes :%v", changes)
+	}
+
+	// Test subdirectory change
+	if err := os.Mkdir(path.Join(rootfs, "sub_change"), 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	file, err = os.Create(path.Join(rootfs, "sub_change", "test"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	file.Close()
+
+	changes, err = filesystem.Changes()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(changes) != 3 {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+	if changes[0].Path != "/sub_change" || changes[0].Kind != ChangeAdd || changes[1].Path != "/sub_change/test" || changes[1].Kind != ChangeAdd {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+
+	// Test permission change
+	if err := os.Chmod(path.Join(rootfs, "root"), 0000); err != nil {
+		t.Fatal(err)
+	}
+	changes, err = filesystem.Changes()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(changes) != 4 {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+	if changes[0].Path != "/root" || changes[0].Kind != ChangeModify {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+
+	// Test removal
+	if err := os.Remove(path.Join(rootfs, "etc", "passwd")); err != nil {
+		t.Fatal(err)
+	}
+	changes, err = filesystem.Changes()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(changes) != 6 {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+	if changes[0].Path != "/etc" || changes[0].Kind != ChangeModify || changes[1].Path != "/etc/passwd" || changes[1].Kind != ChangeDelete {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+
+	// Test sub-directory removal
+	if err := os.Remove(path.Join(rootfs, "usr", "bin", "sudo")); err != nil {
+		t.Fatal(err)
+	}
+	changes, err = filesystem.Changes()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(changes) != 8 {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+	if changes[6].Path != "/usr/bin" || changes[6].Kind != ChangeModify || changes[7].Path != "/usr/bin/sudo" || changes[7].Kind != ChangeDelete {
+		t.Errorf("Unexpected changes: %v", changes)
+	}
+}