Added debug panel; annotate filetree with changeinfo (#7)
This commit is contained in:
parent
acec670354
commit
e67734d38d
4
Makefile
4
Makefile
@ -3,9 +3,9 @@ BIN = die
|
|||||||
all: clean build
|
all: clean build
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./build/$(BIN)
|
./build/$(BIN) die-test
|
||||||
|
|
||||||
build: deps
|
build: #deps
|
||||||
go build -o build/$(BIN) ./cmd/...
|
go build -o build/$(BIN) ./cmd/...
|
||||||
|
|
||||||
install: deps
|
install: deps
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
"github.com/wagoodman/docker-image-explorer/image"
|
"github.com/wagoodman/docker-image-explorer/image"
|
||||||
"github.com/wagoodman/docker-image-explorer/ui"
|
"github.com/wagoodman/docker-image-explorer/ui"
|
||||||
)
|
)
|
||||||
@ -11,15 +15,22 @@ const version = "v0.0.0"
|
|||||||
const author = "wagoodman"
|
const author = "wagoodman"
|
||||||
|
|
||||||
func main() {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func run(args []string) int {
|
|
||||||
image.WriteImage()
|
|
||||||
manifest, refTrees := image.InitializeData()
|
|
||||||
|
|
||||||
ui.Run(manifest, refTrees)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package filetree
|
package filetree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileChangeInfo struct {
|
type FileChangeInfo struct {
|
||||||
@ -22,6 +25,27 @@ const (
|
|||||||
Removed
|
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 {
|
func (d DiffType) String() string {
|
||||||
switch d {
|
switch d {
|
||||||
case Unchanged:
|
case Unchanged:
|
||||||
|
@ -140,6 +140,7 @@ func (tree *FileTree) GetNode(path string) (*FileNode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) {
|
func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) {
|
||||||
|
// fmt.Printf("ADDPATH: %s %+v\n", path, data)
|
||||||
nodeNames := strings.Split(path, "/")
|
nodeNames := strings.Split(path, "/")
|
||||||
node := tree.Root
|
node := tree.Root
|
||||||
for idx, name := range nodeNames {
|
for idx, name := range nodeNames {
|
||||||
@ -172,7 +173,7 @@ func (tree *FileTree) RemovePath(path string) error {
|
|||||||
return node.Remove()
|
return node.Remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tree *FileTree) compare(upper *FileTree) error {
|
func (tree *FileTree) Compare(upper *FileTree) error {
|
||||||
graft := func(node *FileNode) error {
|
graft := func(node *FileNode) error {
|
||||||
if node.IsWhiteout() {
|
if node.IsWhiteout() {
|
||||||
err := tree.MarkRemoved(node.Path())
|
err := tree.MarkRemoved(node.Path())
|
||||||
@ -183,14 +184,14 @@ func (tree *FileTree) compare(upper *FileTree) error {
|
|||||||
existingNode, _ := tree.GetNode(node.Path())
|
existingNode, _ := tree.GetNode(node.Path())
|
||||||
if existingNode == nil {
|
if existingNode == nil {
|
||||||
newNode, err := tree.AddPath(node.Path(), node.Data)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error())
|
return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error())
|
||||||
}
|
}
|
||||||
newNode.AssignDiffType(Added)
|
newNode.AssignDiffType(Added)
|
||||||
} else {
|
} else {
|
||||||
diffType := existingNode.compare(node)
|
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)
|
existingNode.deriveDiffType(diffType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,7 +210,7 @@ func (tree *FileTree) MarkRemoved(path string) error {
|
|||||||
|
|
||||||
func StackRange(trees []*FileTree, index uint) *FileTree {
|
func StackRange(trees []*FileTree, index uint) *FileTree {
|
||||||
tree := trees[1].Copy()
|
tree := trees[1].Copy()
|
||||||
for idx := uint(2); idx < index; idx++ {
|
for idx := uint(1); idx <= index; idx++ {
|
||||||
tree.Stack(trees[idx])
|
tree.Stack(trees[idx])
|
||||||
}
|
}
|
||||||
return tree
|
return tree
|
||||||
|
@ -186,7 +186,7 @@ func TestCompareWithNoChanges(t *testing.T) {
|
|||||||
lowerTree.AddPath(value, &fakeData)
|
lowerTree.AddPath(value, &fakeData)
|
||||||
upperTree.AddPath(value, &fakeData)
|
upperTree.AddPath(value, &fakeData)
|
||||||
}
|
}
|
||||||
lowerTree.compare(upperTree)
|
lowerTree.Compare(upperTree)
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
if n.Path() == "/" {
|
if n.Path() == "/" {
|
||||||
return nil
|
return nil
|
||||||
@ -232,7 +232,7 @@ func TestCompareWithAdds(t *testing.T) {
|
|||||||
upperTree.AddPath(value, &fakeData)
|
upperTree.AddPath(value, &fakeData)
|
||||||
}
|
}
|
||||||
|
|
||||||
lowerTree.compare(upperTree)
|
lowerTree.Compare(upperTree)
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
|
|
||||||
p := n.Path()
|
p := n.Path()
|
||||||
@ -283,7 +283,7 @@ func TestCompareWithChanges(t *testing.T) {
|
|||||||
upperTree.AddPath(value, &fakeData)
|
upperTree.AddPath(value, &fakeData)
|
||||||
}
|
}
|
||||||
|
|
||||||
lowerTree.compare(upperTree)
|
lowerTree.Compare(upperTree)
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
p := n.Path()
|
p := n.Path()
|
||||||
if p == "/" {
|
if p == "/" {
|
||||||
|
183
image/image.go
183
image/image.go
@ -1,12 +1,19 @@
|
|||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"archive/tar"
|
||||||
"os"
|
|
||||||
"bufio"
|
"bufio"
|
||||||
"github.com/docker/docker/client"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/wagoodman/docker-image-explorer/filetree"
|
||||||
"golang.org/x/net/context"
|
"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) {
|
func NewManifest(reader *tar.Reader, header *tar.Header) ImageManifest {
|
||||||
defer readCloser.Close()
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
path := ".image"
|
func InitializeData(imageID string) (*ImageManifest, []*filetree.FileTree) {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
imageTarPath, tmpDir := saveImage(imageID)
|
||||||
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)
|
check(err)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fo.Close(); err != nil {
|
if err := imageFile.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
w := bufio.NewWriter(fo)
|
imageWriter := bufio.NewWriter(imageFile)
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
@ -45,60 +140,50 @@ func saveImage(readCloser io.ReadCloser) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := w.Write(buf[:n]); err != nil {
|
if _, err := imageWriter.Write(buf[:n]); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = w.Flush(); err != nil {
|
if err = imageWriter.Flush(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return imageTarPath, tmpDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo {
|
||||||
func WriteImage() {
|
var files []filetree.FileChangeInfo
|
||||||
ctx := context.Background()
|
size := h.Size
|
||||||
cli, err := client.NewEnvClient()
|
tarredBytes := make([]byte, size)
|
||||||
|
_, err := parentReader.Read(tarredBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
r := bytes.NewReader(tarredBytes)
|
||||||
// imageID := "golang:alpine"
|
tarReader := tar.NewReader(r)
|
||||||
imageID := "die-test:latest"
|
|
||||||
|
|
||||||
fmt.Println("Saving Image...")
|
|
||||||
readCloser, err := cli.ImageSave(ctx, []string{imageID})
|
|
||||||
check(err)
|
|
||||||
saveImage(readCloser)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
inspect, _, err := cli.ImageInspectWithRaw(ctx, imageID)
|
header, err := tarReader.Next()
|
||||||
check(err)
|
|
||||||
|
|
||||||
history, err := cli.ImageHistory(ctx, imageID)
|
if err == io.EOF {
|
||||||
check(err)
|
break
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 == "" {
|
switch header.Typeflag {
|
||||||
break
|
case tar.TypeXGlobalHeader:
|
||||||
} else {
|
fmt.Printf("ERRG: XGlobalHeader: %v: %s\n", header.Typeflag, name)
|
||||||
imageID = inspect.Parent
|
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
|
||||||
}
|
}
|
||||||
|
@ -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]
|
|
||||||
}
|
|
@ -7,14 +7,13 @@ import (
|
|||||||
"github.com/wagoodman/docker-image-explorer/filetree"
|
"github.com/wagoodman/docker-image-explorer/filetree"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type FileTreeView struct {
|
type FileTreeView struct {
|
||||||
Name string
|
Name string
|
||||||
gui *gocui.Gui
|
gui *gocui.Gui
|
||||||
view *gocui.View
|
view *gocui.View
|
||||||
TreeIndex uint
|
TreeIndex uint
|
||||||
Tree *filetree.FileTree
|
Tree *filetree.FileTree
|
||||||
RefTrees []*filetree.FileTree
|
RefTrees []*filetree.FileTree
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree) (treeview *FileTreeView) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mehh, this is just a bad method
|
func (view *FileTreeView) setLayer(layerIndex uint) error {
|
||||||
func (view *FileTreeView) reset(tree *filetree.FileTree) error {
|
view.Tree = filetree.StackRange(view.RefTrees, layerIndex-1)
|
||||||
view.Tree = tree
|
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.view.SetCursor(0, 0)
|
||||||
view.TreeIndex = 0
|
view.TreeIndex = 0
|
||||||
return view.Render()
|
return view.Render()
|
||||||
|
@ -5,19 +5,17 @@ import (
|
|||||||
|
|
||||||
"github.com/jroimartin/gocui"
|
"github.com/jroimartin/gocui"
|
||||||
"github.com/wagoodman/docker-image-explorer/image"
|
"github.com/wagoodman/docker-image-explorer/image"
|
||||||
"github.com/wagoodman/docker-image-explorer/filetree"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type LayerView struct {
|
type LayerView struct {
|
||||||
Name string
|
Name string
|
||||||
gui *gocui.Gui
|
gui *gocui.Gui
|
||||||
view *gocui.View
|
view *gocui.View
|
||||||
LayerIndex uint
|
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)
|
layerview = new(LayerView)
|
||||||
|
|
||||||
// populate main fields
|
// populate main fields
|
||||||
@ -38,10 +36,10 @@ func (view *LayerView) Setup(v *gocui.View) error {
|
|||||||
view.view.SelFgColor = gocui.ColorBlack
|
view.view.SelFgColor = gocui.ColorBlack
|
||||||
|
|
||||||
// set keybindings
|
// 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
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +65,7 @@ func (view *LayerView) CursorDown() error {
|
|||||||
CursorDown(view.gui, view.view)
|
CursorDown(view.gui, view.view)
|
||||||
view.LayerIndex++
|
view.LayerIndex++
|
||||||
view.Render()
|
view.Render()
|
||||||
// this line is evil
|
Views.Tree.setLayer(view.LayerIndex)
|
||||||
Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex))
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -79,7 +76,7 @@ func (view *LayerView) CursorUp() error {
|
|||||||
view.LayerIndex--
|
view.LayerIndex--
|
||||||
view.Render()
|
view.Render()
|
||||||
// this line is evil
|
// this line is evil
|
||||||
Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex))
|
Views.Tree.setLayer(view.LayerIndex)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
18
ui/ui.go
18
ui/ui.go
@ -1,9 +1,10 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/jroimartin/gocui"
|
"github.com/jroimartin/gocui"
|
||||||
"github.com/wagoodman/docker-image-explorer/filetree"
|
"github.com/wagoodman/docker-image-explorer/filetree"
|
||||||
"log"
|
|
||||||
"github.com/wagoodman/docker-image-explorer/image"
|
"github.com/wagoodman/docker-image-explorer/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -76,14 +77,15 @@ func keybindings(g *gocui.Gui) error {
|
|||||||
func layout(g *gocui.Gui) error {
|
func layout(g *gocui.Gui) error {
|
||||||
maxX, maxY := g.Size()
|
maxX, maxY := g.Size()
|
||||||
splitCol := 50
|
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 {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Views.Layer.Setup(view)
|
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 {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -94,10 +96,16 @@ func layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, err := g.SetView("debug", debugCol, -1, maxX, maxY); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(manifest *image.Manifest, refTrees []*filetree.FileTree) {
|
func Run(manifest *image.ImageManifest, refTrees []*filetree.FileTree) {
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -119,4 +127,4 @@ func Run(manifest *image.Manifest, refTrees []*filetree.FileTree) {
|
|||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user