with fancy pre-processing
This commit is contained in:
parent
54b20c096b
commit
dddb2e6b97
2
Makefile
2
Makefile
@ -4,7 +4,7 @@ all: clean build
|
|||||||
|
|
||||||
run: build
|
run: build
|
||||||
docker image ls | grep "dive-test" >/dev/null || docker build -t dive-test:latest .
|
docker image ls | grep "dive-test" >/dev/null || docker build -t dive-test:latest .
|
||||||
./build/$(BIN) die-test
|
./build/$(BIN) dive-test
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -o build/$(BIN)
|
go build -o build/$(BIN)
|
||||||
|
10
README.md
10
README.md
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
A tool for interrogating docker images.
|
A tool for interrogating docker images.
|
||||||
|
|
||||||
|
**This is beta quality!**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Installing
|
||||||
```
|
```
|
||||||
docker build -t die-test:latest .
|
docker build -t die-test:latest .
|
||||||
go run main.go
|
go run main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
# TODO:
|
|
||||||
|
|
||||||
- [ ] Diff trees
|
|
||||||
|
|
||||||
- [ ] Add ui for diffing stack to layer
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"github.com/wagoodman/dive/image"
|
"github.com/wagoodman/dive/image"
|
||||||
"github.com/wagoodman/dive/ui"
|
"github.com/wagoodman/dive/ui"
|
||||||
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
// analyze takes a docker image tag, digest, or id and displayes the
|
// analyze takes a docker image tag, digest, or id and displayes the
|
||||||
@ -16,6 +17,7 @@ func analyze(cmd *cobra.Command, args []string) {
|
|||||||
fmt.Println("No image argument given")
|
fmt.Println("No image argument given")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
color.New(color.Bold).Println("Analyzing Image")
|
||||||
manifest, refTrees := image.InitializeData(userImage)
|
manifest, refTrees := image.InitializeData(userImage)
|
||||||
ui.Run(manifest, refTrees)
|
ui.Run(manifest, refTrees)
|
||||||
}
|
}
|
135
image/image.go
135
image/image.go
@ -15,14 +15,57 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/wagoodman/dive/filetree"
|
"github.com/wagoodman/dive/filetree"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
"github.com/wagoodman/jotframe"
|
||||||
|
"github.com/k0kubun/go-ansi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: this file should be rethought... but since it's only for preprocessing it'll be tech debt for now.
|
||||||
|
|
||||||
func check(e error) {
|
func check(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProgressBar struct {
|
||||||
|
percent int
|
||||||
|
rawTotal int64
|
||||||
|
rawCurrent int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProgressBar(total int64) *ProgressBar{
|
||||||
|
return &ProgressBar{
|
||||||
|
rawTotal: total,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) Done() {
|
||||||
|
pb.rawCurrent = pb.rawTotal
|
||||||
|
pb.percent = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) Update(currentValue int64) (hasChanged bool) {
|
||||||
|
pb.rawCurrent = currentValue
|
||||||
|
percent := int(100.0*(float64(pb.rawCurrent) / float64(pb.rawTotal)))
|
||||||
|
if percent != pb.percent {
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
pb.percent = percent
|
||||||
|
return hasChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) String() string {
|
||||||
|
width := 40
|
||||||
|
done := int((pb.percent*width)/100.0)
|
||||||
|
todo := width - done
|
||||||
|
head := 1
|
||||||
|
// if pb.percent >= 100 {
|
||||||
|
// head = 0
|
||||||
|
// }
|
||||||
|
|
||||||
|
return "[" + strings.Repeat("=", done) + strings.Repeat(">", head) + strings.Repeat(" ", todo) + "]" + fmt.Sprintf(" %d %% (%d/%d)", pb.percent, pb.rawCurrent, pb.rawTotal)
|
||||||
|
}
|
||||||
|
|
||||||
type ImageManifest struct {
|
type ImageManifest struct {
|
||||||
ConfigPath string `json:"Config"`
|
ConfigPath string `json:"Config"`
|
||||||
RepoTags []string `json:"RepoTags"`
|
RepoTags []string `json:"RepoTags"`
|
||||||
@ -92,7 +135,7 @@ func NewImageConfig(reader *tar.Reader, header *tar.Header) ImageConfig {
|
|||||||
func GetImageConfig(imageTarPath string, manifest ImageManifest) ImageConfig{
|
func GetImageConfig(imageTarPath string, manifest ImageManifest) ImageConfig{
|
||||||
var config ImageConfig
|
var config ImageConfig
|
||||||
// read through the image contents and build a tree
|
// read through the image contents and build a tree
|
||||||
fmt.Println("Fetching image config...")
|
fmt.Println(" Fetching image config...")
|
||||||
tarFile, err := os.Open(imageTarPath)
|
tarFile, err := os.Open(imageTarPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -123,16 +166,27 @@ func GetImageConfig(imageTarPath string, manifest ImageManifest) ImageConfig{
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func processTar(layerMap map[string]*filetree.FileTree, name string, tarredBytes []byte) {
|
func processLayerTar(line *jotframe.Line, layerMap map[string]*filetree.FileTree, name string, tarredBytes []byte) {
|
||||||
tree := filetree.NewFileTree()
|
tree := filetree.NewFileTree()
|
||||||
tree.Name = name
|
tree.Name = name
|
||||||
|
|
||||||
fileInfos := getFileList(tarredBytes)
|
fileInfos := getFileList(tarredBytes)
|
||||||
for _, element := range fileInfos {
|
|
||||||
|
shortName := name[:15]
|
||||||
|
pb := NewProgressBar(int64(len(fileInfos)))
|
||||||
|
for idx, element := range fileInfos {
|
||||||
tree.FileSize += uint64(element.TarHeader.FileInfo().Size())
|
tree.FileSize += uint64(element.TarHeader.FileInfo().Size())
|
||||||
tree.AddPath(element.Path, element)
|
tree.AddPath(element.Path, element)
|
||||||
|
|
||||||
|
if pb.Update(int64(idx)) {
|
||||||
|
io.WriteString(line, fmt.Sprintf(" ├─ %s : %s", shortName, pb.String()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
pb.Done()
|
||||||
|
io.WriteString(line, fmt.Sprintf(" ├─ %s : %s", shortName, pb.String()))
|
||||||
|
|
||||||
layerMap[tree.Name] = tree
|
layerMap[tree.Name] = tree
|
||||||
|
line.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||||
@ -140,16 +194,16 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
var layerMap = make(map[string]*filetree.FileTree)
|
var layerMap = make(map[string]*filetree.FileTree)
|
||||||
var trees = make([]*filetree.FileTree, 0)
|
var trees = make([]*filetree.FileTree, 0)
|
||||||
|
|
||||||
|
ansi.CursorHide()
|
||||||
|
|
||||||
// save this image to disk temporarily to get the content info
|
// save this image to disk temporarily to get the content info
|
||||||
fmt.Println("Fetching image...")
|
|
||||||
imageTarPath, tmpDir := saveImage(imageID)
|
imageTarPath, tmpDir := saveImage(imageID)
|
||||||
// imageTarPath := "/tmp/dive229500681/image.tar"
|
// imageTarPath := "/tmp/dive516670682/image.tar"
|
||||||
// tmpDir := "/tmp/dive229500681"
|
// tmpDir := "/tmp/dive516670682"
|
||||||
// fmt.Println(tmpDir)
|
// fmt.Println(tmpDir)
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
// read through the image contents and build a tree
|
// read through the image contents and build a tree
|
||||||
fmt.Printf("Reading image '%s'...\n", imageID)
|
|
||||||
tarFile, err := os.Open(imageTarPath)
|
tarFile, err := os.Open(imageTarPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -157,14 +211,25 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
}
|
}
|
||||||
defer tarFile.Close()
|
defer tarFile.Close()
|
||||||
|
|
||||||
|
fi, err := tarFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
totalSize := fi.Size()
|
||||||
|
var observedBytes int64
|
||||||
|
var percent int
|
||||||
|
|
||||||
tarReader := tar.NewReader(tarFile)
|
tarReader := tar.NewReader(tarFile)
|
||||||
|
frame := jotframe.NewFixedFrame(1, true, false, false)
|
||||||
|
lastLine := frame.Lines()[0]
|
||||||
|
io.WriteString(lastLine, " ╧")
|
||||||
|
lastLine.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
header, err := tarReader.Next()
|
header, err := tarReader.Next()
|
||||||
|
|
||||||
// log.Debug(header.Name)
|
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
io.WriteString(frame.Header(), " Discovering layers... Done!")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,23 +238,33 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observedBytes += header.Size
|
||||||
|
percent = int(100.0*(float64(observedBytes) / float64(totalSize)))
|
||||||
|
io.WriteString(frame.Header(), fmt.Sprintf(" Discovering layers... %d %%", percent))
|
||||||
|
|
||||||
name := header.Name
|
name := header.Name
|
||||||
|
|
||||||
switch header.Typeflag {
|
switch header.Typeflag {
|
||||||
case tar.TypeDir:
|
case tar.TypeDir:
|
||||||
continue
|
continue
|
||||||
case tar.TypeReg:
|
case tar.TypeReg:
|
||||||
// todo: process this loop in parallel, visualize with jotframe
|
|
||||||
if strings.HasSuffix(name, "layer.tar") {
|
if strings.HasSuffix(name, "layer.tar") {
|
||||||
|
line, err := frame.Prepend()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
shortName := name[:15]
|
||||||
|
io.WriteString(line, " ├─ " + shortName + " : loading...")
|
||||||
|
|
||||||
var tarredBytes = make([]byte, header.Size)
|
var tarredBytes = make([]byte, header.Size)
|
||||||
|
|
||||||
_, err := tarReader.Read(tarredBytes)
|
_, err = tarReader.Read(tarredBytes)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go processTar(layerMap, name, tarredBytes)
|
|
||||||
|
go processLayerTar(line, layerMap, name, tarredBytes)
|
||||||
|
|
||||||
} else if name == "manifest.json" {
|
} else if name == "manifest.json" {
|
||||||
manifest = NewImageManifest(tarReader, header)
|
manifest = NewImageManifest(tarReader, header)
|
||||||
@ -198,14 +273,17 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
fmt.Printf("ERRG: unknown tar entry: %v: %s\n", header.Typeflag, name)
|
fmt.Printf("ERRG: unknown tar entry: %v: %s\n", header.Typeflag, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
frame.Header().Close()
|
||||||
|
frame.Wait()
|
||||||
|
frame.Remove(lastLine)
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
// obtain the image history
|
// obtain the image history
|
||||||
config := GetImageConfig(imageTarPath, manifest)
|
config := GetImageConfig(imageTarPath, manifest)
|
||||||
|
|
||||||
// build the content tree
|
// build the content tree
|
||||||
fmt.Println("Building tree...")
|
fmt.Println(" Building tree...")
|
||||||
for _, treeName := range manifest.LayerTarPaths {
|
for _, treeName := range manifest.LayerTarPaths {
|
||||||
fmt.Printf("%s : %+v\n", treeName, layerMap[treeName])
|
|
||||||
trees = append(trees, layerMap[treeName])
|
trees = append(trees, layerMap[treeName])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +314,8 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
layerIdx--
|
layerIdx--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ansi.CursorShow()
|
||||||
|
|
||||||
return layers, trees
|
return layers, trees
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +326,20 @@ func saveImage(imageID string) (string, string) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frame := jotframe.NewFixedFrame(0, false, false, true)
|
||||||
|
line, err := frame.Append()
|
||||||
|
check(err)
|
||||||
|
io.WriteString(line, " Fetching metadata...")
|
||||||
|
|
||||||
|
result, _, err := dockerClient.ImageInspectWithRaw(ctx, imageID)
|
||||||
|
check(err)
|
||||||
|
totalSize := result.Size
|
||||||
|
|
||||||
|
frame.Remove(line)
|
||||||
|
line, err = frame.Append()
|
||||||
|
check(err)
|
||||||
|
io.WriteString(line, " Fetching image...")
|
||||||
|
|
||||||
readCloser, err := dockerClient.ImageSave(ctx, []string{imageID})
|
readCloser, err := dockerClient.ImageSave(ctx, []string{imageID})
|
||||||
check(err)
|
check(err)
|
||||||
defer readCloser.Close()
|
defer readCloser.Close()
|
||||||
@ -263,6 +357,9 @@ func saveImage(imageID string) (string, string) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
imageWriter := bufio.NewWriter(imageFile)
|
imageWriter := bufio.NewWriter(imageFile)
|
||||||
|
pb := NewProgressBar(totalSize)
|
||||||
|
|
||||||
|
var observedBytes int64
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
@ -274,6 +371,12 @@ func saveImage(imageID string) (string, string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observedBytes += int64(n)
|
||||||
|
|
||||||
|
if pb.Update(observedBytes) {
|
||||||
|
io.WriteString(line, fmt.Sprintf(" Fetching image... %s", pb.String()))
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := imageWriter.Write(buf[:n]); err != nil {
|
if _, err := imageWriter.Write(buf[:n]); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -283,6 +386,10 @@ func saveImage(imageID string) (string, string) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pb.Done()
|
||||||
|
io.WriteString(line, fmt.Sprintf(" Fetching image... %s", pb.String()))
|
||||||
|
frame.Close()
|
||||||
|
|
||||||
return imageTarPath, tmpDir
|
return imageTarPath, tmpDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user