Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.

Commit 23d0231

Browse files
committed
Add rpmlayer differ
This commit adds the rpmlayer differ that allows to perform analysis at rpm package level on each layer. Lists new, deleted and modified (different version) packages on each layer. Signed-off-by: David Cassany <[email protected]>
1 parent 509496e commit 23d0231

File tree

3 files changed

+119
-4
lines changed

3 files changed

+119
-4
lines changed

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const (
5959
RemotePrefix = "remote://"
6060
)
6161

62-
var layerAnalyzers = [...]string{"layer", "aptlayer"}
62+
var layerAnalyzers = [...]string{"layer", "aptlayer", "rpmlayer"}
6363

6464
var RootCmd = &cobra.Command{
6565
Use: "container-diff",

differs/differs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ var Analyzers = map[string]Analyzer{
4949
"apt": AptAnalyzer{},
5050
"aptlayer": AptLayerAnalyzer{},
5151
"rpm": RPMAnalyzer{},
52+
"rpmlayer": RPMLayerAnalyzer{},
5253
"pip": PipAnalyzer{},
5354
"node": NodeAnalyzer{},
5455
}

differs/rpm_diff.go

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)