@@ -34,6 +34,8 @@ import (
34
34
"github.com/google/go-containerregistry/pkg/name"
35
35
"github.com/google/go-containerregistry/pkg/v1"
36
36
"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"
37
39
38
40
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
39
41
"github.com/GoogleContainerTools/container-diff/util"
@@ -101,7 +103,7 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn
101
103
packages , err := rpmDataFromImageFS (image )
102
104
if err != nil {
103
105
logrus .Info ("Running RPM binary from image in a container" )
104
- return rpmDataFromContainer (image )
106
+ return rpmDataFromContainer (image . Image )
105
107
}
106
108
return packages , err
107
109
}
@@ -164,7 +166,7 @@ func rpmDBPath(rootFSPath string) (string, error) {
164
166
165
167
// rpmDataFromContainer runs image in a container, queries the data of
166
168
// 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 ) {
168
170
packages := make (map [string ]util.PackageInfo )
169
171
170
172
client , err := godocker .NewClientFromEnv ()
@@ -175,7 +177,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
175
177
return packages , err
176
178
}
177
179
178
- imageName , err := loadImageToDaemon (image . Image )
180
+ imageName , err := loadImageToDaemon (image )
179
181
180
182
if err != nil {
181
183
return packages , fmt .Errorf ("Error loading image: %s" , err )
@@ -364,3 +366,115 @@ func unlock() error {
364
366
daemonMutex .Unlock ()
365
367
return nil
366
368
}
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