@@ -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
4346var json bool
47+
4448var save bool
4549var types diffTypes
50+ var noCache bool
4651
4752var LogLevel string
4853var format string
@@ -125,14 +130,17 @@ func checkIfValidAnalyzer(_ []string) error {
125130}
126131
127132func 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+
211271func 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+
220290func 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}
0 commit comments