Merge "cos_image_analyzer: Milestone 3 - Rootfs, Stateful, OS-Configs, and Partition-Structure difference"
diff --git a/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt b/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt
new file mode 100644
index 0000000..5cddf4a
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt
@@ -0,0 +1,11 @@
+/bin/
+/lib/modules/
+/lib64/
+/usr/libexec/
+/usr/bin/
+/usr/sbin/
+/usr/lib64/
+/usr/share/zoneinfo/
+/usr/share/git/
+/usr/lib/
+/sbin/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt b/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt
new file mode 100644
index 0000000..38331fc
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt
@@ -0,0 +1 @@
+/var_overlay/db/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/binary/diff.go b/src/cmd/cos_image_analyzer/internal/binary/diff.go
index a866278..504fdb1 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/diff.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/diff.go
@@ -1,12 +1,22 @@
package binary
import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
+ "cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
)
// Global variables
var (
// Command-line path strings
+ // /etc is the OS configurations directory
+ etc = "/etc/"
+
// /etc/os-release is the file describing COS versioning
etcOSRelease = "/etc/os-release"
)
@@ -14,36 +24,172 @@
// Differences is a Intermediate Struct used to store all binary differences
// Field names are pre-defined in parse_input.go and will be cross-checked with -binary flag.
type Differences struct {
- Version []string
- BuildID []string
+ Version []string
+ BuildID []string
+ Rootfs string
+ KernelCommandLine string
+ Stateful string
+ PartitionStructure string
+ SysctlSettings string
+ OSConfigs map[string]string
+ KernelConfigs string
}
-// VersionDiff calculates the Version difference of two images
-func (binaryDiff *Differences) VersionDiff(image1 *input.ImageInfo, image2 *input.ImageInfo) {
+// versionDiff calculates the Version difference of two images
+func (binaryDiff *Differences) versionDiff(image1, image2 *input.ImageInfo) {
if image1.Version != image2.Version {
binaryDiff.Version = []string{image1.Version, image2.Version}
}
}
-// BuildDiff calculates the BuildID difference of two images
-func (binaryDiff *Differences) BuildDiff(image1 *input.ImageInfo, image2 *input.ImageInfo) {
+// buildDiff calculates the BuildID difference of two images
+func (binaryDiff *Differences) buildDiff(image1, image2 *input.ImageInfo) {
if image1.BuildID != image2.BuildID {
binaryDiff.BuildID = []string{image1.BuildID, image2.BuildID}
}
}
+// rootfsDiff calculates the Root FS difference of two images
+func (binaryDiff *Differences) rootfsDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ compressedRoots, err := ioutil.ReadFile(flagInfo.CompressRootfsFile)
+ if err != nil {
+ return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressRootfsFile, err)
+ }
+ compressedRootsSlice := strings.Split(string(compressedRoots), "\n")
+ rootfsDiff, err := directoryDiff(image1.RootfsPartition3, image2.RootfsPartition3, "rootfs", flagInfo.Verbose, compressedRootsSlice)
+ if err != nil {
+ return fmt.Errorf("fail to find rootfs difference: %v", err)
+ }
+ binaryDiff.Rootfs = rootfsDiff
+ return nil
+}
+
+// osConfigDiff calculates the OsConfig difference of two images
+func (binaryDiff *Differences) osConfigDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ if err := findOSConfigs(image1, image2, binaryDiff); err != nil {
+ return fmt.Errorf("failed to find OS Configs: %v", err)
+ }
+ for etcEntryName, diff := range binaryDiff.OSConfigs {
+ etcEntryPath := filepath.Join(etc, etcEntryName)
+ if diff != "" {
+ uniqueEntryPath := filepath.Join(diff+"/rootfs/", etcEntryPath)
+ info, err := os.Stat(uniqueEntryPath)
+ if err != nil {
+ return fmt.Errorf("failed to get info on file %v: %v", uniqueEntryPath, err)
+ }
+
+ if info.IsDir() {
+ binaryDiff.OSConfigs[etcEntryName] = diff + " has unique directory " + etcEntryPath
+ } else {
+ binaryDiff.OSConfigs[etcEntryName] = diff + " has unique file " + etcEntryPath
+ }
+ } else {
+ compressedRoots, err := ioutil.ReadFile(flagInfo.CompressRootfsFile)
+ if err != nil {
+ return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressRootfsFile, err)
+ }
+ compressedRootsSlice := strings.Split(string(compressedRoots), "\n")
+
+ osConfigDiff, err := directoryDiff(filepath.Join(image1.RootfsPartition3, etcEntryPath), filepath.Join(image2.RootfsPartition3, etcEntryPath), "rootfs", flagInfo.Verbose, compressedRootsSlice)
+ if err != nil {
+ return fmt.Errorf("fail to find difference on /etc/ entry %v: %v", etcEntryName, err)
+ }
+ binaryDiff.OSConfigs[etcEntryName] = osConfigDiff
+ }
+ }
+ return nil
+}
+
+// statefulDiff calculates the stateful partition difference of two images
+func (binaryDiff *Differences) statefulDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ compressedStateful, err := ioutil.ReadFile(flagInfo.CompressStatefulFile)
+ if err != nil {
+ return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressStatefulFile, err)
+ }
+ compressedStatefulSlice := strings.Split(string(compressedStateful), "\n")
+
+ statefulDiff, err := directoryDiff(image1.StatePartition1, image2.StatePartition1, "stateful", flagInfo.Verbose, compressedStatefulSlice)
+ if err != nil {
+ return fmt.Errorf("failed to diff %v and %v: %v", image1.StatePartition1, image2.StatePartition1, err)
+ }
+ binaryDiff.Stateful = statefulDiff
+ return nil
+}
+
+// partitionStructureDiff calculates the Version difference of two images
+func (binaryDiff *Differences) partitionStructureDiff(image1, image2 *input.ImageInfo) error {
+ if image2.TempDir != "" {
+ partitionStructureDiff, err := pureDiff(image1.PartitionFile, image2.PartitionFile)
+ if err != nil {
+ return fmt.Errorf("fail to find Partition Structure difference: %v", err)
+ }
+ binaryDiff.PartitionStructure = partitionStructureDiff
+ } else {
+ image1Structure, err := ioutil.ReadFile(image1.PartitionFile)
+ if err != nil {
+ return fmt.Errorf("failed to convert file %v to string: %v", image1.PartitionFile, err)
+ }
+ binaryDiff.PartitionStructure = string(image1Structure)
+ }
+ return nil
+}
+
// FormatVersionDiff returns a formated string of the version difference
-func (binaryDiff *Differences) FormatVersionDiff(flagInfo *input.FlagInfo) string {
+func (binaryDiff *Differences) FormatVersionDiff() string {
if len(binaryDiff.Version) == 2 {
- return "Version\n< " + binaryDiff.Version[0] + "\n> " + binaryDiff.Version[1] + "\n"
+ if binaryDiff.Version[1] != "" {
+ return "-----Version-----\n< " + binaryDiff.Version[0] + "\n> " + binaryDiff.Version[1] + "\n\n"
+ }
+ return "-----Version-----\n" + binaryDiff.Version[0] + "\n\n"
}
return ""
}
// FormatBuildIDDiff returns a formated string of the build difference
-func (binaryDiff *Differences) FormatBuildIDDiff(flagInfo *input.FlagInfo) string {
+func (binaryDiff *Differences) FormatBuildIDDiff() string {
if len(binaryDiff.BuildID) == 2 {
- return "BuildID\n< " + binaryDiff.BuildID[0] + "\n> " + binaryDiff.BuildID[1] + "\n"
+ if binaryDiff.BuildID[1] != "" {
+ return "-----BuildID-----\n< " + binaryDiff.BuildID[0] + "\n> " + binaryDiff.BuildID[1] + "\n\n"
+ }
+ return "-----BuildID-----\n" + binaryDiff.BuildID[0] + "\n\n"
+ }
+ return ""
+}
+
+// FormatRootfsDiff returns a formated string of the rootfs difference
+func (binaryDiff *Differences) FormatRootfsDiff() string {
+ if binaryDiff.Rootfs != "" {
+ return "-----RootFS-----\n " + binaryDiff.Rootfs + "\n\n"
+ }
+ return ""
+}
+
+// FormatStatefulDiff returns a formated string of the stateful partition difference
+func (binaryDiff *Differences) FormatStatefulDiff() string {
+ if binaryDiff.Stateful != "" {
+ return "-----Stateful Partition-----\n " + binaryDiff.Stateful + "\n\n"
+ }
+ return ""
+}
+
+// FormatOSConfigDiff returns a formated string of the OS Config difference
+func (binaryDiff *Differences) FormatOSConfigDiff() string {
+ if len(binaryDiff.OSConfigs) > 0 {
+ osConfigDifference := "-----OS Configurations-----\n"
+ for etcEntryName, diff := range binaryDiff.OSConfigs {
+ if diff != "" {
+ osConfigDifference += etcEntryName + "\n" + diff + "\n\n"
+ }
+ }
+ return osConfigDifference
+ }
+ return ""
+}
+
+// FormatPartitionStructureDiff returns a formated string of the partition structure difference
+func (binaryDiff *Differences) FormatPartitionStructureDiff() string {
+ if binaryDiff.PartitionStructure != "" {
+ return "-----Partition Structure-----\n " + binaryDiff.PartitionStructure + "\n\n"
}
return ""
}
@@ -53,12 +199,40 @@
// Input:
// (*ImageInfo) image1 - A struct that will store binary info for image1
// (*ImageInfo) image2 - A struct that will store binary info for image2
+// (*FlagInfo) flagInfo - A struct that holds input preference from the user
// Output:
// (*Differences) BinaryDiff - A struct that will store the binary differences
-func Diff(image1, image2 *input.ImageInfo) (*Differences, error) {
+func Diff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) (*Differences, error) {
BinaryDiff := &Differences{}
- BinaryDiff.VersionDiff(image1, image2)
- BinaryDiff.BuildDiff(image1, image2)
+ if utilities.InArray("Version", flagInfo.BinaryTypesSelected) {
+ BinaryDiff.versionDiff(image1, image2)
+ }
+ if utilities.InArray("BuildID", flagInfo.BinaryTypesSelected) {
+ BinaryDiff.buildDiff(image1, image2)
+ }
+ if utilities.InArray("Partition-structure", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.partitionStructureDiff(image1, image2); err != nil {
+ return BinaryDiff, fmt.Errorf("Failed to get Partition-structure difference: %v", err)
+ }
+ }
+
+ if image2.TempDir != "" {
+ if utilities.InArray("Rootfs", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.rootfsDiff(image1, image2, flagInfo); err != nil {
+ return BinaryDiff, fmt.Errorf("Failed to get Roofs difference: %v", err)
+ }
+ }
+ if utilities.InArray("OS-config", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.osConfigDiff(image1, image2, flagInfo); err != nil {
+ return BinaryDiff, fmt.Errorf("Failed to get OS-config difference: %v", err)
+ }
+ }
+ if utilities.InArray("Stateful-partition", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.statefulDiff(image1, image2, flagInfo); err != nil {
+ return BinaryDiff, fmt.Errorf("Failed to get Stateful-partition difference: %v", err)
+ }
+ }
+ }
return BinaryDiff, nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/diff_test.go b/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
index 7438155..299c88f 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
@@ -7,36 +7,160 @@
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
)
-// test ReadFileToMap function
+// test Diff function
func TestDiff(t *testing.T) {
+ // Rootfs test data
+ testVerboseRootfsDiff := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
+Files ../testdata/image1/rootfs/proc/security/access.conf and ../testdata/image2/rootfs/proc/security/access.conf differ
+Files ../testdata/image1/rootfs/proc/security/configs and ../testdata/image2/rootfs/proc/security/configs differ
+Only in ../testdata/image1/rootfs/usr/lib: usr-lib-image1
+Only in ../testdata/image2/rootfs/usr/lib: usr-lib-image2`
+ testBriefRootfsDiff := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
+Files in ../testdata/image1/rootfs/proc and ../testdata/image2/rootfs/proc differ
+Unique files in ../testdata/image1/rootfs/usr/lib
+Unique files in ../testdata/image2/rootfs/usr/lib`
+
+ // OS Config test data
+ testVerboseOSConfig := map[string]string{
+ "docker": `Files ../testdata/image1/rootfs/etc/docker/credentials.txt and ../testdata/image2/rootfs/etc/docker/credentials.txt differ
+Only in ../testdata/image1/rootfs/etc/docker/util: docker.txt
+Only in ../testdata/image1/rootfs/etc/docker/util: lib32
+Only in ../testdata/image2/rootfs/etc/docker/util: lib64`,
+ "os-release": "Files ../testdata/image1/rootfs/etc/os-release and ../testdata/image2/rootfs/etc/os-release differ"}
+ testBriefOSConfig := map[string]string{
+ "docker": `Files ../testdata/image1/rootfs/etc/docker/credentials.txt and ../testdata/image2/rootfs/etc/docker/credentials.txt differ
+Only in ../testdata/image1/rootfs/etc/docker/util: docker.txt
+Only in ../testdata/image1/rootfs/etc/docker/util: lib32
+Only in ../testdata/image2/rootfs/etc/docker/util: lib64`,
+ "os-release": "Files ../testdata/image1/rootfs/etc/os-release and ../testdata/image2/rootfs/etc/os-release differ"}
+
+ // Stateful test data
+ testVerboseStatefulDiff := `Only in ../testdata/image1/stateful/dev_image: image1_dev.txt
+Only in ../testdata/image2/stateful/dev_image: image2_dev.txt
+Only in ../testdata/image1/stateful/var_overlay/db: image1_data.txt
+Only in ../testdata/image2/stateful/var_overlay/db: image2_data.txt`
+ testBriefStatefulDiff := `Only in ../testdata/image1/stateful/dev_image: image1_dev.txt
+Only in ../testdata/image2/stateful/dev_image: image2_dev.txt
+Unique files in ../testdata/image1/stateful/var_overlay
+Unique files in ../testdata/image2/stateful/var_overlay`
+
+ // Partition Structure test data
+ testPartitionStructure := `1c1
+< Disk /img_disks/cos_81_12871_119_disk/disk.raw: 20971520 sectors, 10.0 GiB
+---
+> Disk /img_disks/cos_77_12371_273_disk/disk.raw: 20971520 sectors, 10.0 GiB
+3c3
+< Disk identifier (GUID): 0274E604-5DE3-5E4E-A4FD-F4D00FBBD7AA
+---
+> Disk identifier (GUID): AB9719F2-3174-4F46-8079-1CF470D2D9BC
+11c11
+< 1 8704000 18874476 4.8 GiB 8300 STATE
+---
+> 1 8704000 18874476 4.8 GiB 0700 STATE
+18c18
+< 8 86016 118783 16.0 MiB 8300 OEM
+---
+> 8 86016 118783 16.0 MiB 0700 OEM`
+
for _, tc := range []struct {
- Image1 *input.ImageInfo
- Image2 *input.ImageInfo
- want *Differences
- }{
- {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
- Image2: &input.ImageInfo{},
- want: &Differences{Version: []string{"81", ""}, BuildID: []string{"12871.119.0", ""}}},
+ Image1 *input.ImageInfo
+ Image2 *input.ImageInfo
+ FlagInfo *input.FlagInfo
+ want *Differences
+ }{ // Version and BuildID difference tests
+ {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+ want: &Differences{Version: []string{"81", ""}, BuildID: []string{"12871.119.0", ""}}},
- {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
- Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image2", Version: "77", BuildID: "12371.273.0"},
- want: &Differences{Version: []string{"81", "77"}, BuildID: []string{"12871.119.0", "12371.273.0"}}},
+ {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+ Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image2/rootfs/", Version: "77", BuildID: "12371.273.0"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+ want: &Differences{Version: []string{"81", "77"}, BuildID: []string{"12871.119.0", "12371.273.0"}}},
- {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
- Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.1"},
- want: &Differences{Version: []string{}, BuildID: []string{"12871.119.0", "12871.119.1"}}},
+ {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+ Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.1"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+ want: &Differences{Version: []string{}, BuildID: []string{"12871.119.0", "12871.119.1"}}},
- {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
- Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
- want: &Differences{}},
+ {Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+ Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+ want: &Differences{}},
+
+ // Rootfs difference test
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}},
+ want: &Differences{}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}, Verbose: true, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ want: &Differences{Rootfs: testVerboseRootfsDiff}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}, Verbose: false, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ want: &Differences{Rootfs: testBriefRootfsDiff}},
+
+ // OS Config difference test
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}},
+ want: &Differences{}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}, Verbose: true, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ want: &Differences{OSConfigs: testVerboseOSConfig}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}, Verbose: false, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ want: &Differences{OSConfigs: testBriefOSConfig}},
+
+ // Stateful difference test
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}},
+ want: &Differences{}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", StatePartition1: "../testdata/image1/stateful/"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", StatePartition1: "../testdata/image2/stateful/"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}, Verbose: true, CompressStatefulFile: "../testdata/CompressStatefulFile.txt"},
+ want: &Differences{Stateful: testVerboseStatefulDiff}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", StatePartition1: "../testdata/image1/stateful/"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", StatePartition1: "../testdata/image2/stateful/"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}, Verbose: false, CompressStatefulFile: "../testdata/CompressStatefulFile.txt"},
+ want: &Differences{Stateful: testBriefStatefulDiff}},
+
+ // Partition Structure
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Partition-structure"}},
+ want: &Differences{}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", PartitionFile: "../testdata/image1/partitions.txt"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", PartitionFile: "../testdata/image2/partitions.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Partition-structure"}},
+ want: &Differences{PartitionStructure: testPartitionStructure}},
} {
- got, _ := Diff(tc.Image1, tc.Image2)
+ got, _ := Diff(tc.Image1, tc.Image2, tc.FlagInfo)
if !utilities.EqualArrays(tc.want.Version, got.Version) {
- t.Fatalf("Diff expected: %v, got: %v", tc.want.Version, got.Version)
+ t.Fatalf("Diff expected version %v, got: %v", tc.want.Version, got.Version)
}
if !utilities.EqualArrays(tc.want.BuildID, got.BuildID) {
- t.Fatalf("Diff expected: %v, got: %v", tc.want.BuildID, got.BuildID)
+ t.Fatalf("Diff expected BuildID %v, got: %v", tc.want.BuildID, got.BuildID)
+ }
+ if tc.want.Rootfs != got.Rootfs {
+ t.Fatalf("Diff expected Rootfs diff \n$%v$\ngot:\n$%v$", tc.want.Rootfs, got.Rootfs)
+ }
+ for etcEntry := range got.OSConfigs {
+ if res, _ := utilities.CmpMapValues(tc.want.OSConfigs, got.OSConfigs, etcEntry); res != 0 {
+ t.Fatalf("Diff expected OSConfigs \n$%v$\ngot:\n$%v$", tc.want.OSConfigs, got.OSConfigs)
+ }
+ }
+ if tc.want.Stateful != got.Stateful {
+ t.Fatalf("Diff expected stateful diff \n$%v$\ngot:\n$%v$", tc.want.Stateful, got.Stateful)
+ }
+ if tc.want.PartitionStructure != got.PartitionStructure {
+ t.Fatalf("Diff expected partition structure diff \n$%v$\ngot:\n$%v$", tc.want.PartitionStructure, got.PartitionStructure)
}
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/helpers.go b/src/cmd/cos_image_analyzer/internal/binary/helpers.go
new file mode 100644
index 0000000..125f945
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/helpers.go
@@ -0,0 +1,195 @@
+package binary
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
+ "cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
+)
+
+// findOSConfigs finds the list of directory entries in the /etc of both images
+// and stores them into the binaryDiff struct
+/// Input:
+// (*ImageInfo) image1 - Holds needed rootfs path for image 1
+// (*ImageInfo) image2 - Holds needed rootfs path
+// Output:
+// (*Differences) binaryDiff - Holds needed KernelCommandLine map
+func findOSConfigs(image1, image2 *input.ImageInfo, binaryDiff *Differences) error {
+ etcFiles1, err := ioutil.ReadDir(image1.RootfsPartition3 + etc)
+ if err != nil {
+ return fmt.Errorf("fail to read contents of directory %v: %v", image1.RootfsPartition3+etc, err)
+ }
+ etcEntries1 := []string{}
+ for _, f := range etcFiles1 {
+ etcEntries1 = append(etcEntries1, f.Name())
+ }
+
+ etcFiles2, err := ioutil.ReadDir(image2.RootfsPartition3 + etc)
+ if err != nil {
+ return fmt.Errorf("fail to read contents of directory %v: %v", image2.RootfsPartition3+etc, err)
+ }
+ etcEntries2 := []string{}
+ for _, f := range etcFiles2 {
+ etcEntries2 = append(etcEntries2, f.Name())
+ }
+
+ osConfigsMap := make(map[string]string)
+ for _, elem1 := range etcEntries1 {
+ if !utilities.InArray(elem1, etcEntries2) { // Unique file or directory in image 1
+ osConfigsMap[elem1] = image1.TempDir
+ } else { // Common /etc files or directories for image 1 and 2
+ osConfigsMap[elem1] = ""
+ }
+ }
+ for _, elem2 := range etcEntries2 {
+ if _, ok := osConfigsMap[elem2]; !ok { // Unique file or directory in image 2
+ osConfigsMap[elem2] = image2.TempDir
+ }
+ }
+ binaryDiff.OSConfigs = osConfigsMap
+ return nil
+}
+
+// findDiffDir finds the directory name from the "diff" command
+// for the "Only in [file path]" case.
+// Input:
+// (string) line - A single line of output from the "diff -rq" command
+// (string) dir1 - Path to directory 1
+// (string) dir2 - Path to directory 2
+// (bool) ok - Flag to indicate a directory has been found
+// Output:
+// (string) dir1 or dir2 - The directory found in "line"
+func findDiffDir(line, dir1, dir2 string) (string, bool) {
+ lineSplit := strings.Split(line, " ")
+ if len(lineSplit) < 3 {
+ return "", false
+ }
+
+ for _, word := range lineSplit {
+ if strings.Contains(word, dir1) && strings.Contains(word, dir2) {
+ return "", false
+ }
+ if strings.Contains(word, dir1) {
+ return dir1, true
+ }
+ if strings.Contains(word, dir2) {
+ return dir2, true
+ }
+ }
+ return "", false
+}
+
+// compressString compresses lines of a string that fit a pattern
+// Input:
+// (string) dir1 - Path to directory 1
+// (string) dir2 - Path to directory 2
+// (string) root - Name of the root for directories 1 and 2
+// (string) input - The string to be filtered
+// ([]string) patterns - The list of patterns to be filtered out
+// Output:
+// (string) output - The compacted version of the input string
+func compressString(dir1, dir2, root, input string, patterns []string) (string, error) {
+ patternMap := utilities.SliceToMapStr(patterns)
+
+ lines := strings.Split(string(input), "\n")
+ for i, line := range lines {
+ for pat, count := range patternMap {
+ fullPattern := filepath.Join(root, pat)
+ fileInPattern := fullPattern + "/"
+ onlyInPattern := fullPattern + ":"
+ if strings.Contains(line, fileInPattern) || strings.Contains(line, onlyInPattern) {
+ lineSplit := strings.Split(line, " ")
+ if len(lineSplit) < 3 {
+ continue
+ }
+
+ typeOfDiff := lineSplit[0]
+ if typeOfDiff == "Files" || typeOfDiff == "Symbolic" || typeOfDiff == "File" {
+ if strings.Contains(count, "differentFilesFound") {
+ lines[i] = ""
+ continue
+ }
+ lines[i] = "Files in " + filepath.Join(dir1, pat) + " and " + filepath.Join(dir2, pat) + " differ"
+ patternMap[pat] += "differentFilesFound"
+ } else if typeOfDiff == "Only" {
+ if strings.Contains(count, "dir1_UniqueFileFound") && strings.Contains(count, "dir2_UniqueFileFound") {
+ lines[i] = ""
+ continue
+ }
+ if onlyDir, ok := findDiffDir(line, dir1, dir2); ok {
+ if onlyDir == dir1 {
+ if !strings.Contains(count, "dir1_UniqueFileFound") {
+ lines[i] = "Unique files in " + filepath.Join(onlyDir, pat)
+ patternMap[pat] += "dir1_UniqueFileFound"
+ } else {
+ lines[i] = ""
+ continue
+ }
+ } else if onlyDir == dir2 {
+ if !strings.Contains(count, "dir2_UniqueFileFound") {
+ lines[i] = "Unique files in " + filepath.Join(onlyDir, pat)
+ patternMap[pat] += "dir2_UniqueFileFound"
+ } else {
+ lines[i] = ""
+ continue
+ }
+ }
+ }
+ } else { // Compress any other diff output not described above
+ lines[i] = ""
+ }
+ }
+ }
+ }
+
+ output := strings.Join(lines, "\n")
+ output = regexp.MustCompile(`[\t\r\n]+`).ReplaceAllString(strings.TrimSpace(output), "\n")
+ return output, nil
+}
+
+// DirectoryDiff finds the recursive file difference between two directories.
+// If verbose is true return full difference, else compress based on compressedDirs
+// Input:
+// (string) dir1 - Path to directory 1
+// (string) dir2 - Path to directory 2
+// (string) root - Name of the root for directories 1 and 2
+// ([]string) compressedDirs - List of directories to compress by
+// (bool) verbose - Flag that determines whether to show full or compressed difference
+// Output:
+// (string) diff - The file difference output of the "diff" command
+func directoryDiff(dir1, dir2, root string, verbose bool, compressedDirs []string) (string, error) {
+ diff, err := exec.Command("sudo", "diff", "--no-dereference", "-rq", "-x", "etc", dir1, dir2).Output()
+ if exitError, ok := err.(*exec.ExitError); ok {
+ if exitError.ExitCode() == 2 {
+ return "", fmt.Errorf("failed to call 'diff' command on directories %v and %v: %v", dir1, dir2, err)
+ }
+ }
+ diffStrln := fmt.Sprintf("%s", diff)
+ diffStr := strings.TrimSuffix(diffStrln, "\n")
+ if verbose {
+ return diffStr, nil
+ }
+ compressedDiffStr, err := compressString(dir1, dir2, root, diffStr, compressedDirs)
+ if err != nil {
+ return "", fmt.Errorf("failed to call compress 'diff' output between %v and %v: %v", dir1, dir2, err)
+ }
+ return compressedDiffStr, nil
+}
+
+// pureDiff returns the output of a normal diff between two files or directories
+func pureDiff(input1, input2 string) (string, error) {
+ diff, err := exec.Command("sudo", "diff", input1, input2).Output()
+ if exitError, ok := err.(*exec.ExitError); ok {
+ if exitError.ExitCode() == 2 {
+ return "", fmt.Errorf("failed to call 'diff' on %v and %v: %v", input1, input2, err)
+ }
+ }
+ diffStrln := fmt.Sprintf("%s", diff)
+ diffStr := strings.TrimSuffix(diffStrln, "\n")
+ return diffStr, nil
+}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go b/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
new file mode 100644
index 0000000..d07af4d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
@@ -0,0 +1,61 @@
+package binary
+
+import (
+ "testing"
+)
+
+// test DirectoryDiff function
+func TestDirectoryDiff(t *testing.T) {
+ testVerboseOutput := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
+Files ../testdata/image1/rootfs/proc/security/access.conf and ../testdata/image2/rootfs/proc/security/access.conf differ
+Files ../testdata/image1/rootfs/proc/security/configs and ../testdata/image2/rootfs/proc/security/configs differ
+Only in ../testdata/image1/rootfs/usr/lib: usr-lib-image1
+Only in ../testdata/image2/rootfs/usr/lib: usr-lib-image2`
+ testBriefOutput := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
+Files in ../testdata/image1/rootfs/proc and ../testdata/image2/rootfs/proc differ
+Unique files in ../testdata/image1/rootfs/usr/lib
+Unique files in ../testdata/image2/rootfs/usr/lib`
+
+ for _, tc := range []struct {
+ dir1 string
+ dir2 string
+ root string
+ verbose bool
+ compressedDirs []string
+ want string
+ }{
+ {dir1: "../testdata/image1/rootfs/", dir2: "../testdata/image2/rootfs/", root: "rootfs", verbose: true, compressedDirs: []string{"/proc/", "/usr/lib/"}, want: testVerboseOutput},
+ {dir1: "../testdata/image1/rootfs/", dir2: "../testdata/image2/rootfs/", root: "rootfs", verbose: false, compressedDirs: []string{"/proc/", "/usr/lib/"}, want: testBriefOutput},
+ } {
+ got, _ := directoryDiff(tc.dir1, tc.dir2, tc.root, tc.verbose, tc.compressedDirs)
+ if got != tc.want {
+ t.Fatalf("directoryDiff expected:\n$%v$\ngot:\n$%v$", tc.want, got)
+ }
+ }
+}
+
+// test PureDiff function
+func TestPureDiff(t *testing.T) {
+ testOutput1 := `1c1
+< testing 123 can you hear me?
+---
+> testing 456 can you hear me?`
+ testOutput2 := `1c1
+< These are not the configs you are looking for
+---
+> These are the configs you are looking for`
+ for _, tc := range []struct {
+ input1 string
+ input2 string
+ want string
+ }{
+ {input1: "../testdata/image1/rootfs/proc/security/access.conf", input2: "../testdata/image2/rootfs/proc/security/access.conf", want: testOutput1},
+ {input1: "../testdata/image1/rootfs/proc/security/configs", input2: "../testdata/image2/rootfs/proc/security/configs", want: testOutput2},
+ {input1: "../testdata/image1/rootfs/proc/security/lib-image1", input2: "../testdata/image2/rootfs/proc/security/lib-image2", want: ""},
+ } {
+ got, _ := pureDiff(tc.input1, tc.input2)
+ if got != tc.want {
+ t.Fatalf("PureDiff expected:\n$%v$\ngot:\n$%v$", tc.want, got)
+ }
+ }
+}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/info.go b/src/cmd/cos_image_analyzer/internal/binary/info.go
index 30e7ce6..cce8b5e 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/info.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/info.go
@@ -11,22 +11,33 @@
// GetBinaryInfo finds relevant binary information for the COS image
// Input:
// (*ImageInfo) image - A struct that will store binary info for the image
+// (localInput) bool - Flag to determine whether to rename disk.raw file
+// (*FlagInfo) flagInfo - A struct that holds input preference from the user
// Output: nil on success, else error
-func GetBinaryInfo(image *input.ImageInfo) error {
- if image.RootfsPartition3 == "" {
+func GetBinaryInfo(image *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ if image.TempDir == "" {
return nil
}
- osReleaseMap, err := utilities.ReadFileToMap(image.RootfsPartition3+etcOSRelease, "=")
- if err != nil {
- return fmt.Errorf("Failed to read /etc/os-release file in rootfs of image: %v", err)
- }
- var ok bool
- if image.Version, ok = osReleaseMap["VERSION"]; !ok {
- return errors.New("Error: \"Version\" field not found in /etc/os-release file")
+
+ if image.RootfsPartition3 != "" {
+ osReleaseMap, err := utilities.ReadFileToMap(image.RootfsPartition3+etcOSRelease, "=")
+ if err != nil {
+ return fmt.Errorf("Failed to read /etc/os-release file in rootfs of image %v : %v", image.TempDir, err)
+ }
+ var ok bool
+ if image.Version, ok = osReleaseMap["VERSION"]; !ok {
+ return errors.New("Error: \"Version\" field not found in /etc/os-release file")
+ }
+ if image.BuildID, ok = osReleaseMap["BUILD_ID"]; !ok {
+ return errors.New("Error: \"Build_ID\" field not found in /etc/os-release file")
+ }
}
- if image.BuildID, ok = osReleaseMap["BUILD_ID"]; !ok {
- return errors.New("Error: \"Build_ID\" field not found in /etc/os-release file")
+ if utilities.InArray("Partition-structure", flagInfo.BinaryTypesSelected) {
+ if err := image.GetPartitionStructure(); err != nil {
+ return fmt.Errorf("failed to get partition structure for image %v: %v", image.TempDir, err)
+ }
}
+
return nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/info_test.go b/src/cmd/cos_image_analyzer/internal/binary/info_test.go
index a5b922b..890444f 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/info_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/info_test.go
@@ -6,37 +6,32 @@
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
)
-// test ReadFileToMap function
+// test GetBinaryInf function
func TestGetBinaryInfo(t *testing.T) {
- // test normal file
- testImage1 := &input.ImageInfo{RootfsPartition3: "../testdata/image1"}
- testImage2 := &input.ImageInfo{}
- testImage3 := &input.ImageInfo{RootfsPartition3: "../testdata/image2"}
+ for _, tc := range []struct {
+ image *input.ImageInfo
+ flagInfo *input.FlagInfo
+ want *input.ImageInfo
+ }{
+ {image: &input.ImageInfo{TempDir: "../testdata/image1/", RootfsPartition3: "../testdata/image1/rootfs/"},
+ flagInfo: &input.FlagInfo{LocalPtr: true},
+ want: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"}},
- expectedImage1 := &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"}
- expectedImage2 := &input.ImageInfo{}
- expectedImage3 := &input.ImageInfo{RootfsPartition3: "../testdata/image2", Version: "77", BuildID: "12371.273.0"}
+ {image: &input.ImageInfo{},
+ flagInfo: &input.FlagInfo{LocalPtr: true},
+ want: &input.ImageInfo{}},
- type test struct {
- Image1 *input.ImageInfo
- want *input.ImageInfo
- }
+ {image: &input.ImageInfo{TempDir: "../testdata/image2/", RootfsPartition3: "../testdata/image2/rootfs/"},
+ flagInfo: &input.FlagInfo{LocalPtr: true},
+ want: &input.ImageInfo{RootfsPartition3: "../testdata/image2/rootfs", Version: "77", BuildID: "12371.273.0"}},
+ } {
+ GetBinaryInfo(tc.image, tc.flagInfo)
- tests := []test{
- {Image1: testImage1, want: expectedImage1},
- {Image1: testImage2, want: expectedImage2},
- {Image1: testImage3, want: expectedImage3},
- }
-
- for _, tc := range tests {
- GetBinaryInfo(tc.Image1)
-
- if tc.want.Version != tc.Image1.Version {
- t.Fatalf("Diff expected: %v, got: %v", tc.want.Version, tc.Image1.Version)
+ if tc.want.Version != tc.image.Version {
+ t.Fatalf("GetBinaryInfo expected: %v, got: %v", tc.want.Version, tc.image.Version)
}
- if tc.want.BuildID != tc.Image1.BuildID {
- t.Fatalf("Diff expected: %v, got: %v", tc.want.BuildID, tc.Image1.BuildID)
+ if tc.want.BuildID != tc.image.BuildID {
+ t.Fatalf("GetBinaryInfo expected: %v, got: %v", tc.want.BuildID, tc.image.BuildID)
}
}
-
}
diff --git a/src/cmd/cos_image_analyzer/internal/input/flaginfo.go b/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
index f8dbe4e..7370c4e 100644
--- a/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
+++ b/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
@@ -23,6 +23,18 @@
CommitSelected bool
// Release Notes
ReleaseNotesSelected bool
+
+ //Verbosity of output
+ Verbose bool
+
+ // File used to compress output from Rootfs and OS-Config difference
+ // (either user provided or default CompressRootfs.txt)
+ CompressRootfsFile string
+
+ // File used to compress output from Stateful-partition difference
+ // (either user provided or default CompressStateful.txt)
+ CompressStatefulFile string
+
// Output
OutputSelected string
}
diff --git a/src/cmd/cos_image_analyzer/internal/input/imageinfo.go b/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
index ad55179..38089bb 100644
--- a/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
+++ b/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
@@ -20,7 +20,7 @@
const sectorSize = 512
const gcsObjFormat = ".tar.gz"
-const filemode = 0700
+const makeDirFilemode = 0700
const timeOut = "7200s"
const imageFormat = "vmdk"
const name = "gcr.io/compute-image-tools/gce_vm_image_export:release"
@@ -30,8 +30,13 @@
// Input Overhead
TempDir string // Temporary directory holding the mounted image and disk file
DiskFile string // Path to the DOS/MBR disk partition file
+ PartitionFile string // Path to the file storing the disk partition structure from "sgdisk"
+ StatePartition1 string // Path to mounted directory of partition #1, stateful partition
RootfsPartition3 string // Path to mounted directory of partition #3, Rootfs-A
+ EFIPartition12 string // Path to mounted directory of partition #12, EFI-System
+ LoopDevice1 string // Active loop device for mounted image
LoopDevice3 string // Active loop device for mounted image
+ LoopDevice12 string // Active loop device for mounted image
// Binary info
Version string
@@ -42,6 +47,31 @@
// Release notes info
}
+// Rename temporary directory and its contents once Version and BuildID are known
+func (image *ImageInfo) Rename(flagInfo *FlagInfo) error {
+ if image.Version != "" && image.BuildID != "" {
+ fullImageName := "cos-" + image.Version + "-" + image.BuildID
+ if err := os.Rename(image.TempDir, fullImageName); err != nil {
+ return fmt.Errorf("Error: Failed to rename directory %v to %v: %v", image.TempDir, fullImageName, err)
+ }
+ image.TempDir = fullImageName
+
+ if !flagInfo.LocalPtr {
+ image.DiskFile = filepath.Join(fullImageName, "disk.raw")
+ }
+ if image.RootfsPartition3 != "" {
+ image.RootfsPartition3 = filepath.Join(fullImageName, "rootfs")
+ }
+ if image.StatePartition1 != "" {
+ image.StatePartition1 = filepath.Join(fullImageName, "stateful")
+ }
+ if image.EFIPartition12 != "" {
+ image.EFIPartition12 = filepath.Join(fullImageName, "efi")
+ }
+ }
+ return nil
+}
+
// getPartitionStart finds the start partition offset of the disk
// Input:
// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
@@ -79,6 +109,29 @@
return start, nil
}
+// GetPartitionStructure returns the partition structure of .raw file
+// Input:
+// (string) diskRaw - Path to the boot .raw file
+// Output:
+// (string) partitionStructure - The output of the fdisk command
+func (image *ImageInfo) GetPartitionStructure() error {
+ if image.TempDir == "" {
+ return nil
+ }
+
+ out, err := exec.Command("sudo", "sgdisk", "-p", image.DiskFile).Output()
+ if err != nil {
+ return fmt.Errorf("failed to call sgdisk -p %v: %v", image.DiskFile, err)
+ }
+
+ partitionFile := filepath.Join(image.TempDir, "partitions.txt")
+ if err := utilities.WriteToNewFile(partitionFile, string(out[:])); err != nil {
+ return fmt.Errorf("failed create file %v and write %v: %v", partitionFile, string(out[:]), err)
+ }
+ image.PartitionFile = partitionFile
+ return nil
+}
+
// mountDisk finds a free loop device and mounts a DOS/MBR disk file
// Input:
// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
@@ -108,25 +161,54 @@
// MountImage is an ImagInfo method that mounts partitions 1,3 and 12 of
// the image into the temporary directory
-// Input: None
+// Input:
+// (string) arr - List of binary types selected from the user
// Output: nil on success, else error
-func (image *ImageInfo) MountImage() error {
+func (image *ImageInfo) MountImage(arr []string) error {
if image.TempDir == "" {
return nil
}
+ if utilities.InArray("Stateful-partition", arr) {
+ stateful := filepath.Join(image.TempDir, "stateful")
+ if err := os.Mkdir(stateful, makeDirFilemode); err != nil {
+ return fmt.Errorf("failed to create make directory %v: %v", stateful, err)
+ }
+ image.StatePartition1 = stateful
- rootfs := filepath.Join(image.TempDir, "rootFS")
- if err := os.Mkdir(rootfs, filemode); err != nil {
- return fmt.Errorf("failed to create make directory %v: %v", rootfs, err)
+ loopDevice1, err := mountDisk(image.DiskFile, image.StatePartition1, "1")
+ if err != nil {
+ return fmt.Errorf("Failed to mount %v's partition #1 onto %v: %v", image.DiskFile, image.StatePartition1, err)
+ }
+ image.LoopDevice1 = loopDevice1
}
- image.RootfsPartition3 = rootfs
- loopDevice3, err := mountDisk(image.DiskFile, image.RootfsPartition3, "3")
- if err != nil {
- return fmt.Errorf("Failed to mount %v's partition #3 onto %v: %v", image.DiskFile, image.RootfsPartition3, err)
+ if utilities.InArray("Version", arr) || utilities.InArray("BuildID", arr) || utilities.InArray("Rootfs", arr) || utilities.InArray("Sysctl-settings", arr) || utilities.InArray("OS-config", arr) || utilities.InArray("Kernel-configs", arr) {
+ rootfs := filepath.Join(image.TempDir, "rootfs")
+ if err := os.Mkdir(rootfs, makeDirFilemode); err != nil {
+ return fmt.Errorf("failed to create make directory %v: %v", rootfs, err)
+ }
+ image.RootfsPartition3 = rootfs
+
+ loopDevice3, err := mountDisk(image.DiskFile, image.RootfsPartition3, "3")
+ if err != nil {
+ return fmt.Errorf("Failed to mount %v's partition #3 onto %v: %v", image.DiskFile, image.RootfsPartition3, err)
+ }
+ image.LoopDevice3 = loopDevice3
}
- image.LoopDevice3 = loopDevice3
+ if utilities.InArray("Kernel-command-line", arr) {
+ efi := filepath.Join(image.TempDir, "efi")
+ if err := os.Mkdir(efi, makeDirFilemode); err != nil {
+ return fmt.Errorf("failed to create make directory %v: %v", efi, err)
+ }
+ image.EFIPartition12 = efi
+
+ loopDevice12, err := mountDisk(image.DiskFile, image.EFIPartition12, "12")
+ if err != nil {
+ return fmt.Errorf("Failed to mount %v's partition #12 onto %v: %v", image.DiskFile, image.EFIPartition12, err)
+ }
+ image.LoopDevice12 = loopDevice12
+ }
return nil
}
@@ -309,13 +391,19 @@
if image.TempDir == "" {
return nil
}
-
- if image.LoopDevice3 != "" {
- if _, err := exec.Command("sudo", "umount", image.RootfsPartition3).Output(); err != nil {
- return fmt.Errorf("failed to umount directory %v: %v", image.RootfsPartition3, err)
+ if image.LoopDevice1 != "" {
+ if err := utilities.Unmount(image.StatePartition1, image.LoopDevice1); err != nil {
+ return fmt.Errorf("failed to unmount mount directory %v and/or loop device %v: %v", image.StatePartition1, image.LoopDevice1, err)
}
- if _, err := exec.Command("sudo", "losetup", "-d", image.LoopDevice3).Output(); err != nil {
- return fmt.Errorf("failed to delete loop device %v: %v", image.LoopDevice3, err)
+ }
+ if image.LoopDevice3 != "" {
+ if err := utilities.Unmount(image.RootfsPartition3, image.LoopDevice3); err != nil {
+ return fmt.Errorf("failed to unmount mount directory %v and/or loop device %v: %v", image.RootfsPartition3, image.LoopDevice3, err)
+ }
+ }
+ if image.LoopDevice12 != "" {
+ if err := utilities.Unmount(image.EFIPartition12, image.LoopDevice12); err != nil {
+ return fmt.Errorf("failed to unmount mount directory %v and/or loop device %v: %v", image.EFIPartition12, image.LoopDevice12, err)
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/input/parse.go b/src/cmd/cos_image_analyzer/internal/input/parse.go
index 49a7bc3..344f648 100644
--- a/src/cmd/cos_image_analyzer/internal/input/parse.go
+++ b/src/cmd/cos_image_analyzer/internal/input/parse.go
@@ -39,10 +39,6 @@
input is one or two objects stored on Google Cloud Storage of type (.tar.gz). This flag temporarily downloads,
unzips, and loop device mounts the images into this tool's directory.
- Authentication Flags:
- -projectID
- projectID of the Google Cloud Project holding the images. Mandatory for "-cos-cloud" input
-
Difference Flags:
-binary (string)
specify which type of binary difference to show. Types "Version", "BuildID", "Rootfs", "Kernel-command-line",
@@ -58,6 +54,21 @@
specify whether to show release notes difference. Shows differences in human-written release notes between
the two images. To NOT list any release notes difference, set flag to false. (default true)
+ Attribute Flags
+ -verbose
+ include flag to increase verbosity of Rootfs, Stateful-partition, and OS-config differences. See the
+ "internal/binary/CompressRootfs.txt", "internal/binary/CompressStateful.txt", and "internal/binary/CompressOSConfigs.txt"
+ files to edit the default directories whose differences are compressed together.
+ -compress-rootfs (string)
+ to customize which directories are compressed in a non-verbose Rootfs and OS-config difference output, provide a local
+ file path to a text file. Format of the file must be one root file path per line with an ending back slash and no commas.
+ By default the directory(s) that are compressed during a diff are /bin/, /lib/modules/, /lib64/, /usr/libexec/, /usr/bin/,
+ /usr/sbin/, /usr/lib64/, /usr/share/zoneinfo/, /usr/share/git/, /usr/lib/, and /sbin/.
+ -compress-stateful (string)
+ to customize which directories are compressed in a non-verbose Stateful-partition difference output, provide a local
+ file path to a text file. Format of file must be one root file path per line with no commas. By default the directory(s)
+ that are compressed during a diff are /var_overlay/db/.
+
Output Flags:
-output (string)
Specify format of output. Only "terminal" stdout or "json" object is supported. (default "terminal")
@@ -97,6 +108,18 @@
}
}
+ if res := utilities.FileExists(flagInfo.CompressRootfsFile, "txt"); res == -1 {
+ return errors.New("Error: " + flagInfo.CompressRootfsFile + " file does not exist")
+ } else if res == 0 {
+ return errors.New("Error: " + flagInfo.CompressRootfsFile + " is not a \".txt\" file")
+ }
+
+ if res := utilities.FileExists(flagInfo.CompressStatefulFile, "txt"); res == -1 {
+ return errors.New("Error: " + flagInfo.CompressStatefulFile + " file does not exist")
+ } else if res == 0 {
+ return errors.New("Error: " + flagInfo.CompressStatefulFile + " is not a \".txt\" file")
+ }
+
if flagInfo.OutputSelected != "terminal" && flagInfo.OutputSelected != "json" {
return errors.New("Error: \"-output\" flag must be ethier \"terminal\" or \"json\"")
}
@@ -135,6 +158,10 @@
flag.BoolVar(&flagInfo.CommitSelected, "commit", true, "")
flag.BoolVar(&flagInfo.ReleaseNotesSelected, "release-notes", true, "")
+ flag.BoolVar(&flagInfo.Verbose, "verbose", false, "")
+ flag.StringVar(&flagInfo.CompressRootfsFile, "compress-rootfs", "internal/binary/CompressRootfs.txt", "")
+ flag.StringVar(&flagInfo.CompressStatefulFile, "compress-stateful", "internal/binary/CompressStateful.txt", "")
+
flag.StringVar(&flagInfo.OutputSelected, "output", "terminal", "")
flag.Parse()
diff --git a/src/cmd/cos_image_analyzer/internal/output/imagediff.go b/src/cmd/cos_image_analyzer/internal/output/imagediff.go
index 13e1d6f..423f91f 100644
--- a/src/cmd/cos_image_analyzer/internal/output/imagediff.go
+++ b/src/cmd/cos_image_analyzer/internal/output/imagediff.go
@@ -24,13 +24,17 @@
func (imageDiff *ImageDiff) Formater(flagInfo *input.FlagInfo) (string, error) {
if flagInfo.OutputSelected == "terminal" {
binaryStrings := ""
- binaryFunctions := map[string]func(*input.FlagInfo) string{
- "Version": imageDiff.BinaryDiff.FormatVersionDiff,
- "BuildID": imageDiff.BinaryDiff.FormatBuildIDDiff,
+ binaryFunctions := map[string]func() string{
+ "Version": imageDiff.BinaryDiff.FormatVersionDiff,
+ "BuildID": imageDiff.BinaryDiff.FormatBuildIDDiff,
+ "Rootfs": imageDiff.BinaryDiff.FormatRootfsDiff,
+ "Stateful-partition": imageDiff.BinaryDiff.FormatStatefulDiff,
+ "OS-config": imageDiff.BinaryDiff.FormatOSConfigDiff,
+ "Partition-structure": imageDiff.BinaryDiff.FormatPartitionStructureDiff,
}
for diff := range binaryFunctions {
if utilities.InArray(diff, flagInfo.BinaryTypesSelected) {
- binaryStrings += binaryFunctions[diff](flagInfo)
+ binaryStrings += binaryFunctions[diff]()
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt b/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt
new file mode 100644
index 0000000..6219d3d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt
@@ -0,0 +1,3 @@
+/proc/
+/usr/lib/
+/docker/util/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt b/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt
new file mode 100644
index 0000000..2e3c219
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt
@@ -0,0 +1 @@
+/var_overlay/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
new file mode 100644
index 0000000..928e759
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
@@ -0,0 +1,22 @@
+Disk /img_disks/cos_81_12871_119_disk/disk.raw: 20971520 sectors, 10.0 GiB
+Sector size (logical): 512 bytes
+Disk identifier (GUID): 0274E604-5DE3-5E4E-A4FD-F4D00FBBD7AA
+Partition table holds up to 128 entries
+Main partition table begins at sector 2 and ends at sector 33
+First usable sector is 34, last usable sector is 18874491
+Partitions will be aligned on 1-sector boundaries
+Total free space is 135145 sectors (66.0 MiB)
+
+Number Start (sector) End (sector) Size Code Name
+ 1 8704000 18874476 4.8 GiB 8300 STATE
+ 2 20480 53247 16.0 MiB 7F00 KERN-A
+ 3 4509696 8703999 2.0 GiB 7F01 ROOT-A
+ 4 53248 86015 16.0 MiB 7F00 KERN-B
+ 5 315392 4509695 2.0 GiB 7F01 ROOT-B
+ 6 16448 16448 512 bytes 7F00 KERN-C
+ 7 16449 16449 512 bytes 7F01 ROOT-C
+ 8 86016 118783 16.0 MiB 8300 OEM
+ 9 16450 16450 512 bytes 7F02 reserved
+ 10 16451 16451 512 bytes 7F02 reserved
+ 11 64 16447 8.0 MiB EF02 RWFW
+ 12 249856 315391 32.0 MiB EF00 EFI-SYSTEM
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/credentials.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/credentials.txt
new file mode 100644
index 0000000..e2b7a3d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/credentials.txt
@@ -0,0 +1,2 @@
+Name: docker.10.2.4
+job: makes micro kernels
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/docker.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/docker.txt
new file mode 100644
index 0000000..3ad3b6d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/docker.txt
@@ -0,0 +1 @@
+docker configurations for image 1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/lib32/lib32.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/lib32/lib32.txt
new file mode 100644
index 0000000..0433c78
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/lib32/lib32.txt
@@ -0,0 +1 @@
+32 bit library for docker
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/etc/os-release b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/os-release
similarity index 100%
rename from src/cmd/cos_image_analyzer/internal/testdata/image1/etc/os-release
rename to src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/os-release
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/lib64/python.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/lib64/python.txt
new file mode 100644
index 0000000..392bd39
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/lib64/python.txt
@@ -0,0 +1 @@
+Image 1's python info
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/access.conf b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/access.conf
new file mode 100644
index 0000000..764baa6
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/access.conf
@@ -0,0 +1 @@
+testing 123 can you hear me?
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/configs b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/configs
new file mode 100644
index 0000000..fb6d1d0
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/configs
@@ -0,0 +1 @@
+These are not the configs you are looking for
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/usr/lib/usr-lib-image1 b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/usr/lib/usr-lib-image1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/usr/lib/usr-lib-image1
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/dev_image/image1_dev.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/dev_image/image1_dev.txt
new file mode 100644
index 0000000..acf25dc
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/dev_image/image1_dev.txt
@@ -0,0 +1 @@
+dev space for image1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/lost+found/Theseus.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/lost+found/Theseus.txt
new file mode 100644
index 0000000..7a19555
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/lost+found/Theseus.txt
@@ -0,0 +1 @@
+This is just test data, if you are lost check the README file
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/var_overlay/db/image1_data.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/var_overlay/db/image1_data.txt
new file mode 100644
index 0000000..0cc08a0
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/var_overlay/db/image1_data.txt
@@ -0,0 +1 @@
+writeable data for the user on image1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
new file mode 100644
index 0000000..d202b1a
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
@@ -0,0 +1,22 @@
+Disk /img_disks/cos_77_12371_273_disk/disk.raw: 20971520 sectors, 10.0 GiB
+Sector size (logical): 512 bytes
+Disk identifier (GUID): AB9719F2-3174-4F46-8079-1CF470D2D9BC
+Partition table holds up to 128 entries
+Main partition table begins at sector 2 and ends at sector 33
+First usable sector is 34, last usable sector is 18874491
+Partitions will be aligned on 1-sector boundaries
+Total free space is 135145 sectors (66.0 MiB)
+
+Number Start (sector) End (sector) Size Code Name
+ 1 8704000 18874476 4.8 GiB 0700 STATE
+ 2 20480 53247 16.0 MiB 7F00 KERN-A
+ 3 4509696 8703999 2.0 GiB 7F01 ROOT-A
+ 4 53248 86015 16.0 MiB 7F00 KERN-B
+ 5 315392 4509695 2.0 GiB 7F01 ROOT-B
+ 6 16448 16448 512 bytes 7F00 KERN-C
+ 7 16449 16449 512 bytes 7F01 ROOT-C
+ 8 86016 118783 16.0 MiB 0700 OEM
+ 9 16450 16450 512 bytes 7F02 reserved
+ 10 16451 16451 512 bytes 7F02 reserved
+ 11 64 16447 8.0 MiB EF02 RWFW
+ 12 249856 315391 32.0 MiB EF00 EFI-SYSTEM
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/credentials.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/credentials.txt
new file mode 100644
index 0000000..357d7c7
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/credentials.txt
@@ -0,0 +1,2 @@
+Name: docker.10.2.1
+job: makes macro kernels
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/util/lib64/lib64.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/util/lib64/lib64.txt
new file mode 100644
index 0000000..2936bb7
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/util/lib64/lib64.txt
@@ -0,0 +1 @@
+64-bit library for docker
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/etc/os-release b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/os-release
similarity index 100%
rename from src/cmd/cos_image_analyzer/internal/testdata/image2/etc/os-release
rename to src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/os-release
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/lib64/python.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/lib64/python.txt
new file mode 100644
index 0000000..ce445c8
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/lib64/python.txt
@@ -0,0 +1 @@
+Image 2's python info
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/access.conf b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/access.conf
new file mode 100644
index 0000000..72ab259
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/access.conf
@@ -0,0 +1 @@
+testing 456 can you hear me?
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/configs b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/configs
new file mode 100644
index 0000000..87be8a5
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/configs
@@ -0,0 +1 @@
+These are the configs you are looking for
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/usr/lib/usr-lib-image2 b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/usr/lib/usr-lib-image2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/usr/lib/usr-lib-image2
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/dev_image/image2_dev.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/dev_image/image2_dev.txt
new file mode 100644
index 0000000..acf25dc
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/dev_image/image2_dev.txt
@@ -0,0 +1 @@
+dev space for image1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/lost+found/Theseus.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/lost+found/Theseus.txt
new file mode 100644
index 0000000..7a19555
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/lost+found/Theseus.txt
@@ -0,0 +1 @@
+This is just test data, if you are lost check the README file
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/var_overlay/db/image2_data.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/var_overlay/db/image2_data.txt
new file mode 100644
index 0000000..979e8c3
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/var_overlay/db/image2_data.txt
@@ -0,0 +1 @@
+writeable data for the user on image2
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/os-release-77 b/src/cmd/cos_image_analyzer/internal/testdata/os-release-77
deleted file mode 100644
index b21554c..0000000
--- a/src/cmd/cos_image_analyzer/internal/testdata/os-release-77
+++ /dev/null
@@ -1,2 +0,0 @@
-BUILD_ID=12371.273.0
-ID=cos
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/os-release-81 b/src/cmd/cos_image_analyzer/internal/testdata/os-release-81
deleted file mode 100644
index 826d8f8..0000000
--- a/src/cmd/cos_image_analyzer/internal/testdata/os-release-81
+++ /dev/null
@@ -1,3 +0,0 @@
-BUILD_ID=12871.119.0
-VERSION=81
-ID=cos
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
index f74cd8f..04b605d 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
@@ -1,10 +1,10 @@
package utilities
import (
- "errors"
+ "fmt"
"io"
"os"
- "path/filepath"
+ "os/exec"
"strings"
)
@@ -51,34 +51,37 @@
return 1
}
-// CopyFile copies a file over to a new destinaton
-// Input:
-// (string) path - Local path to the file
-// (string) dest - Destination to copy the file
-// Output:
-// (string) copiedFile - path to the newly copied file
-func CopyFile(path, dest string) (string, error) {
- info, err := os.Stat(path)
- if os.IsNotExist(err) || info.IsDir() {
- return "", errors.New("Error: " + path + " is not a file")
- }
-
- sourceFile, err := os.Open(path)
+// WriteToNewFile creates a file and writes a string into it
+func WriteToNewFile(filename string, data string) error {
+ file, err := os.Create(filename)
if err != nil {
- return "", errors.New("Error: failed to open file " + path)
+ return err
}
- defer sourceFile.Close()
+ defer file.Close()
- // Create new file
- copiedFile := filepath.Join(dest, info.Name())
- newFile, err := os.Create(copiedFile)
+ _, err = io.WriteString(file, data)
if err != nil {
- return "", errors.New("Error: failed to create file " + copiedFile)
+ return err
}
- defer newFile.Close()
+ return file.Sync()
+}
- if _, err := io.Copy(newFile, sourceFile); err != nil {
- return "", errors.New("Error: failed to copy " + path + " into " + copiedFile)
+// SliceToMapStr initializes a map with keys from input and empty strings as values
+func SliceToMapStr(input []string) map[string]string {
+ output := make(map[string]string)
+ for _, elem := range input {
+ output[elem] = ""
}
- return copiedFile, nil
+ return output
+}
+
+// Unmount umounts a mounted directory and deletes its loop device
+func Unmount(mountedDirectory, loopDevice string) error {
+ if _, err := exec.Command("sudo", "umount", mountedDirectory).Output(); err != nil {
+ return fmt.Errorf("failed to umount directory %v: %v", mountedDirectory, err)
+ }
+ if _, err := exec.Command("sudo", "losetup", "-d", loopDevice).Output(); err != nil {
+ return fmt.Errorf("failed to delete loop device %v: %v", loopDevice, err)
+ }
+ return nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
index b0b9e27..b513fc5 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
@@ -43,7 +43,6 @@
// (map[string]string) map2 - Second map to be compared
// (string) key - The key of the value be compared in both maps
// Output:
-// (stdout) terminal - If equal, print nothing. Else print difference
// (int) result - -1 for error, 0 for no difference, 1 for difference
func CmpMapValues(map1, map2 map[string]string, key string) (int, error) {
value1, ok1 := map1[key]
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
index 8e43712..64640f1 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
@@ -7,7 +7,7 @@
// test ReadFileToMap function
func TestReadFileToMap(t *testing.T) {
// test normal file
- testFile, sep := "../testdata/os-release-77", "="
+ testFile, sep := "../testdata/image2/rootfs/etc/os-release", "="
expectedMap := map[string]string{"BUILD_ID": "12371.273.0", "ID": "cos"}
resultMap, _ := ReadFileToMap(testFile, sep)
diff --git a/src/cmd/cos_image_analyzer/main.go b/src/cmd/cos_image_analyzer/main.go
index 003c111..15d9a4a 100644
--- a/src/cmd/cos_image_analyzer/main.go
+++ b/src/cmd/cos_image_analyzer/main.go
@@ -23,13 +23,20 @@
imageDiff := &output.ImageDiff{}
err := *new(error)
- if err := binary.GetBinaryInfo(image1); err != nil {
+ if err := binary.GetBinaryInfo(image1, flagInfo); err != nil {
return fmt.Errorf("failed to get GetBinaryInfo from image %v: %v", flagInfo.Image1, err)
}
- if err := binary.GetBinaryInfo(image2); err != nil {
+ if err := binary.GetBinaryInfo(image2, flagInfo); err != nil {
return fmt.Errorf("failed to GetBinaryInfo from image %v: %v", flagInfo.Image2, err)
}
- binaryDiff, err := binary.Diff(image1, image2)
+ if err := image1.Rename(flagInfo); err != nil {
+ return fmt.Errorf("failed to rename image %v: %v", flagInfo.Image1, err)
+ }
+ if err := image2.Rename(flagInfo); err != nil {
+ return fmt.Errorf("failed to rename image %v: %v", flagInfo.Image2, err)
+ }
+
+ binaryDiff, err := binary.Diff(image1, image2, flagInfo)
if err != nil {
return fmt.Errorf("failed to get Binary Difference: %v", err)
}
@@ -48,18 +55,11 @@
}
// CallCosImageAnalyzer is wrapper that gets the images, calls cosImageAnalyzer, and cleans up
-func CallCosImageAnalyzer(flagInfo *input.FlagInfo) error {
- image1, image2, err := input.GetImages(flagInfo)
- defer image1.Cleanup()
- defer image2.Cleanup()
- if err != nil {
- return fmt.Errorf("failed to get images: %v", err)
- }
-
- if err := image1.MountImage(); err != nil {
+func CallCosImageAnalyzer(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ if err := image1.MountImage(flagInfo.BinaryTypesSelected); err != nil {
return fmt.Errorf("failed to mount first image %v: %v", flagInfo.Image1, err)
}
- if err := image2.MountImage(); err != nil {
+ if err := image2.MountImage(flagInfo.BinaryTypesSelected); err != nil {
return fmt.Errorf("failed to mount second image %v: %v", flagInfo.Image2, err)
}
if err := cosImageAnalyzer(image1, image2, flagInfo); err != nil {
@@ -79,8 +79,32 @@
os.Exit(1)
}
- if err = CallCosImageAnalyzer(flagInfo); err != nil {
- log.Printf("%v\n", err)
+ image1, image2, err := input.GetImages(flagInfo)
+ if err != nil {
+ log.Printf("failed to get images: %v", err)
+ if err := image1.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
+ }
+ if err := image2.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
+ }
os.Exit(1)
}
+
+ if err = CallCosImageAnalyzer(image1, image2, flagInfo); err != nil {
+ log.Printf("%v\n", err)
+ if err := image1.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
+ }
+ if err := image2.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
+ }
+ os.Exit(1)
+ }
+ if err := image1.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
+ }
+ if err := image2.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
+ }
}