Reconcile history from image manifest and config (#17)
This commit is contained in:
parent
c599ca5ad2
commit
9625c51aa4
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@
|
|||||||
/_vendor*
|
/_vendor*
|
||||||
/vendor
|
/vendor
|
||||||
/.image
|
/.image
|
||||||
|
*.log
|
23
cmd/root.go
23
cmd/root.go
@ -3,10 +3,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgFile string
|
var cfgFile string
|
||||||
@ -31,6 +31,7 @@ func Execute() {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
|
cobra.OnInitialize(initLogging)
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
// Here you will define your flags and configuration settings.
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
// Cobra supports persistent flags, which, if defined here,
|
||||||
@ -67,3 +68,21 @@ func initConfig() {
|
|||||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initLogging() {
|
||||||
|
// TODO: clean this up and make more configurable
|
||||||
|
var filename string = "dive.log"
|
||||||
|
// create the log file if doesn't exist. And append to it if it already exists.
|
||||||
|
f, err := os.OpenFile(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0644)
|
||||||
|
Formatter := new(log.TextFormatter)
|
||||||
|
Formatter.DisableTimestamp = true
|
||||||
|
log.SetFormatter(Formatter)
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
if err != nil {
|
||||||
|
// cannot open log file. Logging to stderr
|
||||||
|
fmt.Println(err)
|
||||||
|
}else{
|
||||||
|
log.SetOutput(f)
|
||||||
|
}
|
||||||
|
log.Info("Starting Dive...")
|
||||||
|
}
|
@ -20,6 +20,7 @@ const (
|
|||||||
type FileTree struct {
|
type FileTree struct {
|
||||||
Root *FileNode
|
Root *FileNode
|
||||||
Size int
|
Size int
|
||||||
|
FileSize uint64
|
||||||
Name string
|
Name string
|
||||||
Id uuid.UUID
|
Id uuid.UUID
|
||||||
}
|
}
|
||||||
@ -41,6 +42,7 @@ func (tree *FileTree) String(showAttributes bool) string {
|
|||||||
func (tree *FileTree) Copy() *FileTree {
|
func (tree *FileTree) Copy() *FileTree {
|
||||||
newTree := NewFileTree()
|
newTree := NewFileTree()
|
||||||
newTree.Size = tree.Size
|
newTree.Size = tree.Size
|
||||||
|
newTree.FileSize = tree.FileSize
|
||||||
newTree.Root = tree.Root.Copy(newTree.Root)
|
newTree.Root = tree.Root.Copy(newTree.Root)
|
||||||
|
|
||||||
// update the tree pointers
|
// update the tree pointers
|
||||||
|
147
image/image.go
147
image/image.go
@ -33,7 +33,26 @@ type ImageManifest struct {
|
|||||||
LayerTarPaths []string `json:"Layers"`
|
LayerTarPaths []string `json:"Layers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManifest(reader *tar.Reader, header *tar.Header) ImageManifest {
|
type ImageConfig struct {
|
||||||
|
History []ImageHistoryEntry `json:"history"`
|
||||||
|
RootFs RootFs `json:"rootfs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RootFs struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
DiffIds []string `json:"diff_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageHistoryEntry struct {
|
||||||
|
ID string
|
||||||
|
Size uint64
|
||||||
|
Created string `json:"created"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
EmptyLayer bool `json:"empty_layer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageManifest(reader *tar.Reader, header *tar.Header) ImageManifest {
|
||||||
size := header.Size
|
size := header.Size
|
||||||
manifestBytes := make([]byte, size)
|
manifestBytes := make([]byte, size)
|
||||||
_, err := reader.Read(manifestBytes)
|
_, err := reader.Read(manifestBytes)
|
||||||
@ -48,21 +67,36 @@ func NewManifest(reader *tar.Reader, header *tar.Header) ImageManifest {
|
|||||||
return manifest[0]
|
return manifest[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
func NewImageConfig(reader *tar.Reader, header *tar.Header) ImageConfig {
|
||||||
var manifest ImageManifest
|
size := header.Size
|
||||||
var layerMap = make(map[string]*filetree.FileTree)
|
configBytes := make([]byte, size)
|
||||||
var trees []*filetree.FileTree = make([]*filetree.FileTree, 0)
|
_, err := reader.Read(configBytes)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var imageConfig ImageConfig
|
||||||
|
err = json.Unmarshal(configBytes, &imageConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// save this image to disk temporarily to get the content info
|
layerIdx := 0
|
||||||
fmt.Println("Fetching image...")
|
for idx := range imageConfig.History {
|
||||||
// imageTarPath, tmpDir := saveImage(imageID)
|
if imageConfig.History[idx].EmptyLayer {
|
||||||
imageTarPath := "/tmp/dive031537738/image.tar"
|
imageConfig.History[idx].ID = "<missing>"
|
||||||
// tmpDir := "/tmp/dive031537738"
|
} else {
|
||||||
// fmt.Println(tmpDir)
|
imageConfig.History[idx].ID = imageConfig.RootFs.DiffIds[layerIdx]
|
||||||
// defer os.RemoveAll(tmpDir)
|
layerIdx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetImageConfig(imageTarPath string, manifest ImageManifest) ImageConfig{
|
||||||
|
var config ImageConfig
|
||||||
// read through the image contents and build a tree
|
// read through the image contents and build a tree
|
||||||
fmt.Println("Reading image...")
|
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)
|
||||||
@ -83,20 +117,69 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name := header.Name
|
||||||
|
if name == manifest.ConfigPath {
|
||||||
|
config = NewImageConfig(tarReader, header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtain the image history
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// fmt.Println("Fetching image...")
|
||||||
|
imageTarPath, tmpDir := saveImage(imageID)
|
||||||
|
// imageTarPath := "/tmp/dive932744808/image.tar"
|
||||||
|
// tmpDir := "/tmp/dive031537738"
|
||||||
|
// fmt.Println(tmpDir)
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
// read through the image contents and build a tree
|
||||||
|
fmt.Println("Reading image...")
|
||||||
|
tarFile, err := os.Open(imageTarPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer tarFile.Close()
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(tarFile)
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
|
||||||
|
// log.Debug(header.Name)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
name := header.Name
|
name := header.Name
|
||||||
if name == "manifest.json" {
|
if name == "manifest.json" {
|
||||||
manifest = NewManifest(tarReader, header)
|
manifest = NewImageManifest(tarReader, header)
|
||||||
}
|
}
|
||||||
|
|
||||||
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") {
|
||||||
tree := filetree.NewFileTree()
|
tree := filetree.NewFileTree()
|
||||||
tree.Name = name
|
tree.Name = name
|
||||||
fileInfos := getFileList(tarReader, header)
|
fileInfos := getFileList(tarReader, header)
|
||||||
for _, element := range fileInfos {
|
for _, element := range fileInfos {
|
||||||
|
tree.FileSize += uint64(element.TarHeader.FileInfo().Size())
|
||||||
tree.AddPath(element.Path, element)
|
tree.AddPath(element.Path, element)
|
||||||
}
|
}
|
||||||
layerMap[tree.Name] = tree
|
layerMap[tree.Name] = tree
|
||||||
@ -106,33 +189,41 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// obtain the image history
|
||||||
|
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 {
|
||||||
trees = append(trees, layerMap[treeName])
|
trees = append(trees, layerMap[treeName])
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the history of this image
|
|
||||||
ctx := context.Background()
|
|
||||||
dockerClient, err := client.NewClientWithOpts()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
history, err := dockerClient.ImageHistory(ctx, imageID)
|
|
||||||
|
|
||||||
// build the layers array
|
// build the layers array
|
||||||
layers := make([]*Layer, len(trees))
|
layers := make([]*Layer, len(trees))
|
||||||
for idx := 0; idx < len(trees); idx++ {
|
|
||||||
layers[idx] = &Layer{
|
// note that the image config stores images in reverse chronological order, so iterate backwards through layers
|
||||||
History: history[idx],
|
// as you iterate chronologically through history (ignoring history items that have no layer contents)
|
||||||
Index: idx,
|
layerIdx := len(trees)-1
|
||||||
Tree: trees[idx],
|
for idx := 0; idx < len(config.History); idx++ {
|
||||||
|
// ignore empty layers, we are only observing layers with content
|
||||||
|
if config.History[idx].EmptyLayer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
config.History[idx].Size = uint64(trees[(len(trees)-1)-layerIdx].FileSize)
|
||||||
|
|
||||||
|
layers[layerIdx] = &Layer{
|
||||||
|
History: config.History[idx],
|
||||||
|
Index: layerIdx,
|
||||||
|
Tree: trees[layerIdx],
|
||||||
RefTrees: trees,
|
RefTrees: trees,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(manifest.LayerTarPaths) > idx {
|
if len(manifest.LayerTarPaths) > idx {
|
||||||
layers[idx].TarPath = manifest.LayerTarPaths[idx]
|
layers[layerIdx].TarPath = manifest.LayerTarPaths[layerIdx]
|
||||||
}
|
}
|
||||||
|
layerIdx--
|
||||||
}
|
}
|
||||||
|
|
||||||
return layers, trees
|
return layers, trees
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/wagoodman/dive/filetree"
|
"github.com/wagoodman/dive/filetree"
|
||||||
"strings"
|
"strings"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -11,7 +10,7 @@ import (
|
|||||||
|
|
||||||
type Layer struct {
|
type Layer struct {
|
||||||
TarPath string
|
TarPath string
|
||||||
History image.HistoryResponseItem
|
History ImageHistoryEntry
|
||||||
Index int
|
Index int
|
||||||
Tree *filetree.FileTree
|
Tree *filetree.FileTree
|
||||||
RefTrees []*filetree.FileTree
|
RefTrees []*filetree.FileTree
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/jroimartin/gocui"
|
"github.com/jroimartin/gocui"
|
||||||
"github.com/wagoodman/dive/image"
|
"github.com/wagoodman/dive/image"
|
||||||
"github.com/lunixbochs/vtclean"
|
"github.com/lunixbochs/vtclean"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LayerView struct {
|
type LayerView struct {
|
||||||
@ -151,8 +152,7 @@ func (view *LayerView) Render() error {
|
|||||||
layerId = fmt.Sprintf("%-25s", layer.History.ID)
|
layerId = fmt.Sprintf("%-25s", layer.History.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add size
|
layerStr = fmt.Sprintf(image.LayerFormat, layerId, "", humanize.Bytes(uint64(layer.History.Size)), "FROM "+layer.Id())
|
||||||
layerStr = fmt.Sprintf(image.LayerFormat, layerId, "", "", "FROM "+layer.Id())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compareBar := view.renderCompareBar(idx)
|
compareBar := view.renderCompareBar(idx)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user