Skip to content

Commit

Permalink
Added debug panel; annotate filetree with changeinfo (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
wagoodman authored Jun 7, 2018
1 parent acec670 commit e67734d
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 256 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 21 additions & 10 deletions cmd/die/main.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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)
}
}

24 changes: 24 additions & 0 deletions filetree/changeinfo.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package filetree

import (
"archive/tar"
"bytes"
"crypto/md5"
"fmt"
"io"
)

type FileChangeInfo struct {
Expand All @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions filetree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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())
Expand All @@ -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)
}
}
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions filetree/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 == "/" {
Expand Down
181 changes: 133 additions & 48 deletions image/image.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand All @@ -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 {
Expand All @@ -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
}
Loading

0 comments on commit e67734d

Please sign in to comment.