summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--cmd/die/main.go31
-rw-r--r--filetree/changeinfo.go24
-rw-r--r--filetree/tree.go9
-rw-r--r--filetree/tree_test.go6
-rw-r--r--image/image.go181
-rw-r--r--image/tar_read.go166
-rw-r--r--ui/filetreeview.go20
-rw-r--r--ui/layerview.go15
-rw-r--r--ui/ui.go18
10 files changed, 218 insertions, 256 deletions
diff --git a/Makefile b/Makefile
index 83b02ea..168a37c 100644
--- a/Makefile
+++ b/Makefile
@@ -3,9 +3,9 @@ BIN = die
all: clean build
run: build
- ./build/$(BIN)
+ ./build/$(BIN) die-test
-build: deps
+build: #deps
go build -o build/$(BIN) ./cmd/...
install: deps
diff --git a/cmd/die/main.go b/cmd/die/main.go
index ab39ead..fa9b019 100644
--- a/cmd/die/main.go
+++ b/cmd/die/main.go
@@ -1,7 +1,11 @@
package main
import (
+ "fmt"
+ "log"
"os"
+
+ "github.com/urfave/cli"
"github.com/wagoodman/docker-image-explorer/image"
"github.com/wagoodman/docker-image-explorer/ui"
)
@@ -11,15 +15,22 @@ const version = "v0.0.0"
const author = "wagoodman"
func main() {
- os.Exit(run(os.Args))
-}
-
+ app := cli.NewApp()
+ app.Name = "die"
+ app.Usage = "Explore your docker images"
+ app.Action = func(c *cli.Context) error {
+ userImage := c.Args().Get(0)
+ if userImage == "" {
+ fmt.Println("No image argument given")
+ os.Exit(1)
+ }
+ manifest, refTrees := image.InitializeData(userImage)
+ ui.Run(manifest, refTrees)
+ return nil
+ }
-func run(args []string) int {
- image.WriteImage()
- manifest, refTrees := image.InitializeData()
-
- ui.Run(manifest, refTrees)
- return 0
+ err := app.Run(os.Args)
+ if err != nil {
+ log.Fatal(err)
+ }
}
-
diff --git a/filetree/changeinfo.go b/filetree/changeinfo.go
index f1b19ba..95a6529 100644
--- a/filetree/changeinfo.go
+++ b/filetree/changeinfo.go
@@ -1,8 +1,11 @@
package filetree
import (
+ "archive/tar"
"bytes"
+ "crypto/md5"
"fmt"
+ "io"
)
type FileChangeInfo struct {
@@ -22,6 +25,27 @@ const (
Removed
)
+func NewFileChangeInfo(reader *tar.Reader, header *tar.Header, path string) FileChangeInfo {
+ if header.Typeflag == tar.TypeDir {
+ return FileChangeInfo{
+ Path: path,
+ Typeflag: header.Typeflag,
+ MD5sum: [16]byte{},
+ }
+ }
+ fileBytes := make([]byte, header.Size)
+ _, err := reader.Read(fileBytes)
+ if err != nil && err != io.EOF {
+ panic(err)
+ }
+ return FileChangeInfo{
+ Path: path,
+ Typeflag: header.Typeflag,
+ MD5sum: md5.Sum(fileBytes),
+ DiffType: Unchanged,
+ }
+}
+
func (d DiffType) String() string {
switch d {
case Unchanged:
diff --git a/filetree/tree.go b/filetree/tree.go
index d1efd1a..090bf4e 100644
--- a/filetree/tree.go
+++ b/filetree/tree.go
@@ -140,6 +140,7 @@ func (tree *FileTree) GetNode(path string) (*FileNode, error) {
}
func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) {
+ // fmt.Printf("ADDPATH: %s %+v\n", path, data)
nodeNames := strings.Split(path, "/")
node := tree.Root
for idx, name := range nodeNames {
@@ -172,7 +173,7 @@ func (tree *FileTree) RemovePath(path string) error {
return node.Remove()
}
-func (tree *FileTree) compare(upper *FileTree) error {
+func (tree *FileTree) Compare(upper *FileTree) error {
graft := func(node *FileNode) error {
if node.IsWhiteout() {
err := tree.MarkRemoved(node.Path())
@@ -183,14 +184,14 @@ func (tree *FileTree) compare(upper *FileTree) error {
existingNode, _ := tree.GetNode(node.Path())
if existingNode == nil {
newNode, err := tree.AddPath(node.Path(), node.Data)
- fmt.Printf("added new node at %s\n", newNode.Path())
+ // fmt.Printf("added new node at %s\n", newNode.Path())
if err != nil {
return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error())
}
newNode.AssignDiffType(Added)
} else {
diffType := existingNode.compare(node)
- fmt.Printf("found existing node at %s\n", existingNode.Path())
+ // fmt.Printf("found existing node at %s\n", existingNode.Path())
existingNode.deriveDiffType(diffType)
}
}
@@ -209,7 +210,7 @@ func (tree *FileTree) MarkRemoved(path string) error {
func StackRange(trees []*FileTree, index uint) *FileTree {
tree := trees[1].Copy()
- for idx := uint(2); idx < index; idx++ {
+ for idx := uint(1); idx <= index; idx++ {
tree.Stack(trees[idx])
}
return tree
diff --git a/filetree/tree_test.go b/filetree/tree_test.go
index 67d6c2e..14cb61b 100644
--- a/filetree/tree_test.go
+++ b/filetree/tree_test.go
@@ -186,7 +186,7 @@ func TestCompareWithNoChanges(t *testing.T) {
lowerTree.AddPath(value, &fakeData)
upperTree.AddPath(value, &fakeData)
}
- lowerTree.compare(upperTree)
+ lowerTree.Compare(upperTree)
asserter := func(n *FileNode) error {
if n.Path() == "/" {
return nil
@@ -232,7 +232,7 @@ func TestCompareWithAdds(t *testing.T) {
upperTree.AddPath(value, &fakeData)
}
- lowerTree.compare(upperTree)
+ lowerTree.Compare(upperTree)
asserter := func(n *FileNode) error {
p := n.Path()
@@ -283,7 +283,7 @@ func TestCompareWithChanges(t *testing.T) {
upperTree.AddPath(value, &fakeData)
}
- lowerTree.compare(upperTree)
+ lowerTree.Compare(upperTree)
asserter := func(n *FileNode) error {
p := n.Path()
if p == "/" {
diff --git a/image/image.go b/image/image.go
index 76617a1..ad6e745 100644
--- a/image/image.go
+++ b/image/image.go
@@ -1,12 +1,19 @@
package image
import (
+ "archive/tar"
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "fmt"
"io"
+ "io/ioutil"
"os"
- "bufio"
+ "path/filepath"
+ "strings"
+
"github.com/docker/docker/client"
- "fmt"
- "encoding/json"
+ "github.com/wagoodman/docker-image-explorer/filetree"
"golang.org/x/net/context"
)
@@ -16,24 +23,112 @@ func check(e error) {
}
}
+type ImageManifest struct {
+ Config string
+ RepoTags []string
+ Layers []string
+}
-func saveImage(readCloser io.ReadCloser) {
- defer readCloser.Close()
+func NewManifest(reader *tar.Reader, header *tar.Header) ImageManifest {
+ size := header.Size
+ manifestBytes := make([]byte, size)
+ _, err := reader.Read(manifestBytes)
+ if err != nil {
+ panic(err)
+ }
+ var m []ImageManifest
+ err = json.Unmarshal(manifestBytes, &m)
+ if err != nil {
+ panic(err)
+ }
+ return m[0]
+}
+
+func InitializeData(imageID string) (*ImageManifest, []*filetree.FileTree) {
+ imageTarPath, tmpDir := saveImage(imageID)
- path := ".image"
- if _, err := os.Stat(path); os.IsNotExist(err) {
- os.Mkdir(path, 0755)
+ f, err := os.Open(imageTarPath)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
}
- fo, err := os.Create(".image/cache.tar")
+ defer f.Close()
+ defer os.RemoveAll(tmpDir)
+
+ tarReader := tar.NewReader(f)
+ targetName := "manifest.json"
+ var manifest ImageManifest
+ var layerMap map[string]*filetree.FileTree
+ layerMap = make(map[string]*filetree.FileTree)
+
+ for {
+ header, err := tarReader.Next()
+
+ if err == io.EOF {
+ break
+ }
+
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ name := header.Name
+ if name == targetName {
+ manifest = NewManifest(tarReader, header)
+ }
+
+ switch header.Typeflag {
+ case tar.TypeDir:
+ continue
+ case tar.TypeReg:
+ if strings.HasSuffix(name, "layer.tar") {
+ tree := filetree.NewFileTree()
+ tree.Name = name
+ fileInfos := getFileList(tarReader, header)
+ for _, element := range fileInfos {
+ tree.AddPath(element.Path, &element)
+ }
+ layerMap[tree.Name] = tree
+ }
+ default:
+ fmt.Printf("ERRG: unknown tar entry: %v: %s\n", header.Typeflag, name)
+ }
+ }
+ var trees []*filetree.FileTree
+ trees = make([]*filetree.FileTree, 0)
+ for _, treeName := range manifest.Layers {
+ trees = append(trees, layerMap[treeName])
+ }
+
+ return &manifest, trees
+}
+
+func saveImage(imageID string) (string, string) {
+ ctx := context.Background()
+ dockerClient, err := client.NewEnvClient()
+ if err != nil {
+ panic(err)
+ }
+
+ readCloser, err := dockerClient.ImageSave(ctx, []string{imageID})
+ check(err)
+ defer readCloser.Close()
+
+ tmpDir, err := ioutil.TempDir("", "docker-image-explorer")
+ check(err)
+
+ imageTarPath := filepath.Join(tmpDir, "image.tar")
+ imageFile, err := os.Create(imageTarPath)
check(err)
defer func() {
- if err := fo.Close(); err != nil {
+ if err := imageFile.Close(); err != nil {
panic(err)
}
}()
- w := bufio.NewWriter(fo)
+ imageWriter := bufio.NewWriter(imageFile)
buf := make([]byte, 1024)
for {
@@ -45,60 +140,50 @@ func saveImage(readCloser io.ReadCloser) {
break
}
- if _, err := w.Write(buf[:n]); err != nil {
+ if _, err := imageWriter.Write(buf[:n]); err != nil {
panic(err)
}
}
- if err = w.Flush(); err != nil {
+ if err = imageWriter.Flush(); err != nil {
panic(err)
}
-}
+ return imageTarPath, tmpDir
+}
-func WriteImage() {
- ctx := context.Background()
- cli, err := client.NewEnvClient()
+func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo {
+ var files []filetree.FileChangeInfo
+ size := h.Size
+ tarredBytes := make([]byte, size)
+ _, err := parentReader.Read(tarredBytes)
if err != nil {
panic(err)
}
-
- // imageID := "golang:alpine"
- imageID := "die-test:latest"
-
- fmt.Println("Saving Image...")
- readCloser, err := cli.ImageSave(ctx, []string{imageID})
- check(err)
- saveImage(readCloser)
-
+ r := bytes.NewReader(tarredBytes)
+ tarReader := tar.NewReader(r)
for {
- inspect, _, err := cli.ImageInspectWithRaw(ctx, imageID)
- check(err)
-
- history, err := cli.ImageHistory(ctx, imageID)
- check(err)
+ header, err := tarReader.Next()
- historyStr, err := json.MarshalIndent(history, "", " ")
- check(err)
-
- layerStr := ""
- for idx, layer := range inspect.RootFS.Layers {
- prefix := "├── "
- if idx == len(inspect.RootFS.Layers)-1 {
- prefix = "└── "
- }
- layerStr += fmt.Sprintf("%s%s\n", prefix, layer)
+ if err == io.EOF {
+ break
}
- fmt.Printf("Image: %s\nId: %s\nParent: %s\nLayers: %d\n%sHistory: %s\n", imageID, inspect.ID, inspect.Parent, len(inspect.RootFS.Layers), layerStr, historyStr)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
- fmt.Println("")
+ name := header.Name
- if inspect.Parent == "" {
- break
- } else {
- imageID = inspect.Parent
+ switch header.Typeflag {
+ case tar.TypeXGlobalHeader:
+ fmt.Printf("ERRG: XGlobalHeader: %v: %s\n", header.Typeflag, name)
+ case tar.TypeXHeader:
+ fmt.Printf("ERRG: XHeader: %v: %s\n", header.Typeflag, name)
+ default:
+ files = append(files, filetree.NewFileChangeInfo(tarReader, header, name))
}
}
- fmt.Println("See './.image' for the cached image tar")
+ return files
}
diff --git a/image/tar_read.go b/image/tar_read.go
deleted file mode 100644
index 7b9cca5..0000000
--- a/image/tar_read.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package image
-
-import (
- "archive/tar"
- "bytes"
- "crypto/md5"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "strings"
-
- "github.com/wagoodman/docker-image-explorer/filetree"
-)
-
-func InitializeData() (*Manifest, []*filetree.FileTree) {
- f, err := os.Open("./.image/cache.tar")
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- defer f.Close()
-
- tarReader := tar.NewReader(f)
- targetName := "manifest.json"
- var manifest Manifest
- var layerMap map[string]*filetree.FileTree
- layerMap = make(map[string]*filetree.FileTree)
-
- for {
- header, err := tarReader.Next()
-
- if err == io.EOF {
- break
- }
-
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-
- name := header.Name
- if name == targetName {
- manifest = handleManifest(tarReader, header)
- }
-
- switch header.Typeflag {
- case tar.TypeDir:
- continue
- case tar.TypeReg:
-
- if strings.HasSuffix(name, "layer.tar") {
- fmt.Println("Containing:")
- tree := filetree.NewFileTree()
- tree.Name = name
- fmt.Printf("%s\n", tree.Name)
- fileInfos := getFileList(tarReader, header)
- for _, element := range fileInfos {
- tree.AddPath(element.Path, &element)
- }
- layerMap[tree.Name] = tree
- }
- default:
- fmt.Printf("%s : %c %s %s\n",
- "hmmm?",
- header.Typeflag,
- "in file",
- name,
- )
- }
- }
- var trees []*filetree.FileTree
- trees = make([]*filetree.FileTree, 0)
- for _, treeName := range manifest.Layers {
- trees = append(trees, layerMap[treeName])
- }
-
- return &manifest, trees
-}
-
-func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo {
- var files []filetree.FileChangeInfo
- size := h.Size
- tarredBytes := make([]byte, size)
- _, err := parentReader.Read(tarredBytes)
- if err != nil {
- panic(err)
- }
- r := bytes.NewReader(tarredBytes)
- tarReader := tar.NewReader(r)
- for {
- header, err := tarReader.Next()
-
- if err == io.EOF {
- break
- }
-
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-
- name := header.Name
-
- switch header.Typeflag {
- case tar.TypeDir:
- files = append(files, makeEntry(tarReader, header, name))
- case tar.TypeReg:
- files = append(files, makeEntry(tarReader, header, name))
- continue
- case tar.TypeSymlink:
- files = append(files, makeEntry(tarReader, header, name))
- default:
- fmt.Printf("%s : %c %s %s\n",
- "hmmm?",
- header.Typeflag,
- "in file",
- name,
- )
- }
- }
- return files
-}
-
-func makeEntry(r *tar.Reader, h *tar.Header, path string) filetree.FileChangeInfo {
- if h.Typeflag == tar.TypeDir {
- return filetree.FileChangeInfo{
- Path: path,
- Typeflag: h.Typeflag,
- MD5sum: [16]byte{},
- }
- }
- fileBytes := make([]byte, h.Size)
- _, err := r.Read(fileBytes)
- if err != nil && err != io.EOF {
- panic(err)
- }
- hash := md5.Sum(fileBytes)
- return filetree.FileChangeInfo{
- Path: path,
- Typeflag: h.Typeflag,
- MD5sum: hash,
- DiffType: filetree.Unchanged,
- }
-}
-
-type Manifest struct {
- Config string
- RepoTags []string
- Layers []string
-}
-
-func handleManifest(r *tar.Reader, header *tar.Header) Manifest {
- size := header.Size
- manifestBytes := make([]byte, size)
- _, err := r.Read(manifestBytes)
- if err != nil {
- panic(err)
- }
- var m [1]Manifest
- err = json.Unmarshal(manifestBytes, &m)
- if err != nil {
- panic(err)
- }
- return m[0]
-}
diff --git a/ui/filetreeview.go b/ui/filetreeview.go
index 138c796..685b1d2 100644
--- a/ui/filetreeview.go
+++ b/ui/filetreeview.go
@@ -7,14 +7,13 @@ import (
"github.com/wagoodman/docker-image-explorer/filetree"
)
-
type FileTreeView struct {
- Name string
- gui *gocui.Gui
- view *gocui.View
+ Name string
+ gui *gocui.Gui
+ view *gocui.View
TreeIndex uint
- Tree *filetree.FileTree
- RefTrees []*filetree.FileTree
+ Tree *filetree.FileTree
+ RefTrees []*filetree.FileTree
}
func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree) (treeview *FileTreeView) {
@@ -55,9 +54,12 @@ func (view *FileTreeView) Setup(v *gocui.View) error {
return nil
}
-// Mehh, this is just a bad method
-func (view *FileTreeView) reset(tree *filetree.FileTree) error {
- view.Tree = tree
+func (view *FileTreeView) setLayer(layerIndex uint) error {
+ view.Tree = filetree.StackRange(view.RefTrees, layerIndex-1)
+ view.Tree.Compare(view.RefTrees[layerIndex])
+ v, _ := view.gui.View("debug")
+ v.Clear()
+ _, _ = fmt.Fprintln(v, view.RefTrees[layerIndex])
view.view.SetCursor(0, 0)
view.TreeIndex = 0
return view.Render()
diff --git a/ui/layerview.go b/ui/layerview.go
index 281e20d..b8dd08e 100644
--- a/ui/layerview.go
+++ b/ui/layerview.go
@@ -5,19 +5,17 @@ import (
"github.com/jroimartin/gocui"
"github.com/wagoodman/docker-image-explorer/image"
- "github.com/wagoodman/docker-image-explorer/filetree"
)
-
type LayerView struct {
Name string
gui *gocui.Gui
view *gocui.View
LayerIndex uint
- Manifest *image.Manifest
+ Manifest *image.ImageManifest
}
-func NewLayerView(name string, gui *gocui.Gui, manifest *image.Manifest) (layerview *LayerView) {
+func NewLayerView(name string, gui *gocui.Gui, manifest *image.ImageManifest) (layerview *LayerView) {
layerview = new(LayerView)
// populate main fields
@@ -38,10 +36,10 @@ func (view *LayerView) Setup(v *gocui.View) error {
view.view.SelFgColor = gocui.ColorBlack
// set keybindings
- if err := view.gui.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
+ if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
return err
}
- if err := view.gui.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil {
+ if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil {
return err
}
@@ -67,8 +65,7 @@ func (view *LayerView) CursorDown() error {
CursorDown(view.gui, view.view)
view.LayerIndex++
view.Render()
- // this line is evil
- Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex))
+ Views.Tree.setLayer(view.LayerIndex)
}
return nil
}
@@ -79,7 +76,7 @@ func (view *LayerView) CursorUp() error {
view.LayerIndex--
view.Render()
// this line is evil
- Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex))
+ Views.Tree.setLayer(view.LayerIndex)
}
return nil
}
diff --git a/ui/ui.go b/ui/ui.go
index 48321ce..bdbc897 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -1,9 +1,10 @@
package ui
import (
+ "log"
+
"github.com/jroimartin/gocui"
"github.com/wagoodman/docker-image-explorer/filetree"
- "log"
"github.com/wagoodman/docker-image-explorer/image"
)
@@ -76,14 +77,15 @@ func keybindings(g *gocui.Gui) error {
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
splitCol := 50
- if view, err := g.SetView("side", -1, -1, splitCol, maxY); err != nil {
+ debugCol := maxX - 100
+ if view, err := g.SetView(Views.Layer.Name, -1, -1, splitCol, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
Views.Layer.Setup(view)
}
- if view, err := g.SetView("main", splitCol, -1, maxX, maxY); err != nil {
+ if view, err := g.SetView(Views.Tree.Name, splitCol, -1, debugCol, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
@@ -94,10 +96,16 @@ func layout(g *gocui.Gui) error {
return err
}
}
+ if _, err := g.SetView("debug", debugCol, -1, maxX, maxY); err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ }
+
return nil
}
-func Run(manifest *image.Manifest, refTrees []*filetree.FileTree) {
+func Run(manifest *image.ImageManifest, refTrees []*filetree.FileTree) {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
@@ -119,4 +127,4 @@ func Run(manifest *image.Manifest, refTrees []*filetree.FileTree) {
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
-} \ No newline at end of file
+}