@@ -34,6 +34,8 @@ import (
3434 "github.com/google/go-containerregistry/pkg/name"
3535 "github.com/google/go-containerregistry/pkg/v1"
3636 "github.com/google/go-containerregistry/pkg/v1/daemon"
37+ "github.com/google/go-containerregistry/pkg/v1/mutate"
38+ "github.com/google/go-containerregistry/pkg/v1/random"
3739
3840 pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
3941 "github.com/GoogleContainerTools/container-diff/util"
@@ -101,7 +103,7 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn
101103 packages , err := rpmDataFromImageFS (image )
102104 if err != nil {
103105 logrus .Info ("Running RPM binary from image in a container" )
104- return rpmDataFromContainer (image )
106+ return rpmDataFromContainer (image . Image )
105107 }
106108 return packages , err
107109}
@@ -164,7 +166,7 @@ func rpmDBPath(rootFSPath string) (string, error) {
164166
165167// rpmDataFromContainer runs image in a container, queries the data of
166168// installed rpm packages and returns a map of packages.
167- func rpmDataFromContainer (image pkgutil .Image ) (map [string ]util.PackageInfo , error ) {
169+ func rpmDataFromContainer (image v1 .Image ) (map [string ]util.PackageInfo , error ) {
168170 packages := make (map [string ]util.PackageInfo )
169171
170172 client , err := godocker .NewClientFromEnv ()
@@ -175,7 +177,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
175177 return packages , err
176178 }
177179
178- imageName , err := loadImageToDaemon (image . Image )
180+ imageName , err := loadImageToDaemon (image )
179181
180182 if err != nil {
181183 return packages , fmt .Errorf ("Error loading image: %s" , err )
@@ -364,3 +366,115 @@ func unlock() error {
364366 daemonMutex .Unlock ()
365367 return nil
366368}
369+
370+ type RPMLayerAnalyzer struct {
371+ }
372+
373+ // Name returns the name of the analyzer.
374+ func (a RPMLayerAnalyzer ) Name () string {
375+ return "RPMLayerAnalyzer"
376+ }
377+
378+ // Diff compares the installed rpm packages of image1 and image2 for each layer
379+ func (a RPMLayerAnalyzer ) Diff (image1 , image2 pkgutil.Image ) (util.Result , error ) {
380+ diff , err := singleVersionLayerDiff (image1 , image2 , a )
381+ return diff , err
382+ }
383+
384+ // Analyze collects information of the installed rpm packages on each layer
385+ func (a RPMLayerAnalyzer ) Analyze (image pkgutil.Image ) (util.Result , error ) {
386+ analysis , err := singleVersionLayerAnalysis (image , a )
387+ return analysis , err
388+ }
389+
390+ // getPackages returns an array of maps of installed rpm packages on each layer
391+ func (a RPMLayerAnalyzer ) getPackages (image pkgutil.Image ) ([]map [string ]util.PackageInfo , error ) {
392+ path := image .FSPath
393+ var packages []map [string ]util.PackageInfo
394+ if _ , err := os .Stat (path ); err != nil {
395+ // invalid image directory path
396+ return packages , err
397+ }
398+
399+ // try to find the rpm binary in bin/ or usr/bin/
400+ rpmBinary := filepath .Join (path , "bin/rpm" )
401+ if _ , err := os .Stat (rpmBinary ); err != nil {
402+ rpmBinary = filepath .Join (path , "usr/bin/rpm" )
403+ if _ , err = os .Stat (rpmBinary ); err != nil {
404+ logrus .Errorf ("Could not detect RPM binary in unpacked image %s" , image .Source )
405+ return packages , nil
406+ }
407+ }
408+
409+ packages , err := rpmDataFromLayerFS (image )
410+ if err != nil {
411+ logrus .Info ("Running RPM binary from image in a container" )
412+ return rpmDataFromLayeredContainers (image .Image )
413+ }
414+ return packages , err
415+ }
416+
417+ // rpmDataFromLayerFS runs a local rpm binary, if any, to query the layer
418+ // rpmdb and returns an array of maps of installed packages.
419+ func rpmDataFromLayerFS (image pkgutil.Image ) ([]map [string ]util.PackageInfo , error ) {
420+ var packages []map [string ]util.PackageInfo
421+ // Check there is an executable rpm tool in host
422+ if err := exec .Command ("rpm" , "--version" ).Run (); err != nil {
423+ logrus .Warn ("No RPM binary in host" )
424+ return packages , err
425+ }
426+ dbPath , err := rpmDBPath (image .FSPath )
427+ if err != nil {
428+ logrus .Warnf ("Couldn't find RPM database: %s" , err .Error ())
429+ return packages , err
430+ }
431+ for _ , layer := range image .Layers {
432+ layerPackages := make (map [string ]util.PackageInfo )
433+ //query only layers that include the rpm database
434+ if _ , err := os .Stat (filepath .Join (layer .FSPath , dbPath )); err == nil {
435+ cmdArgs := append ([]string {"--root" , layer .FSPath , "--dbpath" , dbPath }, rpmCmd [1 :]... )
436+ out , err := exec .Command (rpmCmd [0 ], cmdArgs ... ).Output ()
437+ if err != nil {
438+ logrus .Warnf ("RPM call failed: %s" , err .Error ())
439+ return packages , err
440+ }
441+ layerPackages , err = parsePackageData (strings .Split (string (out ), "\n " ))
442+ if err != nil {
443+ return packages , err
444+ }
445+ }
446+ packages = append (packages , layerPackages )
447+ }
448+
449+ return packages , nil
450+ }
451+
452+ // rpmDataFromLayeredContainers runs a tmp image in a container for each layer,
453+ // queries the data of installed rpm packages and returns an array of maps of
454+ // packages.
455+ func rpmDataFromLayeredContainers (image v1.Image ) ([]map [string ]util.PackageInfo , error ) {
456+ var packages []map [string ]util.PackageInfo
457+ tmpImage , err := random .Image (0 , 0 )
458+ if err != nil {
459+ return packages , err
460+ }
461+ layers , err := image .Layers ()
462+ if err != nil {
463+ return packages , err
464+ }
465+ // Append layers one by one to an empty image and query rpm
466+ // database on each iteration
467+ for _ , layer := range layers {
468+ tmpImage , err = mutate .AppendLayers (tmpImage , layer )
469+ if err != nil {
470+ return packages , err
471+ }
472+ layerPackages , err := rpmDataFromContainer (tmpImage )
473+ if err != nil {
474+ return packages , err
475+ }
476+ packages = append (packages , layerPackages )
477+ }
478+
479+ return packages , nil
480+ }
0 commit comments