diff --git a/differs/differs.go b/differs/differs.go index c810fa05..6439168c 100644 --- a/differs/differs.go +++ b/differs/differs.go @@ -36,6 +36,7 @@ const rpmAnalyzer = "rpm" const rpmLayerAnalyzer = "rpmlayer" const pipAnalyzer = "pip" const nodeAnalyzer = "node" +const emergeAnalyzer = "emerge" type DiffRequest struct { Image1 pkgutil.Image @@ -67,6 +68,7 @@ var Analyzers = map[string]Analyzer{ rpmLayerAnalyzer: RPMLayerAnalyzer{}, pipAnalyzer: PipAnalyzer{}, nodeAnalyzer: NodeAnalyzer{}, + emergeAnalyzer: EmergeAnalyzer{}, } var LayerAnalyzers = [...]string{layerAnalyzer, sizeLayerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer} diff --git a/differs/emerge_diff.go b/differs/emerge_diff.go new file mode 100644 index 00000000..b080e3f1 --- /dev/null +++ b/differs/emerge_diff.go @@ -0,0 +1,123 @@ +/* +Copyright 2018 Google, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package differs + +import ( + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util" + "github.com/GoogleContainerTools/container-diff/util" + "github.com/sirupsen/logrus" +) + +//Emerge package database location +const emergePkgFile string = "/var/db/pkg" + +type EmergeAnalyzer struct { +} + +func (em EmergeAnalyzer) Name() string { + return "EmergeAnalyzer" +} + +// Diff compares the packages installed by emerge. +func (em EmergeAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { + diff, err := singleVersionDiff(image1, image2, em) + return diff, err +} + +func (em EmergeAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) { + analysis, err := singleVersionAnalysis(image, em) + return analysis, err +} + +func (em EmergeAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageInfo, error) { + var path string + path = image.FSPath + switch path { + case "": + path = emergePkgFile + default: + path = filepath.Join(path, emergePkgFile) + } + packages := make(map[string]util.PackageInfo) + if _, err := os.Stat(path); err != nil { + // invalid image directory path + logrus.Errorf("Invalid image directory path %s", path) + return packages, err + } + + contents, err := ioutil.ReadDir(path) + if err != nil { + logrus.Errorf("Non-content in image directory path %s", path) + return packages, err + } + + for i := 0; i < len(contents); i++ { + c := contents[i] + pkgPrefix := c.Name() + pkgContents, err := ioutil.ReadDir(filepath.Join(path, pkgPrefix)) + if err != nil { + return packages, err + } + for j := 0; j < len(pkgContents); j++ { + c := pkgContents[j] + pkgRawName := c.Name() + // in usual, name of package installed by emerge is formatted as '{pkgName}-{version}' e.g.(pymongo-3.9.0) + s := strings.Split(pkgRawName, "-") + if len(s) != 2 { + continue + } + pkgName, version := s[0], s[1] + pkgPath := filepath.Join(path, pkgPrefix, pkgRawName, "SIZE") + size, err := getPkgSize(pkgPath) + if err != nil { + return packages, err + } + currPackage := util.PackageInfo{Version: version, Size: size} + fullPackageName := strings.Join([]string{pkgPrefix, pkgName}, "/") + packages[fullPackageName] = currPackage + } + } + + return packages, nil +} + +// emerge will count the total size of a package and store it as a SIZE file in pkg metadata directory +// getPkgSize read this SIZE file of a given package +func getPkgSize(pkgPath string) (int64, error) { + var sizeFile *os.File + var err error + sizeFile, err = os.Open(pkgPath) + if err != nil { + logrus.Debugf("unable to open SIZE file for pkg %s", pkgPath) + return 0, err + } + defer sizeFile.Close() + fileBody, err := ioutil.ReadAll(sizeFile) + if err != nil { + logrus.Debugf("unable to read SIZE file for pkg %s", pkgPath) + return 0, err + } + strFileBody := strings.Replace(string(fileBody), "\n", "", -1) + size, _ := strconv.ParseInt(strFileBody, 10, 64) + return size, nil +} diff --git a/differs/emerge_diff_test.go b/differs/emerge_diff_test.go new file mode 100644 index 00000000..74449a7f --- /dev/null +++ b/differs/emerge_diff_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2018 Google, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package differs + +import ( + "reflect" + "testing" + + pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util" + "github.com/GoogleContainerTools/container-diff/util" +) + +func TestGetEmergePackages(t *testing.T) { + testCases := []struct { + descrip string + path string + expected map[string]util.PackageInfo + err bool + }{ + { + descrip: "no directory", + path: "testDirs/notThere", + expected: map[string]util.PackageInfo{}, + err: true, + }, + { + descrip: "no packages", + path: "testDirs/noPackages", + expected: map[string]util.PackageInfo{}, + }, + { + descrip: "packages in expected location", + path: "testDirs/packageEmerge", + expected: map[string]util.PackageInfo{ + "dev-python/pkg1": {Version: "0.0.1", Size: 167112}, + "dev-python/pkg2": {Version: "0.0.2", Size: 167112}, + "sys-libs/pkg3": {Version: "0.0.3", Size: 167112}}, + }, + } + for _, test := range testCases { + d := EmergeAnalyzer{} + image := pkgutil.Image{FSPath: test.path} + packages, err := d.getPackages(image) + if err != nil && !test.err { + t.Errorf("Got unexpected error: %s", err) + } + if err == nil && test.err { + t.Errorf("Expected error but got none.") + } + if !reflect.DeepEqual(packages, test.expected) { + t.Errorf("Expected: %v but got: %v", test.expected, packages) + } + } +} diff --git a/differs/testDirs/noPackages/var/db/pkg/dir/file b/differs/testDirs/noPackages/var/db/pkg/dir/file new file mode 100644 index 00000000..e69de29b diff --git a/differs/testDirs/packageEmerge/var/db/pkg/dev-python/pkg1-0.0.1/SIZE b/differs/testDirs/packageEmerge/var/db/pkg/dev-python/pkg1-0.0.1/SIZE new file mode 100644 index 00000000..0d864809 --- /dev/null +++ b/differs/testDirs/packageEmerge/var/db/pkg/dev-python/pkg1-0.0.1/SIZE @@ -0,0 +1 @@ +167112 \ No newline at end of file diff --git a/differs/testDirs/packageEmerge/var/db/pkg/dev-python/pkg2-0.0.2/SIZE b/differs/testDirs/packageEmerge/var/db/pkg/dev-python/pkg2-0.0.2/SIZE new file mode 100644 index 00000000..0d864809 --- /dev/null +++ b/differs/testDirs/packageEmerge/var/db/pkg/dev-python/pkg2-0.0.2/SIZE @@ -0,0 +1 @@ +167112 \ No newline at end of file diff --git a/differs/testDirs/packageEmerge/var/db/pkg/sys-libs/pkg3-0.0.3/SIZE b/differs/testDirs/packageEmerge/var/db/pkg/sys-libs/pkg3-0.0.3/SIZE new file mode 100644 index 00000000..0d864809 --- /dev/null +++ b/differs/testDirs/packageEmerge/var/db/pkg/sys-libs/pkg3-0.0.3/SIZE @@ -0,0 +1 @@ +167112 \ No newline at end of file