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

Commit 750860d

Browse files
authored
Merge pull request #252 from davidcassany/add_rpmlayer_differ
Add rpmlayer differ
2 parents c255953 + 6751a8c commit 750860d

File tree

3 files changed

+158
-35
lines changed

3 files changed

+158
-35
lines changed

cmd/root.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ const (
6060
RemotePrefix = "remote://"
6161
)
6262

63-
var layerAnalyzers = [...]string{"layer", "aptlayer"}
64-
6563
var RootCmd = &cobra.Command{
6664
Use: "container-diff",
6765
Short: "container-diff is a tool for analyzing and comparing container images",
@@ -277,7 +275,7 @@ func getExtractPathForName(name string) (string, error) {
277275

278276
func includeLayers() bool {
279277
for _, t := range types {
280-
for _, a := range layerAnalyzers {
278+
for _, a := range differs.LayerAnalyzers {
281279
if t == a {
282280
return true
283281
}

differs/differs.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ import (
2424
"github.com/sirupsen/logrus"
2525
)
2626

27+
const historyAnalyzer = "history"
28+
const metadataAnalyzer = "metadata"
29+
const fileAnalyzer = "file"
30+
const layerAnalyzer = "layer"
31+
const aptAnalyzer = "apt"
32+
const aptLayerAnalyzer = "aptlayer"
33+
const rpmAnalyzer = "rpm"
34+
const rpmLayerAnalyzer = "rpmlayer"
35+
const pipAnalyzer = "pip"
36+
const nodeAnalyzer = "node"
37+
2738
type DiffRequest struct {
2839
Image1 pkgutil.Image
2940
Image2 pkgutil.Image
@@ -42,17 +53,20 @@ type Analyzer interface {
4253
}
4354

4455
var Analyzers = map[string]Analyzer{
45-
"history": HistoryAnalyzer{},
46-
"metadata": MetadataAnalyzer{},
47-
"file": FileAnalyzer{},
48-
"layer": FileLayerAnalyzer{},
49-
"apt": AptAnalyzer{},
50-
"aptlayer": AptLayerAnalyzer{},
51-
"rpm": RPMAnalyzer{},
52-
"pip": PipAnalyzer{},
53-
"node": NodeAnalyzer{},
56+
historyAnalyzer: HistoryAnalyzer{},
57+
metadataAnalyzer: MetadataAnalyzer{},
58+
fileAnalyzer: FileAnalyzer{},
59+
layerAnalyzer: FileLayerAnalyzer{},
60+
aptAnalyzer: AptAnalyzer{},
61+
aptLayerAnalyzer: AptLayerAnalyzer{},
62+
rpmAnalyzer: RPMAnalyzer{},
63+
rpmLayerAnalyzer: RPMLayerAnalyzer{},
64+
pipAnalyzer: PipAnalyzer{},
65+
nodeAnalyzer: NodeAnalyzer{},
5466
}
5567

68+
var LayerAnalyzers = [...]string{layerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer}
69+
5670
func (req DiffRequest) GetDiff() (map[string]util.Result, error) {
5771
img1 := req.Image1
5872
img2 := req.Image2

differs/rpm_diff.go

+134-23
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"
@@ -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.
111113
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)
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

Comments
 (0)