rework resolver interface
This commit is contained in:
parent
d2c661eaf7
commit
50d776e845
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
package image
|
||||
|
||||
type Handler interface {
|
||||
Resolver
|
||||
Analyzer
|
||||
}
|
@ -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()
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package image
|
||||
|
||||
type Resolver interface {
|
||||
Get(id string) error
|
||||
Resolve(id string) (Analyzer, error)
|
||||
Build(options []string) (string, error)
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user