-
Notifications
You must be signed in to change notification settings - Fork 237
Add rpmlayer differ #252
Add rpmlayer differ #252
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -34,6 +34,8 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/google/go-containerregistry/pkg/name" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/google/go-containerregistry/pkg/v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/google/go-containerregistry/pkg/v1/daemon" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/google/go-containerregistry/pkg/v1/mutate" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/google/go-containerregistry/pkg/v1/random" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/GoogleContainerTools/container-diff/util" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -100,39 +102,31 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn | |||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
packages, err := rpmDataFromImageFS(image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Info("Running RPM binary from image in a container") | ||||||||||||||||||||||||||||||||||||||||||||||||||
return rpmDataFromContainer(image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Info("Couldn't retrieve RPM data from extracted filesystem; running query in container") | ||||||||||||||||||||||||||||||||||||||||||||||||||
return rpmDataFromContainer(image.Image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmDataFromImageFS runs a local rpm binary, if any, to query the image | ||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmdb and returns a map of installed packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDataFromImageFS(image pkgutil.Image) (map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
packages := make(map[string]util.PackageInfo) | ||||||||||||||||||||||||||||||||||||||||||||||||||
// Check there is an executable rpm tool in host | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err := exec.Command("rpm", "--version").Run(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Warn("No RPM binary in host") | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
dbPath, err := rpmDBPath(image.FSPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||
dbPath, err := rpmEnvCheck(image.FSPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Warnf("Couldn't find RPM database: %s", err.Error()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
cmdArgs := append([]string{"--root", image.FSPath, "--dbpath", dbPath}, rpmCmd[1:]...) | ||||||||||||||||||||||||||||||||||||||||||||||||||
out, err := exec.Command(rpmCmd[0], cmdArgs...).Output() | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Warnf("RPM call failed: %s", err.Error()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
output := strings.Split(string(out), "\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||
return parsePackageData(output) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return rpmDataFromFS(image.FSPath, dbPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmDBPath tries to get the RPM database path from the /usr/lib/rpm/macros | ||||||||||||||||||||||||||||||||||||||||||||||||||
// file in the image rootfs. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDBPath(rootFSPath string) (string, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmEnvCheck checks there is an rpm binary in the host and tries to | ||||||||||||||||||||||||||||||||||||||||||||||||||
// get the RPM database path from the /usr/lib/rpm/macros file in the | ||||||||||||||||||||||||||||||||||||||||||||||||||
// image rootfs | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmEnvCheck(rootFSPath string) (string, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err := exec.Command("rpm", "--version").Run(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Warn("No RPM binary in host") | ||||||||||||||||||||||||||||||||||||||||||||||||||
return "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
imgMacrosFile, err := os.Open(filepath.Join(rootFSPath, rpmMacros)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -164,7 +158,7 @@ func rpmDBPath(rootFSPath string) (string, error) { | |||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmDataFromContainer runs image in a container, queries the data of | ||||||||||||||||||||||||||||||||||||||||||||||||||
// installed rpm packages and returns a map of packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDataFromContainer(image v1.Image) (map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
packages := make(map[string]util.PackageInfo) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
client, err := godocker.NewClientFromEnv() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -175,7 +169,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err | |||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
imageName, err := loadImageToDaemon(image.Image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
imageName, err := loadImageToDaemon(image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, fmt.Errorf("Error loading image: %s", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -364,3 +358,120 @@ func unlock() error { | |||||||||||||||||||||||||||||||||||||||||||||||||
daemonMutex.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||
return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
type RPMLayerAnalyzer struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// Name returns the name of the analyzer. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func (a RPMLayerAnalyzer) Name() string { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return "RPMLayerAnalyzer" | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// Diff compares the installed rpm packages of image1 and image2 for each layer | ||||||||||||||||||||||||||||||||||||||||||||||||||
func (a RPMLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
diff, err := singleVersionLayerDiff(image1, image2, a) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return diff, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// Analyze collects information of the installed rpm packages on each layer | ||||||||||||||||||||||||||||||||||||||||||||||||||
func (a RPMLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
analysis, err := singleVersionLayerAnalysis(image, a) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return analysis, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// getPackages returns an array of maps of installed rpm packages on each layer | ||||||||||||||||||||||||||||||||||||||||||||||||||
func (a RPMLayerAnalyzer) getPackages(image pkgutil.Image) ([]map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
path := image.FSPath | ||||||||||||||||||||||||||||||||||||||||||||||||||
var packages []map[string]util.PackageInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||
if _, err := os.Stat(path); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
// invalid image directory path | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// try to find the rpm binary in bin/ or usr/bin/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
rpmBinary := filepath.Join(path, "bin/rpm") | ||||||||||||||||||||||||||||||||||||||||||||||||||
if _, err := os.Stat(rpmBinary); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
rpmBinary = filepath.Join(path, "usr/bin/rpm") | ||||||||||||||||||||||||||||||||||||||||||||||||||
if _, err = os.Stat(rpmBinary); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Errorf("Could not detect RPM binary in unpacked image %s", image.Source) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
packages, err := rpmDataFromLayerFS(image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Info("Couldn't retrieve RPM data from extracted filesystem; running query in container") | ||||||||||||||||||||||||||||||||||||||||||||||||||
return rpmDataFromLayeredContainers(image.Image) | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmDataFromLayerFS runs a local rpm binary, if any, to query the layer | ||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmdb and returns an array of maps of installed packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDataFromLayerFS(image pkgutil.Image) ([]map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic in this function is really similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was also thinking about it, I can try to move some part to another function an see how it looks like. I think only lines 435->444 are clear candidates to be moved to another function. |
||||||||||||||||||||||||||||||||||||||||||||||||||
var packages []map[string]util.PackageInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||
dbPath, err := rpmEnvCheck(image.FSPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Warnf("Couldn't find RPM database: %s", err.Error()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
for _, layer := range image.Layers { | ||||||||||||||||||||||||||||||||||||||||||||||||||
layerPackages, err := rpmDataFromFS(layer.FSPath, dbPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
packages = append(packages, layerPackages) | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmDataFromFS runs a local rpm binary to query the image | ||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmdb and returns a map of installed packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDataFromFS(fsPath string, dbPath string) (map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
packages := make(map[string]util.PackageInfo) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if _, err := os.Stat(filepath.Join(fsPath, dbPath)); err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
cmdArgs := append([]string{"--root", fsPath, "--dbpath", dbPath}, rpmCmd[1:]...) | ||||||||||||||||||||||||||||||||||||||||||||||||||
out, err := exec.Command(rpmCmd[0], cmdArgs...).Output() | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
logrus.Warnf("RPM call failed: %s", err.Error()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
output := strings.Split(string(out), "\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||
packages, err := parsePackageData(output) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// rpmDataFromLayeredContainers runs a tmp image in a container for each layer, | ||||||||||||||||||||||||||||||||||||||||||||||||||
// queries the data of installed rpm packages and returns an array of maps of | ||||||||||||||||||||||||||||||||||||||||||||||||||
// packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
func rpmDataFromLayeredContainers(image v1.Image) ([]map[string]util.PackageInfo, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
var packages []map[string]util.PackageInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||
tmpImage, err := random.Image(0, 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
layers, err := image.Layers() | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
// Append layers one by one to an empty image and query rpm | ||||||||||||||||||||||||||||||||||||||||||||||||||
// database on each iteration | ||||||||||||||||||||||||||||||||||||||||||||||||||
for _, layer := range layers { | ||||||||||||||||||||||||||||||||||||||||||||||||||
tmpImage, err = mutate.AppendLayers(tmpImage, layer) | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're trying to retrieve a map of packages installed in each layer here, right? I'm wondering if we actually want to be appending each layer onto the previous layers before running e.g. if I have layer
To get around this you could just create a new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is the same situation we have in APT packages, the package database reports all the installed packages, thus in each layer we will get always the list of all packages included in current and previous layers. To work around this there is the method singleVersionLayerAnalysis container-diff/differs/package_differs.go Lines 118 to 141 in c255953
which basically performs a diff between layers. Also I am appending the layers one by one because in this case the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh right, ok I think I follow that. Thanks for the explanation! |
||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
layerPackages, err := rpmDataFromContainer(tmpImage) | ||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, err | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
packages = append(packages, layerPackages) | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
return packages, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we care about the error here? This fallback pattern makes sense to me here, but I feel like we might want to be logging this error (I know we're not doing it in
RPMAnalyzer.getPackages()
either). At the very least we should add something to the log message saying something likeCouldn't retrieve RPM data from extracted filesystem; running query in container
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, lets include some better logs