Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.
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
26 changes: 3 additions & 23 deletions differs/fileDiff.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package differs

import (
"os"
"sort"

"github.com/GoogleCloudPlatform/container-diff/utils"
"github.com/golang/glog"
)

type FileDiffer struct {
Expand All @@ -23,21 +21,11 @@ func diffImageFiles(image1, image2 utils.Image) (utils.DirDiff, error) {

var diff utils.DirDiff

target1 := "j1.json"
err := utils.DirToJSON(img1, target1, true)
img1Dir, err := utils.GetDirectory(img1, true)
if err != nil {
return diff, err
}
target2 := "j2.json"
err = utils.DirToJSON(img2, target2, true)
if err != nil {
return diff, err
}
img1Dir, err := utils.GetDirectory(target1)
if err != nil {
return diff, err
}
img2Dir, err := utils.GetDirectory(target2)
img2Dir, err := utils.GetDirectory(img2, true)
if err != nil {
return diff, err
}
Expand All @@ -52,15 +40,7 @@ func diffImageFiles(image1, image2 utils.Image) (utils.DirDiff, error) {
Image2: image2.Source,
Adds: adds,
Dels: dels,
}

err = os.Remove(target1)
if err != nil {
glog.Error(err)
}
err = os.Remove(target2)
if err != nil {
glog.Error(err)
Mods: []string{},
}
return diff, nil
}
10 changes: 4 additions & 6 deletions differs/pipDiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ func (d PipDiffer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) {
return diff, err
}

func getPythonVersion(pathToLayer string) ([]string, error) {
func getPythonVersion(pathToImage string) ([]string, error) {
matches := []string{}
libPath := filepath.Join(pathToLayer, "usr/local/lib")
libPath := filepath.Join(pathToImage, "usr/local/lib")
libContents, err := ioutil.ReadDir(libPath)
if err != nil {
return matches, err
Expand Down Expand Up @@ -59,9 +59,7 @@ func (d PipDiffer) getPackages(image utils.Image) (map[string]map[string]utils.P
pythonPaths := []string{}
if !reflect.DeepEqual(utils.ConfigSchema{}, image.Config) {
paths := getPythonPaths(image.Config.Config.Env)
for _, p := range paths {
pythonPaths = append(pythonPaths, p)
}
pythonPaths = append(pythonPaths, paths...)
}
pythonVersions, err := getPythonVersion(path)
if err != nil {
Expand All @@ -77,7 +75,7 @@ func (d PipDiffer) getPackages(image utils.Image) (map[string]map[string]utils.P
for _, pythonPath := range pythonPaths {
contents, err := ioutil.ReadDir(pythonPath)
if err != nil {
// python version folder doesn't have a site-packages folder
// python version folder doesn't have a site-packages folder or PYTHONPATH doesn't exist
continue
}

Expand Down
2 changes: 1 addition & 1 deletion tests/file_diff_expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"Dels": [
"/home/test"
],
"Mods": null
"Mods": []
}
}
]
30 changes: 15 additions & 15 deletions tests/multi_diff_expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,6 @@
}
},
"InfoDiff": [
{
"Package": "wheel",
"Info1": [
{
"Version": "0.29.0",
"Size": "103509"
}
],
"Info2": [
{
"Version": "0.29.0",
"Size": "137451"
}
]
},
{
"Package": "mock",
"Info1": [
Expand Down Expand Up @@ -145,6 +130,21 @@
"Size": "1157078"
}
]
},
{
"Package": "wheel",
"Info1": [
{
"Version": "0.29.0",
"Size": "103509"
}
],
"Info2": [
{
"Version": "0.29.0",
"Size": "137451"
}
]
}
]
}
Expand Down
61 changes: 61 additions & 0 deletions utils/docker_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"syscall"
Expand Down Expand Up @@ -36,6 +40,7 @@ func ValidDockerVersion() (bool, error) {
}
return false, nil
}

func getImagePullResponse(image string, response []Event) (string, error) {
var imageDigest string
for _, event := range response {
Expand Down Expand Up @@ -303,3 +308,59 @@ func getImageConfig(image string) (container.Config, error) {
}
return config, nil
}

func getLayersFromManifest(manifestPath string) ([]string, error) {
type Manifest struct {
Layers []string
}

manifestJSON, err := ioutil.ReadFile(manifestPath)
if err != nil {
errMsg := fmt.Sprintf("Could not open manifest to get layer order: %s", err)
return []string{}, errors.New(errMsg)
}

var imageManifest []Manifest
err = json.Unmarshal(manifestJSON, &imageManifest)
if err != nil {
errMsg := fmt.Sprintf("Could not unmarshal manifest to get layer order: %s", err)
return []string{}, errors.New(errMsg)
}
return imageManifest[0].Layers, nil
}

func unpackDockerSave(tarPath string, target string) error {
if _, ok := os.Stat(target); ok != nil {
os.MkdirAll(target, 0777)
}

tempLayerDir := target + "-temp"
err := UnTar(tarPath, tempLayerDir)
if err != nil {
errMsg := fmt.Sprintf("Could not unpack saved Docker image %s: %s", tarPath, err)
return errors.New(errMsg)
}

manifest := filepath.Join(tempLayerDir, "manifest.json")
layers, err := getLayersFromManifest(manifest)
if err != nil {
return err
}

for _, layer := range layers {
layerTar := filepath.Join(tempLayerDir, layer)
if _, err := os.Stat(layerTar); err != nil {
glog.Infof("Did not unpack layer %s because no layer.tar found", layer)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would a layer not have a layer.tar file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it's an empty layer it only has a manifest and a metadata json file, but no contents to unpack

Copy link
Contributor

Choose a reason for hiding this comment

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

ahh, makes sense

continue
}
err = UnTar(layerTar, target)
if err != nil {
glog.Errorf("Could not unpack layer %s: %s", layer, err)
}
}
err = os.RemoveAll(tempLayerDir)
if err != nil {
glog.Errorf("Error deleting temp image layer filesystem: %s", err)
}
return nil
}
69 changes: 52 additions & 17 deletions utils/fs_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package utils

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/golang/glog"
)
Expand All @@ -22,18 +22,33 @@ func GetDirectorySize(path string) (int64, error) {
return size, err
}

