Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.

Remove Docker dependency for retrieving image filesystem #97

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,23 @@ func checkAnalyzeArgNum(args []string) error {
return nil
}

func analyzeImage(imageArg string, analyzerArgs []string) error {
func analyzeImage(imageName string, analyzerArgs []string) error {
cli, err := NewClient()
if err != nil {
return fmt.Errorf("Error getting docker client for differ: %s", err)
}
defer cli.Close()

analyzeTypes, err := differs.GetAnalyzers(analyzerArgs)
if err != nil {
glog.Error(err.Error())
return errors.New("Could not perform image analysis")
}

cli, err := NewClient()
if err != nil {
return fmt.Errorf("Error getting docker client for differ: %s", err)
}
defer cli.Close()
prefixedName := processImageName(imageName)

ip := pkgutil.ImagePrepper{
Source: imageArg,
Source: prefixedName,
Client: cli,
}
image, err := ip.GetImage()
Expand Down
26 changes: 17 additions & 9 deletions cmd/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ var diffCmd = &cobra.Command{
Long: `Compares two images using the specifed analyzers as indicated via flags (see documentation for available ones).`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably document the available image name flags somewhere, does it make sense here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I should put it here. should also go in the README somewhere

Args: func(cmd *cobra.Command, args []string) error {
if err := validateArgs(args, checkDiffArgNum); err != nil {
return errors.New(err.Error())
return err
}
if err := checkIfValidAnalyzer(types); err != nil {
return errors.New(err.Error())
return err
}
return nil
},
Expand All @@ -52,21 +52,20 @@ var diffCmd = &cobra.Command{

func checkDiffArgNum(args []string) error {
if len(args) != 2 {
return errors.New("'diff' requires two images as arguments: container diff [image1] [image2]")
return errors.New("'diff' requires two images as arguments: container-diff diff [image1] [image2]")
}
return nil
}

func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
diffTypes, err := differs.GetAnalyzers(diffArgs)
if err != nil {
glog.Error(err.Error())
return errors.New("Could not perform image diff")
return fmt.Errorf("err msg: %s", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just return err or make this more informative?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed up the errors in this file

}

cli, err := NewClient()
if err != nil {
return fmt.Errorf("Error getting docker client for differ: %s", err)
return fmt.Errorf("err msg: %s", err)
}
defer cli.Close()
var wg sync.WaitGroup
Expand All @@ -81,8 +80,10 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
for imageArg := range imageMap {
go func(imageName string, imageMap map[string]*pkgutil.Image) {
defer wg.Done()

prefixedName := processImageName(imageName)
ip := pkgutil.ImagePrepper{
Source: imageName,
Source: prefixedName,
Client: cli,
}
image, err := ip.GetImage()
Expand All @@ -102,8 +103,7 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
req := differs.DiffRequest{*imageMap[image1Arg], *imageMap[image2Arg], diffTypes}
diffs, err := req.GetDiff()
if err != nil {
glog.Error(err.Error())
return errors.New("Could not perform image diff")
return fmt.Errorf("err msg: %s", err.Error())
}
glog.Info("Retrieving diffs")
outputResults(diffs)
Expand All @@ -116,6 +116,14 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
return nil
}

func processImageName(imageName string) string {
if pkgutil.IsTar(imageName) || strings.HasPrefix(imageName, "daemon://") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it already has remote://? would this add it a second time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't really planning on advertising that prefix to users, but I guess there's nothing stopping them from adding it in which case this would break. fixed

return imageName
}
// not a tar and not explicitly local, so force remote
return "remote://" + imageName
}

func init() {
RootCmd.AddCommand(diffCmd)
addSharedFlags(diffCmd)
Expand Down
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var RootCmd = &cobra.Command{
func NewClient() (*client.Client, error) {
cli, err := client.NewEnvClient()
if err != nil {
return nil, fmt.Errorf("Error getting docker client: %s", err)
return nil, fmt.Errorf("err msg: %s", err)
}
cli.NegotiateAPIVersion(context.Background())

Expand Down Expand Up @@ -93,7 +93,7 @@ func validateArgs(args []string, validatefxns ...validatefxn) error {

func checkIfValidAnalyzer(flagtypes string) error {
if flagtypes == "" {
return nil
return errors.New("Please provide at least one analyzer to run")
}
analyzers := strings.Split(flagtypes, ",")
for _, name := range analyzers {
Expand Down
2 changes: 1 addition & 1 deletion differs/differs.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func GetAnalyzers(analyzeNames []string) (analyzeFuncs []Analyzer, err error) {
if a, exists := Analyzers[name]; exists {
analyzeFuncs = append(analyzeFuncs, a)
} else {
glog.Errorf("Unknown analyzer/differ specified", name)
glog.Errorf("Unknown analyzer/differ specified: %s", name)
}
}
if len(analyzeFuncs) == 0 {
Expand Down
2 changes: 2 additions & 0 deletions pkg/util/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/containers/image/docker:go_default_library",
"//vendor/github.com/containers/image/docker/daemon:go_default_library",
"//vendor/github.com/containers/image/docker/reference:go_default_library",
"//vendor/github.com/containers/image/docker/tarfile:go_default_library",
"//vendor/github.com/containers/image/pkg/compression:go_default_library",
"//vendor/github.com/containers/image/types:go_default_library",
Expand Down
13 changes: 8 additions & 5 deletions pkg/util/cloud_prepper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import (
"regexp"

"github.com/containers/image/docker"
"github.com/containers/image/docker/reference"
)

const RemotePrefix = "remote://"

// CloudPrepper prepares images sourced from a Cloud registry
type CloudPrepper struct {
ImagePrepper
*ImagePrepper
}

func (p CloudPrepper) Name() string {
Expand All @@ -36,12 +39,12 @@ func (p CloudPrepper) GetSource() string {
}

func (p CloudPrepper) SupportsImage() bool {
pattern := regexp.MustCompile("^.+/.+(:.+){0,1}$")
image := p.ImagePrepper.Source
if exp := pattern.FindString(image); exp != image || CheckTar(image) {
daemonRegex := regexp.MustCompile(DaemonPrefix + ".*")
if match := daemonRegex.MatchString(p.ImagePrepper.Source); match {
return false
}
return true
_, err := reference.Parse(p.ImagePrepper.Source)
return (err == nil) && !IsTar(p.ImagePrepper.Source)
}

func (p CloudPrepper) GetFileSystem() (string, error) {
Expand Down
21 changes: 10 additions & 11 deletions pkg/util/daemon_prepper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ package util

import (
"context"
"os"
"regexp"
"strings"

"github.com/containers/image/docker/daemon"
"github.com/containers/image/docker/reference"
"github.com/golang/glog"
)

const DaemonPrefix = "daemon://"

type DaemonPrepper struct {
ImagePrepper
*ImagePrepper
}

func (p DaemonPrepper) Name() string {
Expand All @@ -37,21 +40,17 @@ func (p DaemonPrepper) GetSource() string {
}

func (p DaemonPrepper) SupportsImage() bool {
pattern := regexp.MustCompile("[a-z|0-9]{12}")
if exp := pattern.FindString(p.ImagePrepper.Source); exp != p.ImagePrepper.Source {
return false
}
return true
_, err := reference.Parse(strings.Replace(p.ImagePrepper.Source, DaemonPrefix, "", -1))
return (err != nil) && !IsTar(p.ImagePrepper.Source)
}

func (p DaemonPrepper) GetFileSystem() (string, error) {
tarPath, err := saveImageToTar(p.Client, p.Source, p.Source)
ref, err := daemon.ParseReference(p.Source)
if err != nil {
return "", err
}

defer os.Remove(tarPath)
return getImageFromTar(tarPath)
return getFileSystemFromReference(ref, p.Source)
}

func (p DaemonPrepper) GetConfig() (ConfigSchema, error) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/util/image_prep_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ import (
"github.com/golang/glog"
)

var orderedPreppers = []func(ip ImagePrepper) Prepper{
func(ip ImagePrepper) Prepper { return DaemonPrepper{ImagePrepper: ip} },
func(ip ImagePrepper) Prepper { return CloudPrepper{ImagePrepper: ip} },
func(ip ImagePrepper) Prepper { return TarPrepper{ImagePrepper: ip} },
var orderedPreppers = []func(ip *ImagePrepper) Prepper{
func(ip *ImagePrepper) Prepper { return DaemonPrepper{ImagePrepper: ip} },
func(ip *ImagePrepper) Prepper { return CloudPrepper{ImagePrepper: ip} },
func(ip *ImagePrepper) Prepper { return TarPrepper{ImagePrepper: ip} },
}

type Image struct {
Expand Down
34 changes: 33 additions & 1 deletion pkg/util/image_prepper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package util

import (
"errors"
"fmt"
"strings"

"github.com/docker/docker/client"
"github.com/golang/glog"
Expand All @@ -36,12 +38,42 @@ type Prepper interface {
SupportsImage() bool
}

func (p ImagePrepper) GetImage() (Image, error) {
func getImage(prepper Prepper) (Image, error) {
imgPath, err := prepper.GetFileSystem()
if err != nil {
return Image{}, fmt.Errorf("error msg: %s", err.Error())
}

config, err := prepper.GetConfig()
if err != nil {
return Image{}, fmt.Errorf("error msg: %s", err.Error())
}

glog.Infof("Finished prepping image %s", prepper.GetSource())
return Image{
Source: prepper.GetSource(),
FSPath: imgPath,
Config: config,
}, nil
}

func (p *ImagePrepper) GetImage() (Image, error) {
glog.Infof("Starting prep for image %s", p.Source)
img := p.Source

var prepper Prepper

// first, respect prefixes on image names
if strings.HasPrefix(p.Source, "daemon://") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this should be redundant with the loop below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was stripping off the prefix of the image source before creating the prepper, but since the rest of the logic was essentially redundant I moved this into the SupportsImage() method.

p.Source = strings.Replace(p.Source, "daemon://", "", -1)
prepper = DaemonPrepper{ImagePrepper: p}
return getImage(prepper)
} else if strings.HasPrefix(p.Source, "remote://") {
p.Source = strings.Replace(p.Source, "remote://", "", -1)
prepper = CloudPrepper{ImagePrepper: p}
return getImage(prepper)
}

for _, prepperConstructor := range orderedPreppers {
prepper = prepperConstructor(p)
if prepper.SupportsImage() {
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/tar_prepper.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
)

type TarPrepper struct {
ImagePrepper
*ImagePrepper
}

func (p TarPrepper) Name() string {
Expand Down
4 changes: 3 additions & 1 deletion pkg/util/tar_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ func UnTar(filename string, target string) error {
}

func IsTar(path string) bool {
return filepath.Ext(path) == ".tar"
return filepath.Ext(path) == ".tar" ||
filepath.Ext(path) == ".tar.gz" ||
filepath.Ext(path) == ".tgz"
}

func CheckTar(image string) bool {
Expand Down
33 changes: 33 additions & 0 deletions tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ package tests

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/docker/docker/client"
"github.com/docker/docker/api/types"
)

const (
Expand All @@ -43,6 +48,9 @@ const (

multiBase = "gcr.io/gcp-runtimes/multi-base"
multiModified = "gcr.io/gcp-runtimes/multi-modified"

multiBaseLocal = "daemon://gcr.io/gcp-runtimes/multi-base"
multiModifiedLocal = "daemon://gcr.io/gcp-runtimes/multi-modified"
)

type ContainerDiffRunner struct {
Expand Down Expand Up @@ -115,6 +123,14 @@ func TestDiffAndAnalysis(t *testing.T) {
differFlags: []string{"--types=node,pip,apt"},
expectedFile: "multi_diff_expected.json",
},
{
description: "multi differ local",
subcommand: "diff",
imageA: multiBaseLocal,
imageB: multiModifiedLocal,
differFlags: []string{"--types=node,pip,apt"},
expectedFile: "multi_diff_expected.json",
},
{
description: "history differ",
subcommand: "diff",
Expand Down Expand Up @@ -185,3 +201,20 @@ func TestDiffAndAnalysis(t *testing.T) {
})
}
}

func TestMain(m *testing.M) {
// setup
ctx := context.Background()
cli, _ := client.NewEnvClient()
closer, err := cli.ImagePull(ctx, multiBase, types.ImagePullOptions{})
if err != nil {
os.Exit(1)
}
closer.Close()
closer, err = cli.ImagePull(ctx, multiModified, types.ImagePullOptions{})
if err != nil {
os.Exit(1)
}
closer.Close()
os.Exit(m.Run())
}
4 changes: 0 additions & 4 deletions tests/test_analyzer_runs.txt

This file was deleted.

6 changes: 0 additions & 6 deletions tests/test_differ_runs.txt

This file was deleted.

Loading