@@ -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"
@@ -100,39 +102,31 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn
100102
101103 packages , err := rpmDataFromImageFS (image )
102104 if err != nil {
103- logrus .Info ("Running RPM binary from image in a container" )
104- return rpmDataFromContainer (image )
105+ logrus .Info ("Couldn't retrieve RPM data from extracted filesystem; running query in container" )
106+ return rpmDataFromContainer (image . Image )
105107 }
106108 return packages , err
107109}
108110
109111// rpmDataFromImageFS runs a local rpm binary, if any, to query the image
110112// rpmdb and returns a map of installed packages.
111113func rpmDataFromImageFS (image pkgutil.Image ) (map [string ]util.PackageInfo , error ) {
112- packages := make (map [string ]util.PackageInfo )
113- // Check there is an executable rpm tool in host
114- if err := exec .Command ("rpm" , "--version" ).Run (); err != nil {
115- logrus .Warn ("No RPM binary in host" )
116- return packages , err
117- }
118- dbPath , err := rpmDBPath (image .FSPath )
114+ dbPath , err := rpmEnvCheck (image .FSPath )
119115 if err != nil {
120116 logrus .Warnf ("Couldn't find RPM database: %s" , err .Error ())
121- return packages , err
117+ return nil , err
122118 }
123- cmdArgs := append ([]string {"--root" , image .FSPath , "--dbpath" , dbPath }, rpmCmd [1 :]... )
124- out , err := exec .Command (rpmCmd [0 ], cmdArgs ... ).Output ()
125- if err != nil {
126- logrus .Warnf ("RPM call failed: %s" , err .Error ())
127- return packages , err
128- }
129- output := strings .Split (string (out ), "\n " )
130- return parsePackageData (output )
119+ return rpmDataFromFS (image .FSPath , dbPath )
131120}
132121
133- // rpmDBPath tries to get the RPM database path from the /usr/lib/rpm/macros
134- // file in the image rootfs.
135- func rpmDBPath (rootFSPath string ) (string , error ) {
122+ // rpmEnvCheck checks there is an rpm binary in the host and tries to
123+ // get the RPM database path from the /usr/lib/rpm/macros file in the
124+ // image rootfs
125+ func rpmEnvCheck (rootFSPath string ) (string , error ) {
126+ if err := exec .Command ("rpm" , "--version" ).Run (); err != nil {
127+ logrus .Warn ("No RPM binary in host" )
128+ return "" , err
129+ }
136130 imgMacrosFile , err := os .Open (filepath .Join (rootFSPath , rpmMacros ))
137131 if err != nil {
138132 return "" , err
@@ -164,7 +158,7 @@ func rpmDBPath(rootFSPath string) (string, error) {
164158
165159// rpmDataFromContainer runs image in a container, queries the data of
166160// installed rpm packages and returns a map of packages.
167- func rpmDataFromContainer (image pkgutil .Image ) (map [string ]util.PackageInfo , error ) {
161+ func rpmDataFromContainer (image v1 .Image ) (map [string ]util.PackageInfo , error ) {
168162 packages := make (map [string ]util.PackageInfo )
169163
170164 client , err := godocker .NewClientFromEnv ()
@@ -175,7 +169,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
175169 return packages , err
176170 }
177171
178- imageName , err := loadImageToDaemon (image . Image )
172+ imageName , err := loadImageToDaemon (image )
179173
180174 if err != nil {
181175 return packages , fmt .Errorf ("Error loading image: %s" , err )
@@ -364,3 +358,120 @@ func unlock() error {
364358 daemonMutex .Unlock ()
365359 return nil
366360}
361+
362+ type RPMLayerAnalyzer struct {
363+ }
364+
365+ // Name returns the name of the analyzer.
366+ func (a RPMLayerAnalyzer ) Name () string {
367+ return "RPMLayerAnalyzer"
368+ }
369+
370+ // Diff compares the installed rpm packages of image1 and image2 for each layer
371+ func (a RPMLayerAnalyzer ) Diff (image1 , image2 pkgutil.Image ) (util.Result , error ) {
372+ diff , err := singleVersionLayerDiff (image1 , image2 , a )
373+ return diff , err
374+ }
375+
376+ // Analyze collects information of the installed rpm packages on each layer
377+ func (a RPMLayerAnalyzer ) Analyze (image pkgutil.Image ) (util.Result , error ) {
378+ analysis , err := singleVersionLayerAnalysis (image , a )
379+ return analysis , err
380+ }
381+
382+ // getPackages returns an array of maps of installed rpm packages on each layer
383+ func (a RPMLayerAnalyzer ) getPackages (image pkgutil.Image ) ([]map [string ]util.PackageInfo , error ) {
384+ path := image .FSPath
385+ var packages []map [string ]util.PackageInfo
386+ if _ , err := os .Stat (path ); err != nil {
387+ // invalid image directory path
388+ return packages , err
389+ }
390+
391+ // try to find the rpm binary in bin/ or usr/bin/
392+ rpmBinary := filepath .Join (path , "bin/rpm" )
393+ if _ , err := os .Stat (rpmBinary ); err != nil {
394+ rpmBinary = filepath .Join (path , "usr/bin/rpm" )
395+ if _ , err = os .Stat (rpmBinary ); err != nil {
396+ logrus .Errorf ("Could not detect RPM binary in unpacked image %s" , image .Source )
397+ return packages , nil
398+ }
399+ }
400+
401+ packages , err := rpmDataFromLayerFS (image )
402+ if err != nil {
403+ logrus .Info ("Couldn't retrieve RPM data from extracted filesystem; running query in container" )
404+ return rpmDataFromLayeredContainers (image .Image )
405+ }
406+ return packages , err
407+ }
408+
409+ // rpmDataFromLayerFS runs a local rpm binary, if any, to query the layer
410+ // rpmdb and returns an array of maps of installed packages.
411+ func rpmDataFromLayerFS (image pkgutil.Image ) ([]map [string ]util.PackageInfo , error ) {
412+ var packages []map [string ]util.PackageInfo
413+ dbPath , err := rpmEnvCheck (image .FSPath )
414+ if err != nil {
415+ logrus .Warnf ("Couldn't find RPM database: %s" , err .Error ())
416+ return packages , err
417+ }
418+ for _ , layer := range image .Layers {
419+ layerPackages , err := rpmDataFromFS (layer .FSPath , dbPath )
420+ if err != nil {
421+ return packages , err
422+ }
423+ packages = append (packages , layerPackages )
424+ }
425+
426+ return packages , nil
427+ }
428+
429+ // rpmDataFromFS runs a local rpm binary to query the image
430+ // rpmdb and returns a map of installed packages.
431+ func rpmDataFromFS (fsPath string , dbPath string ) (map [string ]util.PackageInfo , error ) {
432+ packages := make (map [string ]util.PackageInfo )
433+ if _ , err := os .Stat (filepath .Join (fsPath , dbPath )); err == nil {
434+ cmdArgs := append ([]string {"--root" , fsPath , "--dbpath" , dbPath }, rpmCmd [1 :]... )
435+ out , err := exec .Command (rpmCmd [0 ], cmdArgs ... ).Output ()
436+ if err != nil {
437+ logrus .Warnf ("RPM call failed: %s" , err .Error ())
438+ return packages , err
439+ }
440+ output := strings .Split (string (out ), "\n " )
441+ packages , err := parsePackageData (output )
442+ if err != nil {
443+ return packages , err
444+ }
445+ }
446+ return packages , nil
447+ }
448+
449+ // rpmDataFromLayeredContainers runs a tmp image in a container for each layer,
450+ // queries the data of installed rpm packages and returns an array of maps of
451+ // packages.
452+ func rpmDataFromLayeredContainers (image v1.Image ) ([]map [string ]util.PackageInfo , error ) {
453+ var packages []map [string ]util.PackageInfo
454+ tmpImage , err := random .Image (0 , 0 )
455+ if err != nil {
456+ return packages , err
457+ }
458+ layers , err := image .Layers ()
459+ if err != nil {
460+ return packages , err
461+ }
462+ // Append layers one by one to an empty image and query rpm
463+ // database on each iteration
464+ for _ , layer := range layers {
465+ tmpImage , err = mutate .AppendLayers (tmpImage , layer )
466+ if err != nil {
467+ return packages , err
468+ }
469+ layerPackages , err := rpmDataFromContainer (tmpImage )
470+ if err != nil {
471+ return packages , err
472+ }
473+ packages = append (packages , layerPackages )
474+ }
475+
476+ return packages , nil
477+ }
0 commit comments