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

feat: support emerge packages analyzer #337

Merged
merged 1 commit into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions differs/differs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,6 +68,7 @@ var Analyzers = map[string]Analyzer{
rpmLayerAnalyzer: RPMLayerAnalyzer{},
pipAnalyzer: PipAnalyzer{},
nodeAnalyzer: NodeAnalyzer{},
emergeAnalyzer: EmergeAnalyzer{},
}

var LayerAnalyzers = [...]string{layerAnalyzer, sizeLayerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer}
Expand Down
123 changes: 123 additions & 0 deletions differs/emerge_diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2018 Google, Inc. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be 2020. i'll merge this and fix myself, so we don't have to go back and forth.


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
//Emerge package database location
// Emerge package database location

const emergePkgFile string = "/var/db/pkg"

type EmergeAnalyzer struct {
}
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
type EmergeAnalyzer struct {
}
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
Comment on lines +53 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in go, it's more idiomatic to say:

Suggested change
var path string
path = image.FSPath
path := image.FSPath

switch path {
case "":
path = emergePkgFile
default:
path = filepath.Join(path, emergePkgFile)
}
Comment on lines +55 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this switch statement is really a two-case if statement in disguise, so it decreases the readability. i think it's easier to read if you just say:

Suggested change
switch path {
case "":
path = emergePkgFile
default:
path = filepath.Join(path, emergePkgFile)
}
if path == "" {
path = emergePkgFile
} else {
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++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in go, you can iterate directly over the items in a slice:

Suggested change
for i := 0; i < len(contents); i++ {
for _, content := range contents {

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I am missing something, but can this just use os.Stat to get the file size? https://gist.github.com/miguelmota/08bca5febf2ce460a289ef14c7e9af4f

If not, maybe a comment that says

// getPkgSize does X because it's special

Copy link
Contributor Author

@thehackercat thehackercat Sep 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this SIZE file and created by emerge, since emerge will count the total size of a package and store it as a SIZE file in packages metadata directory.

So getPkgSize reads the file to get the size of a packge.

And it's quite complex using os.Stat to get the file size of a package, because all dependency files are scattered. For example, if installing dev-python/mysqlclient-3.0.0, it will generate a link file /usr/lib/_mysql.so, client codes in /usr/lib64/python3.7/site-packages/mysqlclient/ and python egg file in /usr/lib64/python3.7/ . Obviously, it's not easy to get size of all these files.

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
}
68 changes: 68 additions & 0 deletions differs/emerge_diff_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
167112
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
167112
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
167112