@@ -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"
@@ -100,39 +102,31 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn
100
102
101
103
packages , err := rpmDataFromImageFS (image )
102
104
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 )
105
107
}
106
108
return packages , err
107
109
}
108
110
109
111
// rpmDataFromImageFS runs a local rpm binary, if any, to query the image
110
112
// rpmdb and returns a map of installed packages.
111
113
func 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 )
119
115
if err != nil {
120
116
logrus .Warnf ("Couldn't find RPM database: %s" , err .Error ())
121
- return packages , err
117
+ return nil , err
122
118
}
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 )
131
120
}
132
121
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
+ }
136
130
imgMacrosFile , err := os .Open (filepath .Join (rootFSPath , rpmMacros ))
137
131
if err != nil {
138
132
return "" , err
@@ -164,7 +158,7 @@ func rpmDBPath(rootFSPath string) (string, error) {
164
158
165
159
// rpmDataFromContainer runs image in a container, queries the data of
166
160
// 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 ) {
168
162
packages := make (map [string ]util.PackageInfo )
169
163
170
164
client , err := godocker .NewClientFromEnv ()
@@ -175,7 +169,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
175
169
return packages , err
176
170
}
177
171
178
- imageName , err := loadImageToDaemon (image . Image )
172
+ imageName , err := loadImageToDaemon (image )
179
173
180
174
if err != nil {
181
175
return packages , fmt .Errorf ("Error loading image: %s" , err )
@@ -364,3 +358,120 @@ func unlock() error {
364
358
daemonMutex .Unlock ()
365
359
return nil
366
360
}
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