diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3296f89 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}/cmd/die/main.go", + "externalConsole": true, + "env": { + "TERM": "xterm-256color" + }, + "args": ["die-test"], + "showLog": true + } + ] +} \ No newline at end of file diff --git a/filetree/tree.go b/filetree/tree.go index 090bf4e..6ba0703 100644 --- a/filetree/tree.go +++ b/filetree/tree.go @@ -208,9 +208,10 @@ func (tree *FileTree) MarkRemoved(path string) error { return node.AssignDiffType(Removed) } -func StackRange(trees []*FileTree, index uint) *FileTree { +func StackRange(trees []*FileTree, index int) *FileTree { + // TMP TMP TMP: TODO: later change the index of both of these to 0 tree := trees[1].Copy() - for idx := uint(1); idx <= index; idx++ { + for idx := 1; idx <= index; idx++ { tree.Stack(trees[idx]) } return tree diff --git a/image/image.go b/image/image.go index ad6e745..140f4ba 100644 --- a/image/image.go +++ b/image/image.go @@ -12,7 +12,9 @@ import ( "path/filepath" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/client" + humanize "github.com/dustin/go-humanize" "github.com/wagoodman/docker-image-explorer/filetree" "golang.org/x/net/context" ) @@ -24,9 +26,9 @@ func check(e error) { } type ImageManifest struct { - Config string - RepoTags []string - Layers []string + ConfigPath string `json:"Config"` + RepoTags []string `json:"RepoTags"` + LayerTarPaths []string `json:"Layers"` } func NewManifest(reader *tar.Reader, header *tar.Header) ImageManifest { @@ -44,24 +46,37 @@ func NewManifest(reader *tar.Reader, header *tar.Header) ImageManifest { return m[0] } -func InitializeData(imageID string) (*ImageManifest, []*filetree.FileTree) { - imageTarPath, tmpDir := saveImage(imageID) +type Layer struct { + TarPath string + History types.ImageHistory +} - f, err := os.Open(imageTarPath) +func (layer *Layer) String() string { + id := layer.History.ID[0:25] + if len(layer.History.Tags) > 0 { + id = "[" + strings.Join(layer.History.Tags, ",") + "]" + } + return fmt.Sprintf("%25s %7s %s", id, humanize.Bytes(uint64(layer.History.Size)), layer.History.CreatedBy) +} + +func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) { + var manifest ImageManifest + var layerMap = make(map[string]*filetree.FileTree) + var trees []*filetree.FileTree = make([]*filetree.FileTree, 0) + + // save this image to disk temporarily to get the content info + imageTarPath, tmpDir := saveImage(imageID) + defer os.RemoveAll(tmpDir) + + // read through the image contents and build a tree + tarFile, err := os.Open(imageTarPath) if err != nil { fmt.Println(err) os.Exit(1) } + defer tarFile.Close() - 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) - + tarReader := tar.NewReader(tarFile) for { header, err := tarReader.Next() @@ -75,7 +90,7 @@ func InitializeData(imageID string) (*ImageManifest, []*filetree.FileTree) { } name := header.Name - if name == targetName { + if name == "manifest.json" { manifest = NewManifest(tarReader, header) } @@ -96,13 +111,30 @@ func InitializeData(imageID string) (*ImageManifest, []*filetree.FileTree) { 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 { + + // build the content tree + for _, treeName := range manifest.LayerTarPaths { trees = append(trees, layerMap[treeName]) } - return &manifest, trees + // get the history of this image + ctx := context.Background() + dockerClient, err := client.NewEnvClient() + if err != nil { + panic(err) + } + + history, err := dockerClient.ImageHistory(ctx, imageID) + + // build the layers array + layers := make([]*Layer, len(history)-1) + for idx := 0; idx < len(layers); idx++ { + layers[idx] = new(Layer) + layers[idx].History = history[idx] + layers[idx].TarPath = manifest.LayerTarPaths[idx] + } + + return layers, trees } func saveImage(imageID string) (string, string) { @@ -154,14 +186,14 @@ func saveImage(imageID string) (string, string) { func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo { var files []filetree.FileChangeInfo - size := h.Size - tarredBytes := make([]byte, size) + var tarredBytes = make([]byte, h.Size) + _, err := parentReader.Read(tarredBytes) if err != nil { panic(err) } - r := bytes.NewReader(tarredBytes) - tarReader := tar.NewReader(r) + reader := bytes.NewReader(tarredBytes) + tarReader := tar.NewReader(reader) for { header, err := tarReader.Next() diff --git a/ui/filetreeview.go b/ui/filetreeview.go index 685b1d2..1f47115 100644 --- a/ui/filetreeview.go +++ b/ui/filetreeview.go @@ -11,7 +11,7 @@ type FileTreeView struct { Name string gui *gocui.Gui view *gocui.View - TreeIndex uint + TreeIndex int Tree *filetree.FileTree RefTrees []*filetree.FileTree } @@ -54,7 +54,7 @@ func (view *FileTreeView) Setup(v *gocui.View) error { return nil } -func (view *FileTreeView) setLayer(layerIndex uint) error { +func (view *FileTreeView) setLayer(layerIndex int) error { view.Tree = filetree.StackRange(view.RefTrees, layerIndex-1) view.Tree.Compare(view.RefTrees[layerIndex]) v, _ := view.gui.View("debug") @@ -84,7 +84,7 @@ func (view *FileTreeView) CursorUp() error { func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) { var visiter func(*filetree.FileNode) error var evaluator func(*filetree.FileNode) bool - var dfsCounter uint + var dfsCounter int visiter = func(curNode *filetree.FileNode) error { if dfsCounter == view.TreeIndex { diff --git a/ui/layerview.go b/ui/layerview.go index b8dd08e..33179a6 100644 --- a/ui/layerview.go +++ b/ui/layerview.go @@ -11,17 +11,17 @@ type LayerView struct { Name string gui *gocui.Gui view *gocui.View - LayerIndex uint - Manifest *image.ImageManifest + LayerIndex int + Layers []*image.Layer } -func NewLayerView(name string, gui *gocui.Gui, manifest *image.ImageManifest) (layerview *LayerView) { +func NewLayerView(name string, gui *gocui.Gui, layers []*image.Layer) (layerview *LayerView) { layerview = new(LayerView) // populate main fields layerview.Name = name layerview.gui = gui - layerview.Manifest = manifest + layerview.Layers = layers return layerview } @@ -30,7 +30,7 @@ func (view *LayerView) Setup(v *gocui.View) error { // set view options view.view = v - view.view.Wrap = true + view.view.Wrap = false view.view.Highlight = true view.view.SelBgColor = gocui.ColorGreen view.view.SelFgColor = gocui.ColorBlack @@ -51,8 +51,9 @@ func (view *LayerView) Setup(v *gocui.View) error { func (view *LayerView) Render() error { view.gui.Update(func(g *gocui.Gui) error { view.view.Clear() - for ix, layerName := range view.Manifest.Layers { - fmt.Fprintf(view.view, "%d: %s\n", ix+1, layerName[0:25]) + for idx := len(view.Layers) - 1; idx >= 0; idx-- { + layer := view.Layers[idx] + fmt.Fprintln(view.view, layer.String()) } return nil }) @@ -61,22 +62,25 @@ func (view *LayerView) Render() error { } func (view *LayerView) CursorDown() error { - if int(view.LayerIndex) < len(view.Manifest.Layers) { - CursorDown(view.gui, view.view) - view.LayerIndex++ - view.Render() - Views.Tree.setLayer(view.LayerIndex) + if int(view.LayerIndex) < len(view.Layers) { + err := CursorDown(view.gui, view.view) + if err == nil { + view.LayerIndex++ + view.Render() + Views.Tree.setLayer(view.LayerIndex) + } } return nil } func (view *LayerView) CursorUp() error { if int(view.LayerIndex) > 0 { - CursorUp(view.gui, view.view) - view.LayerIndex-- - view.Render() - // this line is evil - Views.Tree.setLayer(view.LayerIndex) + err := CursorUp(view.gui, view.view) + if err == nil { + view.LayerIndex-- + view.Render() + Views.Tree.setLayer(view.LayerIndex) + } } return nil } diff --git a/ui/ui.go b/ui/ui.go index bdbc897..14d255c 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -76,8 +76,8 @@ func keybindings(g *gocui.Gui) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - splitCol := 50 - debugCol := maxX - 100 + splitCol := 100 + debugCol := maxX - 70 if view, err := g.SetView(Views.Layer.Name, -1, -1, splitCol, maxY); err != nil { if err != gocui.ErrUnknownView { return err @@ -105,7 +105,7 @@ func layout(g *gocui.Gui) error { return nil } -func Run(manifest *image.ImageManifest, refTrees []*filetree.FileTree) { +func Run(layers []*image.Layer, refTrees []*filetree.FileTree) { g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { @@ -113,7 +113,7 @@ func Run(manifest *image.ImageManifest, refTrees []*filetree.FileTree) { } defer g.Close() - Views.Layer = NewLayerView("side", g, manifest) + Views.Layer = NewLayerView("side", g, layers) Views.Tree = NewFileTreeView("main", g, filetree.StackRange(refTrees, 0), refTrees) g.Cursor = false