rework resolver interface

This commit is contained in:
Alex Goodman 2019-10-03 10:47:38 -04:00
parent d2c661eaf7
commit 50d776e845
No known key found for this signature in database
GPG key ID: 98AF011C5C78EB7E
8 changed files with 74 additions and 83 deletions

View file

@ -32,12 +32,12 @@ func GetEngine(engine string) Engine {
}
}
func GetImageHandler(engine Engine) (image.Handler, error) {
func GetImageHandler(engine Engine) (image.Resolver, error) {
switch engine {
case EngineDocker:
return docker.NewHandler(), nil
return docker.NewResolver(), nil
case EnginePodman:
return podman.NewHandler(), nil
return podman.NewResolver(), nil
}
return nil, fmt.Errorf("unable to determine image provider")

View file

@ -12,21 +12,23 @@ import (
)
type Image struct {
jsonFiles map[string][]byte
manifest manifest
config config
trees []*filetree.FileTree
layerMap map[string]*filetree.FileTree
layers []*dockerLayer
}
func NewImageFromArchive(tarFile io.ReadCloser) (Image, error) {
img := Image{
// store discovered json files in a map so we can read the image in one pass
jsonFiles: make(map[string][]byte),
func NewImageFromArchive(tarFile io.ReadCloser) (*Image, error) {
img := &Image{
layerMap: make(map[string]*filetree.FileTree),
}
tarReader := tar.NewReader(tarFile)
// store discovered json files in a map so we can read the image in one pass
jsonFiles := make(map[string][]byte)
var currentLayer uint
for {
header, err := tarReader.Next()
@ -45,7 +47,7 @@ func NewImageFromArchive(tarFile io.ReadCloser) (Image, error) {
// some layer tars can be relative layer symlinks to other layer tars
if header.Typeflag == tar.TypeSymlink || header.Typeflag == tar.TypeReg {
if strings.HasSuffix(name, "layer.tar") {
if strings.HasSuffix(name, ".tar") {
currentLayer++
if err != nil {
return img, err
@ -65,17 +67,31 @@ func NewImageFromArchive(tarFile io.ReadCloser) (Image, error) {
if err != nil {
return img, err
}
img.jsonFiles[name] = fileBuffer
jsonFiles[name] = fileBuffer
}
}
}
manifestContent, exists := jsonFiles["manifest.json"]
if !exists {
return img, fmt.Errorf("could not find image manifest")
}
img.manifest = newManifest(manifestContent)
configContent, exists := jsonFiles[img.manifest.ConfigPath]
if !exists {
return img, fmt.Errorf("could not find image config")
}
img.config = newConfig(configContent)
return img, nil
}
func processLayerTar(name string, reader *tar.Reader) (*filetree.FileTree, error) {
tree := filetree.NewFileTree()
tree.Name = pathToLayerId(name)
tree.Name = name
fileInfos, err := getFileList(reader)
if err != nil {
@ -91,13 +107,9 @@ func processLayerTar(name string, reader *tar.Reader) (*filetree.FileTree, error
}
}
return tree, nil
}
func pathToLayerId(name string) string {
return strings.TrimSuffix(strings.TrimSuffix(name, ".tar"), "/layer")
}
func getFileList(tarReader *tar.Reader) ([]filetree.FileInfo, error) {
var files []filetree.FileInfo
@ -129,13 +141,9 @@ func (img *Image) Analyze() (*image.AnalysisResult, error) {
img.trees = make([]*filetree.FileTree, 0)
manifest := newManifest(img.jsonFiles["manifest.json"])
config := newConfig(img.jsonFiles[manifest.ConfigPath])
// build the content tree
for _, treeName := range manifest.LayerTarPaths {
key := pathToLayerId(treeName)
tr, exists := img.layerMap[key]
for _, treeName := range img.manifest.LayerTarPaths {
tr, exists := img.layerMap[treeName]
if exists {
img.trees = append(img.trees, tr)
continue
@ -146,7 +154,7 @@ func (img *Image) Analyze() (*image.AnalysisResult, error) {
// build the layers array
img.layers = make([]*dockerLayer, len(img.trees))
// note that the handler config stores images in reverse chronological order, so iterate backwards through layers
// note that the resolver config stores images in reverse chronological order, so iterate backwards through layers
// as you iterate chronologically through history (ignoring history items that have no layer contents)
// Note: history is not required metadata in a docker image!
tarPathIdx := 0
@ -159,14 +167,14 @@ func (img *Image) Analyze() (*image.AnalysisResult, error) {
historyObj := imageHistoryEntry{
CreatedBy: "(missing)",
}
for nextHistIdx := histIdx; nextHistIdx < len(config.History); nextHistIdx++ {
if !config.History[nextHistIdx].EmptyLayer {
for nextHistIdx := histIdx; nextHistIdx < len(img.config.History); nextHistIdx++ {
if !img.config.History[nextHistIdx].EmptyLayer {
histIdx = nextHistIdx
break
}
}
if histIdx < len(config.History) && !config.History[histIdx].EmptyLayer {
historyObj = config.History[histIdx]
if histIdx < len(img.config.History) && !img.config.History[histIdx].EmptyLayer {
historyObj = img.config.History[histIdx]
histIdx++
}
@ -174,7 +182,7 @@ func (img *Image) Analyze() (*image.AnalysisResult, error) {
history: historyObj,
index: tarPathIdx,
tree: img.trees[layerIdx],
tarPath: manifest.LayerTarPaths[tarPathIdx],
tarPath: img.manifest.LayerTarPaths[tarPathIdx],
}
img.layers[layerIdx].history.Size = tree.FileSize

View file

@ -15,44 +15,41 @@ import (
var dockerVersion string
type handler struct {
type resolver struct {
id string
client *client.Client
image Image
}
func NewHandler() *handler {
return &handler{}
func NewResolver() *resolver {
return &resolver{}
}
func (handler *handler) Get(id string) error {
handler.id = id
func (r *resolver) Resolve(id string) (image.Analyzer, error) {
r.id = id
reader, err := handler.fetchArchive()
reader, err := r.fetchArchive()
if err != nil {
return err
return nil, err
}
defer reader.Close()
img, err := NewImageFromArchive(reader)
if err != nil {
return err
return nil, err
}
handler.image = img
return nil
return img, nil
}
func (handler *handler) Build(args []string) (string, error) {
func (r *resolver) Build(args []string) (string, error) {
var err error
handler.id, err = buildImageFromCli(args)
return handler.id, err
r.id, err = buildImageFromCli(args)
return r.id, err
}
func (handler *handler) fetchArchive() (io.ReadCloser, error) {
func (r *resolver) fetchArchive() (io.ReadCloser, error) {
var err error
// pull the handler if it does not exist
// pull the resolver if it does not exist
ctx := context.Background()
host := os.Getenv("DOCKER_HOST")
@ -85,21 +82,21 @@ func (handler *handler) fetchArchive() (io.ReadCloser, error) {
}
clientOpts = append(clientOpts, client.WithVersion(dockerVersion))
handler.client, err = client.NewClientWithOpts(clientOpts...)
r.client, err = client.NewClientWithOpts(clientOpts...)
if err != nil {
return nil, err
}
_, _, err = handler.client.ImageInspectWithRaw(ctx, handler.id)
_, _, err = r.client.ImageInspectWithRaw(ctx, r.id)
if err != nil {
// don't use the API, the CLI has more informative output
fmt.Println("Handler not available locally. Trying to pull '" + handler.id + "'...")
err = runDockerCmd("pull", handler.id)
fmt.Println("Handler not available locally. Trying to pull '" + r.id + "'...")
err = runDockerCmd("pull", r.id)
if err != nil {
return nil, err
}
}
readCloser, err := handler.client.ImageSave(ctx, []string{handler.id})
readCloser, err := r.client.ImageSave(ctx, []string{r.id})
if err != nil {
return nil, err
}
@ -107,7 +104,3 @@ func (handler *handler) fetchArchive() (io.ReadCloser, error) {
return readCloser, nil
}
func (handler *handler) Analyze() (*image.AnalysisResult, error) {
return handler.image.Analyze()
}

View file

@ -12,11 +12,11 @@ func TestLoadDockerImageTar(tarPath string) (*image.AnalysisResult, error) {
}
defer f.Close()
handler := NewHandler()
err = handler.Get("dive-test:latest")
handler := NewResolver()
img, err := handler.Resolve("dive-test:latest")
if err != nil {
return nil, err
}
return handler.Analyze()
return img.Analyze()
}

View file

@ -1,6 +0,0 @@
package image
type Handler interface {
Resolver
Analyzer
}

View file

@ -11,45 +11,43 @@ import (
"os"
)
type handler struct {
type resolver struct {
id string
// note: podman supports saving docker formatted archives, we're leveraging this here
// todo: add oci parser and image/layer objects
image docker.Image
}
func NewHandler() *handler {
return &handler{}
func NewResolver() *resolver {
return &resolver{}
}
func (handler *handler) Get(id string) error {
func (handler *resolver) Resolve(id string) (image.Analyzer, error) {
handler.id = id
path, err := handler.fetchArchive()
if err != nil {
return err
return nil, err
}
defer os.Remove(path)
file, err := os.Open(path)
// we use podman to extract a docker-formatted image
img, err := docker.NewImageFromArchive(ioutil.NopCloser(bufio.NewReader(file)))
if err != nil {
return err
return nil, err
}
handler.image = img
return nil
return img, nil
}
func (handler *handler) Build(args []string) (string, error) {
func (handler *resolver) Build(args []string) (string, error) {
var err error
handler.id, err = buildImageFromCli(args)
return handler.id, err
}
func (handler *handler) fetchArchive() (string, error) {
func (handler *resolver) fetchArchive() (string, error) {
var err error
var ctx = context.Background()
@ -66,7 +64,7 @@ func (handler *handler) fetchArchive() (string, error) {
for _, item:= range images {
for _, name := range item.Names() {
if name == handler.id {
file, err := ioutil.TempFile(os.TempDir(), "dive-handler-tar")
file, err := ioutil.TempFile(os.TempDir(), "dive-resolver-tar")
if err != nil {
return "", err
}
@ -76,6 +74,8 @@ func (handler *handler) fetchArchive() (string, error) {
return "", err
}
fmt.Println(file.Name())
return file.Name(), nil
}
}
@ -83,7 +83,3 @@ func (handler *handler) fetchArchive() (string, error) {
return "", fmt.Errorf("image could not be found")
}
func (handler *handler) Analyze() (*image.AnalysisResult, error) {
return handler.image.Analyze()
}

View file

@ -1,6 +1,6 @@
package image
type Resolver interface {
Get(id string) error
Resolve(id string) (Analyzer, error)
Build(options []string) (string, error)
}

View file

@ -40,7 +40,7 @@ func Run(options Options) {
// if build is given, get the handler based off of either the explicit runtime
img, err := dive.GetImageHandler(options.Engine)
imageHandler, err := dive.GetImageHandler(options.Engine)
if err != nil {
fmt.Printf("cannot determine image provider: %v\n", err)
utils.Exit(1)
@ -48,14 +48,14 @@ func Run(options Options) {
if doBuild {
fmt.Println(utils.TitleFormat("Building image..."))
options.ImageId, err = img.Build(options.BuildArgs)
options.ImageId, err = imageHandler.Build(options.BuildArgs)
if err != nil {
fmt.Printf("cannot build image: %v\n", err)
utils.Exit(1)
}
}
err = img.Get(options.ImageId)
imgAnalyzer, err := imageHandler.Resolve(options.ImageId)
if err != nil {
fmt.Printf("cannot fetch image: %v\n", err)
utils.Exit(1)
@ -70,7 +70,7 @@ func Run(options Options) {
fmt.Println(utils.TitleFormat("Analyzing image..."))
}
result, err := img.Analyze()
result, err := imgAnalyzer.Analyze()
if err != nil {
fmt.Printf("cannot analyze image: %v\n", err)
utils.Exit(1)