func GetDirectory(dirpath string) (Directory, error) {
dirfile, err := ioutil.ReadFile(dirpath)
if err != nil {
return Directory{}, err
}
// GetDirectoryContents converts the directory starting at the provided path into a Directory struct.
func GetDirectory(path string, deep bool) (Directory, error) {
var directory Directory
directory.Root = path
var err error
if deep {
walkFn := func(currPath string, info os.FileInfo, err error) error {
newContent := strings.TrimPrefix(currPath, directory.Root)
if newContent != "" {
directory.Content = append(directory.Content, newContent)
}
return nil
}

var dir Directory
err = json.Unmarshal(dirfile, &dir)
if err != nil {
return Directory{}, err
err = filepath.Walk(path, walkFn)
} else {
contents, err := ioutil.ReadDir(path)
if err != nil {
return directory, err
}

for _, file := range contents {
fileName := "/" + file.Name()
directory.Content = append(directory.Content, fileName)
}
}
return dir, nil
return directory, err
}

// Checks for content differences between files of the same name from different directories
Expand All @@ -53,6 +68,22 @@ func GetModifiedEntries(d1, d2 Directory) []string {
glog.Errorf("Error checking directory entry %s: %s\n", f, err)
continue
}
f2stat, err := os.Stat(f2path)
if err != nil {
glog.Errorf("Error checking directory entry %s: %s\n", f, err)
continue
}

// If the directory entry in question is a tar, verify that the two have the same size
if isTar(f1path) {
if f1stat.Size() != f2stat.Size() {
modified = append(modified, f)
}
continue
}

// If the directory entry is not a tar and not a directory, then it's a file so make sure the file contents are the same
// Note: We skip over directory entries because to compare directories, we compare their contents
if !f1stat.IsDir() {
same, err := checkSameFile(f1path, f2path)
if err != nil {
Expand Down Expand Up @@ -83,12 +114,20 @@ type DirDiff struct {
Mods []string
}

func compareDirEntries(d1, d2 Directory) DirDiff {
// DiffDirectory takes the diff of two directories, assuming both are completely unpacked
func DiffDirectory(d1, d2 Directory) (DirDiff, bool) {
adds := GetAddedEntries(d1, d2)
dels := GetDeletedEntries(d1, d2)
mods := GetModifiedEntries(d1, d2)

return DirDiff{d1.Root, d2.Root, adds, dels, mods}
var same bool
if len(adds) == 0 && len(dels) == 0 && len(mods) == 0 {
same = true
} else {
same = false
}

return DirDiff{d1.Root, d2.Root, adds, dels, mods}, same
}

func checkSameFile(f1name, f2name string) (bool, error) {
Expand Down Expand Up @@ -121,7 +160,3 @@ func checkSameFile(f1name, f2name string) (bool, error) {
}
return true, nil
}

func DiffDirectory(d1, d2 Directory) DirDiff {
return compareDirEntries(d1, d2)
}
49 changes: 33 additions & 16 deletions utils/fs_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,42 @@ func TestGetModifiedEntries(t *testing.T) {
}

func TestGetDirectory(t *testing.T) {
type dirtestpair struct {
input string
expected_success bool
tests := []struct {
descrip string
path string
expected Directory
deep bool
}{
{
descrip: "deep",
path: "testTars/la-croix3-full",
expected: Directory{
Root: "testTars/la-croix3-full",
Content: []string{"/lime.txt", "/nest", "/nest/f1.txt", "/nested-dir", "/nested-dir/f2.txt", "/passionfruit.txt", "/peach-pear.txt"},
},
deep: true,
},
{
descrip: "shallow",
path: "testTars/la-croix3-full",
expected: Directory{
Root: "testTars/la-croix3-full",
Content: []string{"/lime.txt", "/nest", "/nested-dir", "/passionfruit.txt", "/peach-pear.txt"},
},
deep: false,
},
}
for _, testCase := range tests {
dir, err := GetDirectory(testCase.path, testCase.deep)
if err != nil {
t.Errorf("Error converting directory to Directory struct")
}

var dirtests = []dirtestpair{
{"test_files/dir.json", true},
{"test_files/dir_bad.json", false},
{"nonexistentpath", false},
{"", false},
}
actualDir := dir
expectedDir := testCase.expected

for _, test := range dirtests {
_, err := GetDirectory(test.input)
if err != nil && test.expected_success {
t.Errorf("Got unexpected error: %s", err)
}
if err == nil && !test.expected_success {
t.Errorf("Expected error but got none")
if !reflect.DeepEqual(actualDir, expectedDir) {
t.Errorf("%s test was incorrect\nExpected: %s\nGot: %s", testCase.descrip, expectedDir, actualDir)
}
}
}
Expand Down
Loading