added goals; refactored to filetree

This commit is contained in:
Alex Goodman 2018-05-24 21:22:52 -04:00
parent 2a4e6b7bfd
commit 9350984354
No known key found for this signature in database
GPG Key ID: 05328C611D8A520E
6 changed files with 284 additions and 291 deletions

View File

@ -2,4 +2,13 @@
```
docker build -t die-test:latest .
go run main.go
```
```
# TODO:
[x] Extract docker layers from api
[x] Represent layers as generic tree
[x] Stack ordere tree list together as fake unionfs tree
[ ] Diff trees
[ ] Add ui for browsing layers
[ ] Add ui for diffing stack to layer

View File

@ -1,136 +0,0 @@
package main
import (
"errors"
"strings"
"fmt"
)
type FileTree interface {
AddPath(string, interface{}) *Node
RemovePath(string) error
Visit(Visiter) error
// Diff(*Tree) error
Stack(*Tree) (Tree, error)
}
type Visiter func(*Node) error
func (tree *Tree) Visit(visiter Visiter) error {
return tree.root.Visit(visiter)
}
func (node *Node) Visit(visiter Visiter) error {
for _, child := range node.children {
err := child.Visit(visiter)
if err != nil {
return err
}
}
return visiter(node)
}
func (node *Node) IsWhiteout() bool {
return strings.HasPrefix(node.name, ".wh.")
}
func (node *Node) Path() string {
path := []string{}
curNode := node
for {
if curNode.parent == nil{
break
}
path = append([]string{curNode.name}, path...)
curNode = curNode.parent
}
return "/" + strings.Join(path, "/")
}
func (node *Node) WhiteoutPath() string {
path := []string{}
curNode := node
for {
if curNode.parent == nil{
break
}
name := curNode.name
if curNode == node {
name = strings.TrimPrefix(name, ".wh.")
}
path = append([]string{name}, path...)
curNode = curNode.parent
}
return "/" + strings.Join(path, "/")
}
func (tree *Tree) Stack(upper *Tree) (error) {
graft := func(node *Node) error {
if node.IsWhiteout() {
err := tree.RemovePath(node.WhiteoutPath())
if err != nil {
return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
}
} else {
newNode, err := tree.AddPath(node.Path(), node.data)
if err != nil {
return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error())
}
}
return nil
}
return upper.Visit(graft)
}
func (tree *Tree) GetNode(path string) (*Node, error) {
nodeNames := strings.Split(path, "/")
node := tree.Root()
for _, name := range nodeNames {
if name == "" {
continue
}
if node.children[name] == nil {
return nil, errors.New("Path does not exist")
}
node = node.children[name]
}
return node, nil
}
func (tree *Tree) AddPath(path string, data interface{}) (*Node, error) {
nodeNames := strings.Split(path, "/")
node := tree.Root()
for idx, name := range nodeNames {
if name == "" {
continue
}
// find or create node
if node.children[name] != nil {
node = node.children[name]
} else {
// don't attach the payload. The payload is destined for the
// path's end node, not any intermediary node.
node = node.AddChild(name, nil)
}
// attach payload to the last specified node
if idx == len(nodeNames)-1 {
node.data = data
}
}
return node, nil
}
func (tree *Tree) RemovePath(path string) error {
node, err := tree.GetNode(path)
if err != nil {
return err
}
return node.Remove()
}

View File

@ -1,141 +0,0 @@
package main
import "testing"
func TestAddPath(t *testing.T) {
tree := NewTree()
tree.AddPath("/etc/nginx/nginx.conf", 1)
tree.AddPath("/etc/nginx/public", 2)
tree.AddPath("/var/run/systemd", 3)
tree.AddPath("/var/run/bashful", 4)
tree.AddPath("/tmp", 5)
tree.AddPath("/tmp/nonsense", 6)
expected := `.
etc
nginx
nginx.conf
public
tmp
nonsense
var
run
bashful
systemd
`
actual := tree.String()
if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
}
}
func TestRemovePath(t *testing.T) {
tree := NewTree()
tree.AddPath("/etc/nginx/nginx.conf", 1)
tree.AddPath("/etc/nginx/public", 2)
tree.AddPath("/var/run/systemd", 3)
tree.AddPath("/var/run/bashful", 4)
tree.AddPath("/tmp", 5)
tree.AddPath("/tmp/nonsense", 6)
tree.RemovePath("/var/run/bashful")
tree.RemovePath("/tmp")
expected := `.
etc
nginx
nginx.conf
public
var
run
systemd
`
actual := tree.String()
if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
}
}
func TestPath(t *testing.T) {
expected := "/etc/nginx/nginx.conf"
tree := NewTree()
node, _ := tree.AddPath(expected, nil)
actual := node.Path()
if expected != actual {
t.Errorf("Expected path '%s' got '%s'", expected, actual)
}
}
func TestIsWhiteout(t *testing.T) {
tree1 := NewTree()
p1, _ := tree1.AddPath("/etc/nginx/public1", 2)
p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", 2)
if p1.IsWhiteout() != false {
t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name)
}
if p2.IsWhiteout() != true {
t.Errorf("Expected path '%s' to be a whiteout file", p2.name)
}
}
func TestStack(t *testing.T) {
payloadKey := "/var/run/systemd"
payloadValue := 1263487
tree1 := NewTree()
tree1.AddPath("/etc/nginx/public", 2)
tree1.AddPath(payloadKey, 3)
tree1.AddPath("/var/run/bashful", 4)
tree1.AddPath("/tmp", 5)
tree1.AddPath("/tmp/nonsense", 6)
tree2 := NewTree()
// add new files
tree2.AddPath("/etc/nginx/nginx.conf", 1)
// modify current files
tree2.AddPath(payloadKey, payloadValue)
// whiteout the following files
tree2.AddPath("/var/run/.wh.bashful", nil)
tree2.AddPath("/.wh.tmp", nil)
err := tree1.Stack(tree2)
if err != nil {
t.Errorf("Could not stack trees: %v", err)
}
expected := `.
etc
nginx
nginx.conf
public
var
run
systemd
`
node, err := tree1.GetNode(payloadKey)
if err != nil {
t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey)
}
if node.data != payloadValue {
t.Errorf("Expected '%s' value to be %d but got %d", payloadKey, payloadValue, node.data.(int))
}
actual := tree1.String()
if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
}
}

View File

@ -28,7 +28,7 @@ func main() {
tarReader := tar.NewReader(f)
targetName := "manifest.json"
var m Manifest
var trees []*Tree
var trees []*FileTree
for {
header, err := tarReader.Next()

146
tree.go
View File

@ -2,32 +2,45 @@ package main
import (
"sort"
"strings"
"fmt"
"errors"
)
const (
newLine = "\n"
emptySpace = " "
middleItem = "├── "
continueItem = "│ "
lastItem = "└── "
newLine = "\n"
emptySpace = " "
middleItem = "├── "
continueItem = "│ "
lastItem = "└── "
whiteoutPrefix = ".wh."
)
type Tree struct {
//type FileTree interface {
// AddPath(string, interface{}) *Node
// RemovePath(string) error
// Visit(Visiter) error
// // Diff(*FileTree) error
// Stack(*FileTree) (FileTree, error)
//}
type FileTree struct {
root *Node
size int
name string
}
type Node struct {
tree *Tree
tree *FileTree
parent *Node
name string
data interface{}
children map[string]*Node
}
func NewTree() (tree *Tree) {
tree = new(Tree)
func NewTree() (tree *FileTree) {
tree = new(FileTree)
tree.size = 0
tree.root = new(Node)
tree.root.tree = tree
@ -45,7 +58,7 @@ func NewNode(parent *Node, name string, data interface{}) (node *Node) {
return node
}
func (tree *Tree) Root() *Node {
func (tree *FileTree) Root() *Node {
return tree.root
}
@ -74,7 +87,7 @@ func (node *Node) String() string {
return node.name
}
func (tree *Tree) String() string {
func (tree *FileTree) String() string {
var renderLine func(string, []bool, bool) string
var walkTree func(*Node, []bool) string
@ -129,7 +142,7 @@ func (node *Node) Copy() *Node {
return newNode
}
func (tree *Tree) Copy() *Tree {
func (tree *FileTree) Copy() *FileTree {
newTree := NewTree()
*newTree = *tree
newTree.root = tree.Root().Copy()
@ -137,3 +150,112 @@ func (tree *Tree) Copy() *Tree {
return newTree
}
type Visiter func(*Node) error
func (tree *FileTree) Visit(visiter Visiter) error {
return tree.root.Visit(visiter)
}
func (node *Node) Visit(visiter Visiter) error {
for _, child := range node.children {
err := child.Visit(visiter)
if err != nil {
return err
}
}
return visiter(node)
}
func (node *Node) IsWhiteout() bool {
return strings.HasPrefix(node.name, whiteoutPrefix)
}
func (node *Node) Path() string {
path := []string{}
curNode := node
for {
if curNode.parent == nil{
break
}
name := curNode.name
if curNode == node {
// white out prefixes are fictitious on leaf nodes
name = strings.TrimPrefix(name, whiteoutPrefix)
}
path = append([]string{name}, path...)
curNode = curNode.parent
}
return "/" + strings.Join(path, "/")
}
func (tree *FileTree) Stack(upper *FileTree) (error) {
graft := func(node *Node) error {
if node.IsWhiteout() {
err := tree.RemovePath(node.Path())
if err != nil {
return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
}
} else {
newNode, err := tree.AddPath(node.Path(), node.data)
if err != nil {
return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error())
}
}
return nil
}
return upper.Visit(graft)
}
func (tree *FileTree) GetNode(path string) (*Node, error) {
nodeNames := strings.Split(path, "/")
node := tree.Root()
for _, name := range nodeNames {
if name == "" {
continue
}
if node.children[name] == nil {
return nil, errors.New("Path does not exist")
}
node = node.children[name]
}
return node, nil
}
func (tree *FileTree) AddPath(path string, data interface{}) (*Node, error) {
nodeNames := strings.Split(path, "/")
node := tree.Root()
for idx, name := range nodeNames {
if name == "" {
continue
}
// find or create node
if node.children[name] != nil {
node = node.children[name]
} else {
// don't attach the payload. The payload is destined for the
// path's end node, not any intermediary node.
node = node.AddChild(name, nil)
}
// attach payload to the last specified node
if idx == len(nodeNames)-1 {
node.data = data
}
}
return node, nil
}
func (tree *FileTree) RemovePath(path string) error {
node, err := tree.GetNode(path)
if err != nil {
return err
}
return node.Remove()
}

View File

@ -95,3 +95,142 @@ func TestPrintTree(t *testing.T) {
}
}
func TestAddPath(t *testing.T) {
tree := NewTree()
tree.AddPath("/etc/nginx/nginx.conf", 1)
tree.AddPath("/etc/nginx/public", 2)
tree.AddPath("/var/run/systemd", 3)
tree.AddPath("/var/run/bashful", 4)
tree.AddPath("/tmp", 5)
tree.AddPath("/tmp/nonsense", 6)
expected := `.
etc
nginx
nginx.conf
public
tmp
nonsense
var
run
bashful
systemd
`
actual := tree.String()
if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
}
}
func TestRemovePath(t *testing.T) {
tree := NewTree()
tree.AddPath("/etc/nginx/nginx.conf", 1)
tree.AddPath("/etc/nginx/public", 2)
tree.AddPath("/var/run/systemd", 3)
tree.AddPath("/var/run/bashful", 4)
tree.AddPath("/tmp", 5)
tree.AddPath("/tmp/nonsense", 6)
tree.RemovePath("/var/run/bashful")
tree.RemovePath("/tmp")
expected := `.
etc
nginx
nginx.conf
public
var
run
systemd
`
actual := tree.String()
if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
}
}
func TestPath(t *testing.T) {
expected := "/etc/nginx/nginx.conf"
tree := NewTree()
node, _ := tree.AddPath(expected, nil)
actual := node.Path()
if expected != actual {
t.Errorf("Expected path '%s' got '%s'", expected, actual)
}
}
func TestIsWhiteout(t *testing.T) {
tree1 := NewTree()
p1, _ := tree1.AddPath("/etc/nginx/public1", 2)
p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", 2)
if p1.IsWhiteout() != false {
t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name)
}
if p2.IsWhiteout() != true {
t.Errorf("Expected path '%s' to be a whiteout file", p2.name)
}
}
func TestStack(t *testing.T) {
payloadKey := "/var/run/systemd"
payloadValue := 1263487
tree1 := NewTree()
tree1.AddPath("/etc/nginx/public", 2)
tree1.AddPath(payloadKey, 3)
tree1.AddPath("/var/run/bashful", 4)
tree1.AddPath("/tmp", 5)
tree1.AddPath("/tmp/nonsense", 6)
tree2 := NewTree()
// add new files
tree2.AddPath("/etc/nginx/nginx.conf", 1)
// modify current files
tree2.AddPath(payloadKey, payloadValue)
// whiteout the following files
tree2.AddPath("/var/run/.wh.bashful", nil)
tree2.AddPath("/.wh.tmp", nil)
err := tree1.Stack(tree2)
if err != nil {
t.Errorf("Could not stack trees: %v", err)
}
expected := `.
etc
nginx
nginx.conf
public
var
run
systemd
`
node, err := tree1.GetNode(payloadKey)
if err != nil {
t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey)
}
if node.data != payloadValue {
t.Errorf("Expected '%s' value to be %d but got %d", payloadKey, payloadValue, node.data.(int))
}
actual := tree1.String()
if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
}
}