From 50d776e84592b01d65732c9f17db4d9f30a115e7 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 3 Oct 2019 10:47:38 -0400 Subject: [PATCH] rework resolver interface --- dive/get_image_handler.go | 6 +- dive/image/docker/image.go | 56 +++++++++++-------- dive/image/docker/{handler.go => resolver.go} | 45 +++++++-------- dive/image/docker/testing.go | 6 +- dive/image/handler.go | 6 -- dive/image/podman/{handler.go => resolver.go} | 28 ++++------ dive/image/resolver.go | 2 +- runtime/run.go | 8 +-- 8 files changed, 74 insertions(+), 83 deletions(-) rename dive/image/docker/{handler.go => resolver.go} (65%) delete mode 100644 dive/image/handler.go rename dive/image/podman/{handler.go => resolver.go} (71%) diff --git a/dive/get_image_handler.go b/dive/get_image_handler.go index a3eb5a1..d8e5cec 100644 --- a/dive/get_image_handler.go +++ b/dive/get_image_handler.go @@ -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") diff --git a/dive/image/docker/image.go b/dive/image/docker/image.go index 7523c04..f2b8cbd 100644 --- a/dive/image/docker/image.go +++ b/dive/image/docker/image.go @@ -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 diff --git a/dive/image/docker/handler.go b/dive/image/docker/resolver.go similarity index 65% rename from dive/image/docker/handler.go rename to dive/image/docker/resolver.go index d8b1457..5d0d3d9 100644 --- a/dive/image/docker/handler.go +++ b/dive/image/docker/resolver.go @@ -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() -} diff --git a/dive/image/docker/testing.go b/dive/image/docker/testing.go index 48e465a..0354a29 100644 --- a/dive/image/docker/testing.go +++ b/dive/image/docker/testing.go @@ -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() } diff --git a/dive/image/handler.go b/dive/image/handler.go deleted file mode 100644 index 924f5f3..0000000 --- a/dive/image/handler.go +++ /dev/null @@ -1,6 +0,0 @@ -package image - -type Handler interface { - Resolver - Analyzer -} diff --git a/dive/image/podman/handler.go b/dive/image/podman/resolver.go similarity index 71% rename from dive/image/podman/handler.go rename to dive/image/podman/resolver.go index 719f0ba..87da3a8 100644 --- a/dive/image/podman/handler.go +++ b/dive/image/podman/resolver.go @@ -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() -} diff --git a/dive/image/resolver.go b/dive/image/resolver.go index 9485e13..825f557 100644 --- a/dive/image/resolver.go +++ b/dive/image/resolver.go @@ -1,6 +1,6 @@ package image type Resolver interface { - Get(id string) error + Resolve(id string) (Analyzer, error) Build(options []string) (string, error) } diff --git a/runtime/run.go b/runtime/run.go index c42ee96..66c04e6 100644 --- a/runtime/run.go +++ b/runtime/run.go @@ -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)