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

Commit 0be9c41

Browse files
committed
Add filesystem caching
1 parent 09ad15a commit 0be9c41

22 files changed

+550
-62
lines changed

Gopkg.lock

+9-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ go_library(
2121
"//vendor/github.com/google/go-containerregistry/pkg/v1/daemon:go_default_library",
2222
"//vendor/github.com/google/go-containerregistry/pkg/v1/remote:go_default_library",
2323
"//vendor/github.com/google/go-containerregistry/pkg/v1/tarball:go_default_library",
24+
"//vendor/github.com/mitchellh/go-homedir:go_default_library",
2425
"//vendor/github.com/sirupsen/logrus:go_default_library",
2526
"//vendor/github.com/spf13/cobra:go_default_library",
2627
"//vendor/github.com/spf13/pflag:go_default_library",

cmd/analyze.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func analyzeImage(imageName string, analyzerArgs []string) error {
6464
return err
6565
}
6666

67-
if !save {
67+
if noCache && !save {
6868
defer pkgutil.CleanupImage(image)
6969
}
7070
if err != nil {
@@ -79,11 +79,11 @@ func analyzeImage(imageName string, analyzerArgs []string) error {
7979
return fmt.Errorf("Error performing image analysis: %s", err)
8080
}
8181

82-
output.PrintToStdErr("Retrieving analyses\n")
82+
logrus.Info("retrieving analyses")
8383
outputResults(analyses)
8484

85-
if save {
86-
logrus.Infof("Image was saved at %s", image.FSPath)
85+
if noCache && save {
86+
logrus.Infof("image was saved at %s", image.FSPath)
8787
}
8888

8989
return nil

cmd/diff.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
7878
var wg sync.WaitGroup
7979
wg.Add(2)
8080

81-
output.PrintToStdErr("Starting diff on images %s and %s, using differs: %s\n", image1Arg, image2Arg, diffArgs)
81+
logrus.Infof("starting diff on images %s and %s, using differs: %s\n", image1Arg, image2Arg, diffArgs)
8282

8383
imageMap := map[string]*pkgutil.Image{
8484
image1Arg: {},
@@ -97,12 +97,12 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
9797
}
9898
wg.Wait()
9999

100-
if !save {
100+
if noCache && !save {
101101
defer pkgutil.CleanupImage(*imageMap[image1Arg])
102102
defer pkgutil.CleanupImage(*imageMap[image2Arg])
103103
}
104104

105-
output.PrintToStdErr("Computing diffs\n")
105+
logrus.Info("computing diffs")
106106
req := differs.DiffRequest{
107107
Image1: *imageMap[image1Arg],
108108
Image2: *imageMap[image2Arg],
@@ -114,14 +114,14 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
114114
outputResults(diffs)
115115

116116
if filename != "" {
117-
output.PrintToStdErr("Computing filename diffs\n")
117+
logrus.Info("computing filename diffs")
118118
err := diffFile(imageMap[image1Arg], imageMap[image2Arg])
119119
if err != nil {
120120
return err
121121
}
122122
}
123123

124-
if save {
124+
if noCache && save {
125125
logrus.Infof("Images were saved at %s and %s", imageMap[image1Arg].FSPath,
126126
imageMap[image2Arg].FSPath)
127127
}

cmd/root.go

+79-8
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import (
2222
"io/ioutil"
2323
"net/http"
2424
"os"
25+
"path/filepath"
2526
"sort"
2627
"strings"
28+
"time"
2729

2830
"github.com/google/go-containerregistry/pkg/authn"
2931
"github.com/google/go-containerregistry/pkg/name"
@@ -35,14 +37,17 @@ import (
3537
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
3638
"github.com/GoogleContainerTools/container-diff/util"
3739
"github.com/google/go-containerregistry/pkg/v1"
40+
homedir "github.com/mitchellh/go-homedir"
3841
"github.com/sirupsen/logrus"
3942
"github.com/spf13/cobra"
4043
"github.com/spf13/pflag"
4144
)
4245

4346
var json bool
47+
4448
var save bool
4549
var types diffTypes
50+
var noCache bool
4651

4752
var LogLevel string
4853
var format string
@@ -125,14 +130,17 @@ func checkIfValidAnalyzer(_ []string) error {
125130
}
126131

127132
func getImageForName(imageName string) (pkgutil.Image, error) {
128-
logrus.Infof("getting image for name %s", imageName)
133+
logrus.Infof("retrieving image: %s", imageName)
129134
var img v1.Image
130135
var err error
131136
if pkgutil.IsTar(imageName) {
137+
start := time.Now()
132138
img, err = tarball.ImageFromPath(imageName, nil)
133139
if err != nil {
134140
return pkgutil.Image{}, err
135141
}
142+
elapsed := time.Now().Sub(start)
143+
logrus.Infof("retrieving image from tar took %f seconds", elapsed.Seconds())
136144
}
137145

138146
if strings.HasPrefix(imageName, DaemonPrefix) {
@@ -144,10 +152,16 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
144152
return pkgutil.Image{}, err
145153
}
146154

147-
img, err = daemon.Image(ref, &daemon.ReadOptions{})
155+
start := time.Now()
156+
// TODO(nkubala): specify gzip.NoCompression here when functional options are supported
157+
img, err = daemon.Image(ref, &daemon.ReadOptions{
158+
Buffer: true,
159+
})
148160
if err != nil {
149161
return pkgutil.Image{}, err
150162
}
163+
elapsed := time.Now().Sub(start)
164+
logrus.Infof("retrieving image from daemon took %f seconds", elapsed.Seconds())
151165
} else {
152166
// either has remote prefix or has no prefix, in which case we force remote
153167
imageName = strings.Replace(imageName, RemotePrefix, "", -1)
@@ -159,22 +173,27 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
159173
if err != nil {
160174
return pkgutil.Image{}, err
161175
}
176+
start := time.Now()
162177
img, err = remote.Image(ref, auth, http.DefaultTransport)
163178
if err != nil {
164179
return pkgutil.Image{}, err
165180
}
181+
elapsed := time.Now().Sub(start)
182+
logrus.Infof("retrieving remote image took %f seconds", elapsed.Seconds())
166183
}
167-
// TODO(nkubala): implement caching
168184

169185
// create tempdir and extract fs into it
170186
var layers []pkgutil.Layer
171187
if includeLayers() {
188+
start := time.Now()
172189
imgLayers, err := img.Layers()
173190
if err != nil {
174191
return pkgutil.Image{}, err
175192
}
176193
for _, layer := range imgLayers {
177-
path, err := ioutil.TempDir("", strings.Replace(imageName, "/", "", -1))
194+
layerStart := time.Now()
195+
digest, err := layer.Digest()
196+
path, err := getExtractPathForName(digest.String())
178197
if err != nil {
179198
return pkgutil.Image{
180199
Layers: layers,
@@ -188,12 +207,15 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
188207
layers = append(layers, pkgutil.Layer{
189208
FSPath: path,
190209
})
210+
elapsed := time.Now().Sub(layerStart)
211+
logrus.Infof("time elapsed retrieving layer: %fs", elapsed.Seconds())
191212
}
213+
elapsed := time.Now().Sub(start)
214+
logrus.Infof("time elapsed retrieving image layers: %fs", elapsed.Seconds())
192215
}
193-
path, err := ioutil.TempDir("", strings.Replace(imageName, "/", "", -1))
194-
if err != nil {
195-
return pkgutil.Image{}, err
196-
}
216+
217+
path, err := getExtractPathForImage(imageName, img)
218+
// extract fs into provided dir
197219
if err := pkgutil.GetFileSystemForImage(img, path, nil); err != nil {
198220
return pkgutil.Image{
199221
FSPath: path,
@@ -208,6 +230,44 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
208230
}, nil
209231
}
210232

233+
func getExtractPathForImage(imageName string, image v1.Image) (string, error) {
234+
start := time.Now()
235+
digest, err := image.Digest()
236+
if err != nil {
237+
return "", err
238+
}
239+
elapsed := time.Now().Sub(start)
240+
logrus.Infof("time elapsed retrieving image digest: %fs", elapsed.Seconds())
241+
return getExtractPathForName(pkgutil.RemoveTag(imageName) + "@" + digest.String())
242+
}
243+
244+
func getExtractPathForName(name string) (string, error) {
245+
var path string
246+
var err error
247+
if !noCache {
248+
path, err = cacheDir(name)
249+
if err != nil {
250+
return "", err
251+
}
252+
// if cachedir doesn't exist, create it
253+
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
254+
err = os.MkdirAll(path, 0700)
255+
if err != nil {
256+
return "", err
257+
}
258+
logrus.Infof("caching filesystem at %s", path)
259+
}
260+
} else {
261+
// otherwise, create tempdir
262+
logrus.Infof("skipping caching")
263+
path, err = ioutil.TempDir("", strings.Replace(name, "/", "", -1))
264+
if err != nil {
265+
return "", err
266+
}
267+
}
268+
return path, nil
269+
}
270+
211271
func includeLayers() bool {
212272
for _, t := range types {
213273
if t == "layer" {
@@ -217,6 +277,16 @@ func includeLayers() bool {
217277
return false
218278
}
219279

280+
func cacheDir(imageName string) (string, error) {
281+
dir, err := homedir.Dir()
282+
if err != nil {
283+
return "", err
284+
}
285+
rootDir := filepath.Join(dir, ".container-diff", "cache")
286+
imageName = strings.Replace(imageName, string(os.PathSeparator), "", -1)
287+
return filepath.Join(rootDir, filepath.Clean(imageName)), nil
288+
}
289+
220290
func init() {
221291
RootCmd.PersistentFlags().StringVarP(&LogLevel, "verbosity", "v", "warning", "This flag controls the verbosity of container-diff.")
222292
RootCmd.PersistentFlags().StringVarP(&format, "format", "", "", "Format to output diff in.")
@@ -254,4 +324,5 @@ func addSharedFlags(cmd *cobra.Command) {
254324
cmd.Flags().VarP(&types, "type", "t", "This flag sets the list of analyzer types to use. Set it repeatedly to use multiple analyzers.")
255325
cmd.Flags().BoolVarP(&save, "save", "s", false, "Set this flag to save rather than remove the final image filesystems on exit.")
256326
cmd.Flags().BoolVarP(&util.SortSize, "order", "o", false, "Set this flag to sort any file/package results by descending size. Otherwise, they will be sorted by name.")
327+
cmd.Flags().BoolVarP(&noCache, "no-cache", "n", false, "Set this to force retrieval of image filesystem on each run.")
257328
}

pkg/util/fs_utils.go

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package util
1818

1919
import (
2020
"bytes"
21+
"io"
2122
"io/ioutil"
2223
"os"
2324
"path/filepath"
@@ -191,3 +192,18 @@ func HasFilepathPrefix(path, prefix string) bool {
191192
}
192193
return true
193194
}
195+
196+
// given a path to a directory, check if it has any contents
197+
func DirIsEmpty(path string) (bool, error) {
198+
f, err := os.Open(path)
199+
if err != nil {
200+
return false, err
201+
}
202+
defer f.Close()
203+
204+
_, err = f.Readdir(1)
205+
if err == io.EOF {
206+
return true, nil
207+
}
208+
return false, err
209+
}

pkg/util/image_utils.go

+23-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import (
3333
"github.com/sirupsen/logrus"
3434
)
3535

36+
const tagRegexStr = ".*:([^/]+$)"
37+
3638
type Layer struct {
3739
FSPath string
3840
}
@@ -83,7 +85,16 @@ func GetFileSystemForLayer(layer v1.Layer, root string, whitelist []string) erro
8385
}
8486

8587
// unpack image filesystem to local disk
88+
// if provided directory is not empty, do nothing
8689
func GetFileSystemForImage(image v1.Image, root string, whitelist []string) error {
90+
empty, err := DirIsEmpty(root)
91+
if err != nil {
92+
return err
93+
}
94+
if !empty {
95+
logrus.Infof("using cached filesystem in %s", root)
96+
return nil
97+
}
8798
if err := unpackTar(tar.NewReader(mutate.Extract(image)), root, whitelist); err != nil {
8899
return err
89100
}
@@ -134,6 +145,17 @@ func copyToFile(outfile string, r io.Reader) error {
134145

135146
// checks to see if an image string contains a tag.
136147
func HasTag(image string) bool {
137-
tagRegex := regexp.MustCompile(".*:[^/]+$")
148+
tagRegex := regexp.MustCompile(tagRegexStr)
138149
return tagRegex.MatchString(image)
139150
}
151+
152+
// returns a raw image name with the tag removed
153+
func RemoveTag(image string) string {
154+
if !HasTag(image) {
155+
return image
156+
}
157+
tagRegex := regexp.MustCompile(tagRegexStr)
158+
parts := tagRegex.FindStringSubmatch(image)
159+
tag := parts[len(parts)-1]
160+
return image[0 : len(image)-len(tag)-1]
161+
}

0 commit comments

Comments
 (0)