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 all 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.

20 changes: 11 additions & 9 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,25 @@ 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()
prepper, err := getPrepperForImage(imageName)
if err != nil {
return fmt.Errorf("Error getting docker client for differ: %s", err)
return err
}
defer cli.Close()
ip := pkgutil.ImagePrepper{
Source: imageArg,
Client: cli,
}
image, err := ip.GetImage()

image, err := prepper.GetImage()

if !save {
defer pkgutil.CleanupImage(image)
Expand Down
27 changes: 13 additions & 14 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 err
}

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

prepper, err := getPrepperForImage(imageName)
if err != nil {
glog.Error(err)
return
}
image, err := ip.GetImage()
image, err := prepper.GetImage()
imageMap[imageName] = &image
if err != nil {
glog.Errorf("Diff may be inaccurate: %s", err.Error())
glog.Warningf("Diff may be inaccurate: %s", err)
}
}(imageArg, imageMap)
}
Expand All @@ -102,16 +103,14 @@ 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)

if save {
glog.Infof("Images were saved at %s and %s", imageMap[image1Arg].FSPath,
imageMap[image2Arg].FSPath)

}
return nil
}
Expand Down
35 changes: 33 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import (
"strings"

"github.com/GoogleCloudPlatform/container-diff/differs"
pkgutil "github.com/GoogleCloudPlatform/container-diff/pkg/util"
"github.com/GoogleCloudPlatform/container-diff/util"

"github.com/docker/docker/client"
"github.com/golang/glog"
"github.com/pkg/errors"
Expand All @@ -38,6 +40,11 @@ var types string

type validatefxn func(args []string) error

const (
DaemonPrefix = "daemon://"
RemotePrefix = "remote://"
)

var RootCmd = &cobra.Command{
Use: "container-diff",
Short: "container-diff is a tool for analyzing and comparing container images",
Expand All @@ -47,7 +54,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 +100,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 All @@ -106,6 +113,30 @@ func checkIfValidAnalyzer(flagtypes string) error {
return nil
}

func getPrepperForImage(image string) (pkgutil.Prepper, error) {
cli, err := client.NewEnvClient()
if err != nil {
return nil, err
}
if pkgutil.IsTar(image) {
return pkgutil.TarPrepper{
Source: image,
Client: cli,
}, nil

} else if strings.HasPrefix(image, DaemonPrefix) {
return pkgutil.DaemonPrepper{
Source: strings.Replace(image, DaemonPrefix, "", -1),
Client: cli,
}, nil
}
// either has remote prefix or has no prefix, in which case we force remote
return pkgutil.CloudPrepper{
Source: strings.Replace(image, RemotePrefix, "", -1),
Client: cli,
}, nil
}

func init() {
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
}
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
17 changes: 6 additions & 11 deletions pkg/util/cloud_prepper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,26 @@ limitations under the License.
package util

import (
"regexp"

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

// CloudPrepper prepares images sourced from a Cloud registry
type CloudPrepper struct {
ImagePrepper
Source string
Client *client.Client
}

func (p CloudPrepper) Name() string {
return "Cloud Registry"
}

func (p CloudPrepper) GetSource() string {
return p.ImagePrepper.Source
return p.Source
}

func (p CloudPrepper) SupportsImage() bool {
pattern := regexp.MustCompile("^.+/.+(:.+){0,1}$")
image := p.ImagePrepper.Source
if exp := pattern.FindString(image); exp != image || CheckTar(image) {
return false
}
return true
func (p CloudPrepper) GetImage() (Image, error) {
return getImage(p)
Copy link
Contributor

Choose a reason for hiding this comment

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

is getImage used anywhere else? Should you just inline it here instead of breaking it out?

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 it's actually the same across all the preppers (currently), but it made more sense to me to just have them all use a helper method and expose the GetImage() publicly on each of the preppers. WDYT?

}

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

import (
"context"
"os"
"regexp"

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

type DaemonPrepper struct {
ImagePrepper
Source string
Client *client.Client
}

func (p DaemonPrepper) Name() string {
return "Local Daemon"
}

func (p DaemonPrepper) GetSource() string {
return p.ImagePrepper.Source
return p.Source
}

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
func (p DaemonPrepper) GetImage() (Image, error) {
return getImage(p)
}

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) {
inspect, _, err := p.Client.ImageInspectWithRaw(context.Background(), p.Source)
ref, err := daemon.ParseReference(p.Source)
if err != nil {
return ConfigSchema{}, err
}

config := ConfigObject{
Env: inspect.Config.Env,
}
history := p.GetHistory()
return ConfigSchema{
Config: config,
History: history,
}, nil
return getConfigFromReference(ref, p.Source)
}

func (p DaemonPrepper) GetHistory() []ImageHistoryItem {
Expand Down
32 changes: 28 additions & 4 deletions pkg/util/image_prep_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"archive/tar"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -30,10 +31,12 @@ 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} },
type Prepper interface {
Name() string
GetConfig() (ConfigSchema, error)
GetFileSystem() (string, error)
GetImage() (Image, error)
GetSource() string
}

type Image struct {
Expand All @@ -55,6 +58,27 @@ type ConfigSchema struct {
History []ImageHistoryItem `json:"history"`
}

func getImage(p Prepper) (Image, error) {
glog.Infof("Retrieving image %s from source %s", p.GetSource(), p.Name())
imgPath, err := p.GetFileSystem()
if err != nil {
return Image{}, err
}

config, err := p.GetConfig()
if err != nil {
glog.Error("Error retrieving History: ", err)
}

glog.Infof("Finished prepping image %s", p.GetSource())
return Image{
Source: p.GetSource(),
FSPath: imgPath,
Config: config,
}, nil
return Image{}, fmt.Errorf("Could not retrieve image %s from source", p.GetSource())
}

func getImageFromTar(tarPath string) (string, error) {
glog.Info("Extracting image tar to obtain image file system")
path := strings.TrimSuffix(tarPath, filepath.Ext(tarPath))
Expand Down
Loading