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

Commit 509496e

Browse files
authored
Merge pull request #248 from davidcassany/layered_analysis_for_single_version_packages
Layered analysis for single version packages
2 parents d5404a0 + 1edcffb commit 509496e

9 files changed

+279
-17
lines changed

cmd/root.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const (
5959
RemotePrefix = "remote://"
6060
)
6161

62+
var layerAnalyzers = [...]string{"layer", "aptlayer"}
63+
6264
var RootCmd = &cobra.Command{
6365
Use: "container-diff",
6466
Short: "container-diff is a tool for analyzing and comparing container images",
@@ -268,8 +270,10 @@ func getExtractPathForName(name string) (string, error) {
268270

269271
func includeLayers() bool {
270272
for _, t := range types {
271-
if t == "layer" {
272-
return true
273+
for _, a := range layerAnalyzers {
274+
if t == a {
275+
return true
276+
}
273277
}
274278
}
275279
return false

differs/apt_diff.go

+49-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
"github.com/sirupsen/logrus"
2929
)
3030

31+
//APT package database location
32+
const dpkgStatusFile string = "var/lib/dpkg/status"
33+
3134
type AptAnalyzer struct {
3235
}
3336

@@ -47,13 +50,16 @@ func (a AptAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
4750
}
4851

4952
func (a AptAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageInfo, error) {
50-
path := image.FSPath
53+
return readStatusFile(image.FSPath)
54+
}
55+
56+
func readStatusFile(root string) (map[string]util.PackageInfo, error) {
5157
packages := make(map[string]util.PackageInfo)
52-
if _, err := os.Stat(path); err != nil {
58+
if _, err := os.Stat(root); err != nil {
5359
// invalid image directory path
5460
return packages, err
5561
}
56-
statusFile := filepath.Join(path, "var/lib/dpkg/status")
62+
statusFile := filepath.Join(root, dpkgStatusFile)
5763
if _, err := os.Stat(statusFile); err != nil {
5864
// status file does not exist in this layer
5965
return packages, nil
@@ -120,3 +126,43 @@ func parseLine(text string, currPackage string, packages map[string]util.Package
120126
}
121127
return currPackage
122128
}
129+
130+
type AptLayerAnalyzer struct {
131+
}
132+
133+
func (a AptLayerAnalyzer) Name() string {
134+
return "AptLayerAnalyzer"
135+
}
136+
137+
// AptDiff compares the packages installed by apt-get.
138+
func (a AptLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
139+
diff, err := singleVersionLayerDiff(image1, image2, a)
140+
return diff, err
141+
}
142+
143+
func (a AptLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
144+
analysis, err := singleVersionLayerAnalysis(image, a)
145+
return analysis, err
146+
}
147+
148+
func (a AptLayerAnalyzer) getPackages(image pkgutil.Image) ([]map[string]util.PackageInfo, error) {
149+
var packages []map[string]util.PackageInfo
150+
if _, err := os.Stat(image.FSPath); err != nil {
151+
// invalid image directory path
152+
return packages, err
153+
}
154+
statusFile := filepath.Join(image.FSPath, dpkgStatusFile)
155+
if _, err := os.Stat(statusFile); err != nil {
156+
// status file does not exist in this image
157+
return packages, nil
158+
}
159+
for _, layer := range image.Layers {
160+
layerPackages, err := readStatusFile(layer.FSPath)
161+
if err != nil {
162+
return packages, err
163+
}
164+
packages = append(packages, layerPackages)
165+
}
166+
167+
return packages, nil
168+
}

differs/differs.go

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var Analyzers = map[string]Analyzer{
4747
"file": FileAnalyzer{},
4848
"layer": FileLayerAnalyzer{},
4949
"apt": AptAnalyzer{},
50+
"aptlayer": AptLayerAnalyzer{},
5051
"rpm": RPMAnalyzer{},
5152
"pip": PipAnalyzer{},
5253
"node": NodeAnalyzer{},

differs/package_differs.go

+50
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ limitations under the License.
1717
package differs
1818

1919
import (
20+
"errors"
2021
"strings"
2122

2223
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
2324
"github.com/GoogleContainerTools/container-diff/util"
25+
"github.com/sirupsen/logrus"
2426
)
2527

2628
type MultiVersionPackageAnalyzer interface {
@@ -33,6 +35,11 @@ type SingleVersionPackageAnalyzer interface {
3335
Name() string
3436
}
3537

38+
type SingleVersionPackageLayerAnalyzer interface {
39+
getPackages(image pkgutil.Image) ([]map[string]util.PackageInfo, error)
40+
Name() string
41+
}
42+
3643
func multiVersionDiff(image1, image2 pkgutil.Image, differ MultiVersionPackageAnalyzer) (*util.MultiVersionPackageDiffResult, error) {
3744
pack1, err := differ.getPackages(image1)
3845
if err != nil {
@@ -71,6 +78,13 @@ func singleVersionDiff(image1, image2 pkgutil.Image, differ SingleVersionPackage
7178
}, nil
7279
}
7380

81+
// singleVersionLayerDiff returns an error as this diff is not supported as
82+
// it is far from obvious to define it in meaningful way
83+
func singleVersionLayerDiff(image1, image2 pkgutil.Image, differ SingleVersionPackageLayerAnalyzer) (*util.SingleVersionPackageLayerDiffResult, error) {
84+
logrus.Warning("'diff' command for packages on layers is not supported, consider using 'analyze' on each image instead")
85+
return &util.SingleVersionPackageLayerDiffResult{}, errors.New("Diff for packages on layers is not supported, only analysis is supported")
86+
}
87+
7488
func multiVersionAnalysis(image pkgutil.Image, analyzer MultiVersionPackageAnalyzer) (*util.MultiVersionPackageAnalyzeResult, error) {
7589
pack, err := analyzer.getPackages(image)
7690
if err != nil {
@@ -98,3 +112,39 @@ func singleVersionAnalysis(image pkgutil.Image, analyzer SingleVersionPackageAna
98112
}
99113
return &analysis, nil
100114
}
115+
116+
// singleVersionLayerAnalysis returns the packages included, deleted or
117+
// updated in each layer
118+
func singleVersionLayerAnalysis(image pkgutil.Image, analyzer SingleVersionPackageLayerAnalyzer) (*util.SingleVersionPackageLayerAnalyzeResult, error) {
119+
pack, err := analyzer.getPackages(image)
120+
if err != nil {
121+
return &util.SingleVersionPackageLayerAnalyzeResult{}, err
122+
}
123+
var pkgDiffs []util.PackageDiff
124+
125+
// Each layer with modified packages includes a complete list of packages
126+
// in its package database. Thus we diff the current layer with the
127+
// previous one that contains a package database. Layers that do not
128+
// include a package database are omitted.
129+
preInd := -1
130+
for i := range pack {
131+
var pkgDiff util.PackageDiff
132+
if preInd < 0 && len(pack[i]) > 0 {
133+
pkgDiff = util.GetMapDiff(make(map[string]util.PackageInfo), pack[i])
134+
preInd = i
135+
} else if preInd >= 0 && len(pack[i]) > 0 {
136+
pkgDiff = util.GetMapDiff(pack[preInd], pack[i])
137+
preInd = i
138+
}
139+
140+
pkgDiffs = append(pkgDiffs, pkgDiff)
141+
}
142+
143+
return &util.SingleVersionPackageLayerAnalyzeResult{
144+
Image: image.Source,
145+
AnalyzeType: strings.TrimSuffix(analyzer.Name(), "Analyzer"),
146+
Analysis: util.PackageLayerDiff{
147+
PackageDiffs: pkgDiffs,
148+
},
149+
}, nil
150+
}

util/analyze_output_utils.go

+72
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,78 @@ func (r SingleVersionPackageAnalyzeResult) OutputText(diffType string, format st
136136
return TemplateOutputFromFormat(strResult, "SingleVersionPackageAnalyze", format)
137137
}
138138

139+
type SingleVersionPackageLayerAnalyzeResult AnalyzeResult
140+
141+
func (r SingleVersionPackageLayerAnalyzeResult) OutputStruct() interface{} {
142+
analysis, valid := r.Analysis.(PackageLayerDiff)
143+
if !valid {
144+
logrus.Error("Unexpected structure of Analysis. Should be of type PackageLayerDiff")
145+
return fmt.Errorf("Could not output %s analysis result", r.AnalyzeType)
146+
}
147+
148+
type PkgDiff struct {
149+
Packages1 []PackageOutput
150+
Packages2 []PackageOutput
151+
InfoDiff []Info
152+
}
153+
154+
var analysisOutput []PkgDiff
155+
for _, d := range analysis.PackageDiffs {
156+
diffOutput := PkgDiff{
157+
Packages1: getSingleVersionPackageOutput(d.Packages1),
158+
Packages2: getSingleVersionPackageOutput(d.Packages2),
159+
InfoDiff: getSingleVersionInfoDiffOutput(d.InfoDiff),
160+
}
161+
analysisOutput = append(analysisOutput, diffOutput)
162+
}
163+
164+
output := struct {
165+
Image string
166+
AnalyzeType string
167+
Analysis []PkgDiff
168+
}{
169+
Image: r.Image,
170+
AnalyzeType: r.AnalyzeType,
171+
Analysis: analysisOutput,
172+
}
173+
return output
174+
}
175+
176+
func (r SingleVersionPackageLayerAnalyzeResult) OutputText(diffType string, format string) error {
177+
analysis, valid := r.Analysis.(PackageLayerDiff)
178+
if !valid {
179+
logrus.Error("Unexpected structure of Analysis. Should be of type PackageLayerDiff")
180+
return fmt.Errorf("Could not output %s analysis result", r.AnalyzeType)
181+
}
182+
183+
type StrDiff struct {
184+
Packages1 []StrPackageOutput
185+
Packages2 []StrPackageOutput
186+
InfoDiff []StrInfo
187+
}
188+
189+
var analysisOutput []StrDiff
190+
for _, d := range analysis.PackageDiffs {
191+
diffOutput := StrDiff{
192+
Packages1: stringifyPackages(getSingleVersionPackageOutput(d.Packages1)),
193+
Packages2: stringifyPackages(getSingleVersionPackageOutput(d.Packages2)),
194+
InfoDiff: stringifyPackageDiff(getSingleVersionInfoDiffOutput(d.InfoDiff)),
195+
}
196+
analysisOutput = append(analysisOutput, diffOutput)
197+
}
198+
199+
strResult := struct {
200+
Image string
201+
AnalyzeType string
202+
Analysis []StrDiff
203+
}{
204+
Image: r.Image,
205+
AnalyzeType: r.AnalyzeType,
206+
Analysis: analysisOutput,
207+
}
208+
return TemplateOutputFromFormat(strResult, "SingleVersionPackageLayerAnalyze", format)
209+
}
210+
139211
type PackageOutput struct {
140212
Name string
141213
Path string `json:",omitempty"`

util/diff_output_utils.go

+66
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,72 @@ func getSingleVersionInfoDiffOutput(infoDiff []Info) []Info {
162162
return infoDiff
163163
}
164164

165+
type SingleVersionPackageLayerDiffResult DiffResult
166+
167+
func (r SingleVersionPackageLayerDiffResult) OutputStruct() interface{} {
168+
diff, valid := r.Diff.(PackageLayerDiff)
169+
if !valid {
170+
logrus.Error("Unexpected structure of Diff. Should follow the PackageLayerDiff struct")
171+
return fmt.Errorf("Could not output %s diff result", r.DiffType)
172+
}
173+
174+
type PkgDiff struct {
175+
Packages1 []PackageOutput
176+
Packages2 []PackageOutput
177+
InfoDiff []Info
178+
}
179+
180+
var diffOutputs []PkgDiff
181+
for _, d := range diff.PackageDiffs {
182+
diffOutput := PkgDiff{
183+
Packages1: getSingleVersionPackageOutput(d.Packages1),
184+
Packages2: getSingleVersionPackageOutput(d.Packages2),
185+
InfoDiff: getSingleVersionInfoDiffOutput(d.InfoDiff),
186+
}
187+
diffOutputs = append(diffOutputs, diffOutput)
188+
}
189+
190+
r.Diff = diffOutputs
191+
return r
192+
}
193+
194+
func (r SingleVersionPackageLayerDiffResult) OutputText(diffType string, format string) error {
195+
diff, valid := r.Diff.(PackageLayerDiff)
196+
if !valid {
197+
logrus.Error("Unexpected structure of Diff. Should follow the PackageLayerDiff struct")
198+
return fmt.Errorf("Could not output %s diff result", r.DiffType)
199+
}
200+
201+
type StrDiff struct {
202+
Packages1 []StrPackageOutput
203+
Packages2 []StrPackageOutput
204+
InfoDiff []StrInfo
205+
}
206+
207+
var diffOutputs []StrDiff
208+
for _, d := range diff.PackageDiffs {
209+
diffOutput := StrDiff{
210+
Packages1: stringifyPackages(getSingleVersionPackageOutput(d.Packages1)),
211+
Packages2: stringifyPackages(getSingleVersionPackageOutput(d.Packages2)),
212+
InfoDiff: stringifyPackageDiff(getSingleVersionInfoDiffOutput(d.InfoDiff)),
213+
}
214+
diffOutputs = append(diffOutputs, diffOutput)
215+
}
216+
217+
strResult := struct {
218+
Image1 string
219+
Image2 string
220+
DiffType string
221+
Diff []StrDiff
222+
}{
223+
Image1: r.Image1,
224+
Image2: r.Image2,
225+
DiffType: r.DiffType,
226+
Diff: diffOutputs,
227+
}
228+
return TemplateOutputFromFormat(strResult, "SingleVersionPackageLayerDiff", format)
229+
}
230+
165231
type HistDiffResult DiffResult
166232

167233
func (r HistDiffResult) OutputStruct() interface{} {

util/format_utils.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,19 @@ import (
2929
)
3030

3131
var templates = map[string]string{
32-
"SingleVersionPackageDiff": SingleVersionDiffOutput,
33-
"MultiVersionPackageDiff": MultiVersionDiffOutput,
34-
"HistDiff": HistoryDiffOutput,
35-
"MetadataDiff": MetadataDiffOutput,
36-
"DirDiff": FSDiffOutput,
37-
"MultipleDirDiff": FSLayerDiffOutput,
38-
"FilenameDiff": FilenameDiffOutput,
39-
"ListAnalyze": ListAnalysisOutput,
40-
"FileAnalyze": FileAnalysisOutput,
41-
"FileLayerAnalyze": FileLayerAnalysisOutput,
42-
"MultiVersionPackageAnalyze": MultiVersionPackageOutput,
43-
"SingleVersionPackageAnalyze": SingleVersionPackageOutput,
32+
"SingleVersionPackageDiff": SingleVersionDiffOutput,
33+
"MultiVersionPackageDiff": MultiVersionDiffOutput,
34+
"HistDiff": HistoryDiffOutput,
35+
"MetadataDiff": MetadataDiffOutput,
36+
"DirDiff": FSDiffOutput,
37+
"MultipleDirDiff": FSLayerDiffOutput,
38+
"FilenameDiff": FilenameDiffOutput,
39+
"ListAnalyze": ListAnalysisOutput,
40+
"FileAnalyze": FileAnalysisOutput,
41+
"FileLayerAnalyze": FileLayerAnalysisOutput,
42+
"MultiVersionPackageAnalyze": MultiVersionPackageOutput,
43+
"SingleVersionPackageAnalyze": SingleVersionPackageOutput,
44+
"SingleVersionPackageLayerAnalyze": SingleVersionPackageLayerOutput,
4445
}
4546

4647
func JSONify(diff interface{}) error {

util/package_diff_utils.go

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ type PackageDiff struct {
4646
InfoDiff []Info
4747
}
4848

49+
// PackageLayerDiff stores the difference information between two images
50+
// layer by layer in PackageDiff array
51+
type PackageLayerDiff struct {
52+
PackageDiffs []PackageDiff
53+
}
54+
4955
// Info stores the information for one package in two different images.
5056
type Info struct {
5157
Package string

util/template_utils.go

+16
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,19 @@ Packages found in {{.Image}}:{{if not .Analysis}} None{{else}}
139139
NAME VERSION SIZE{{range .Analysis}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}
140140
{{end}}
141141
`
142+
143+
const SingleVersionPackageLayerOutput = `
144+
-----{{.AnalyzeType}}-----
145+
{{range $index, $analysis := .Analysis}}
146+
For Layer {{$index}}:{{if not (or (or $analysis.Packages1 $analysis.Packages2) $analysis.InfoDiff)}} No package changes {{else}}
147+
{{if ne $index 0}}Deleted packages from previous layers:{{if not $analysis.Packages1}} None{{else}}
148+
NAME VERSION SIZE{{range $analysis.Packages1}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}{{end}}
149+
150+
{{end}}Packages added in this layer:{{if not $analysis.Packages2}} None{{else}}
151+
NAME VERSION SIZE{{range $analysis.Packages2}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}{{end}}
152+
{{if ne $index 0}}
153+
Version differences:{{if not $analysis.InfoDiff}} None{{else}}
154+
PACKAGE PREV_LAYER CURRENT_LAYER {{range $analysis.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{.Info1.Version}}, {{.Info1.Size}} {{.Info2.Version}}, {{.Info2.Size}}{{end}}
155+
{{end}}{{end}}{{end}}
156+
{{end}}
157+
`

0 commit comments

Comments
 (0)