@@ -22,8 +22,10 @@ import (
22
22
"io/ioutil"
23
23
"net/http"
24
24
"os"
25
+ "path/filepath"
25
26
"sort"
26
27
"strings"
28
+ "time"
27
29
28
30
"github.com/google/go-containerregistry/pkg/authn"
29
31
"github.com/google/go-containerregistry/pkg/name"
@@ -35,14 +37,17 @@ import (
35
37
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
36
38
"github.com/GoogleContainerTools/container-diff/util"
37
39
"github.com/google/go-containerregistry/pkg/v1"
40
+ homedir "github.com/mitchellh/go-homedir"
38
41
"github.com/sirupsen/logrus"
39
42
"github.com/spf13/cobra"
40
43
"github.com/spf13/pflag"
41
44
)
42
45
43
46
var json bool
47
+
44
48
var save bool
45
49
var types diffTypes
50
+ var noCache bool
46
51
47
52
var LogLevel string
48
53
var format string
@@ -125,14 +130,17 @@ func checkIfValidAnalyzer(_ []string) error {
125
130
}
126
131
127
132
func getImageForName (imageName string ) (pkgutil.Image , error ) {
128
- logrus .Infof ("getting image for name %s" , imageName )
133
+ logrus .Infof ("retrieving image: %s" , imageName )
129
134
var img v1.Image
130
135
var err error
131
136
if pkgutil .IsTar (imageName ) {
137
+ start := time .Now ()
132
138
img , err = tarball .ImageFromPath (imageName , nil )
133
139
if err != nil {
134
140
return pkgutil.Image {}, err
135
141
}
142
+ elapsed := time .Now ().Sub (start )
143
+ logrus .Infof ("retrieving image from tar took %f seconds" , elapsed .Seconds ())
136
144
}
137
145
138
146
if strings .HasPrefix (imageName , DaemonPrefix ) {
@@ -144,10 +152,16 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
144
152
return pkgutil.Image {}, err
145
153
}
146
154
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
+ })
148
160
if err != nil {
149
161
return pkgutil.Image {}, err
150
162
}
163
+ elapsed := time .Now ().Sub (start )
164
+ logrus .Infof ("retrieving image from daemon took %f seconds" , elapsed .Seconds ())
151
165
} else {
152
166
// either has remote prefix or has no prefix, in which case we force remote
153
167
imageName = strings .Replace (imageName , RemotePrefix , "" , - 1 )
@@ -159,22 +173,27 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
159
173
if err != nil {
160
174
return pkgutil.Image {}, err
161
175
}
176
+ start := time .Now ()
162
177
img , err = remote .Image (ref , auth , http .DefaultTransport )
163
178
if err != nil {
164
179
return pkgutil.Image {}, err
165
180
}
181
+ elapsed := time .Now ().Sub (start )
182
+ logrus .Infof ("retrieving remote image took %f seconds" , elapsed .Seconds ())
166
183
}
167
- // TODO(nkubala): implement caching
168
184
169
185
// create tempdir and extract fs into it
170
186
var layers []pkgutil.Layer
171
187
if includeLayers () {
188
+ start := time .Now ()
172
189
imgLayers , err := img .Layers ()
173
190
if err != nil {
174
191
return pkgutil.Image {}, err
175
192
}
176
193
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 ())
178
197
if err != nil {
179
198
return pkgutil.Image {
180
199
Layers : layers ,
@@ -188,12 +207,15 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
188
207
layers = append (layers , pkgutil.Layer {
189
208
FSPath : path ,
190
209
})
210
+ elapsed := time .Now ().Sub (layerStart )
211
+ logrus .Infof ("time elapsed retrieving layer: %fs" , elapsed .Seconds ())
191
212
}
213
+ elapsed := time .Now ().Sub (start )
214
+ logrus .Infof ("time elapsed retrieving image layers: %fs" , elapsed .Seconds ())
192
215
}
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
197
219
if err := pkgutil .GetFileSystemForImage (img , path , nil ); err != nil {
198
220
return pkgutil.Image {
199
221
FSPath : path ,
@@ -208,6 +230,44 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
208
230
}, nil
209
231
}
210
232
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
+
211
271
func includeLayers () bool {
212
272
for _ , t := range types {
213
273
if t == "layer" {
@@ -217,6 +277,16 @@ func includeLayers() bool {
217
277
return false
218
278
}
219
279
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
+
220
290
func init () {
221
291
RootCmd .PersistentFlags ().StringVarP (& LogLevel , "verbosity" , "v" , "warning" , "This flag controls the verbosity of container-diff." )
222
292
RootCmd .PersistentFlags ().StringVarP (& format , "format" , "" , "" , "Format to output diff in." )
@@ -254,4 +324,5 @@ func addSharedFlags(cmd *cobra.Command) {
254
324
cmd .Flags ().VarP (& types , "type" , "t" , "This flag sets the list of analyzer types to use. Set it repeatedly to use multiple analyzers." )
255
325
cmd .Flags ().BoolVarP (& save , "save" , "s" , false , "Set this flag to save rather than remove the final image filesystems on exit." )
256
326
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." )
257
328
}
0 commit comments