cos_image_analyzer: Milestone 2
Change-Id: I6cf45c2f1f72126e76dcca59dae3214fa8171dc3
diff --git a/src/cmd/cos_image_analyzer/internal/binary/binaryDiff.go b/src/cmd/cos_image_analyzer/internal/binary/binarydiff.go
similarity index 71%
rename from src/cmd/cos_image_analyzer/internal/binary/binaryDiff.go
rename to src/cmd/cos_image_analyzer/internal/binary/binarydiff.go
index 02355f8..7f22f4c 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/binaryDiff.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/binarydiff.go
@@ -14,11 +14,11 @@
// BinaryDiff is a tool that finds all binary differneces of two COS images
// (COS version, rootfs, kernel command line, stateful parition, ...)
-// Input:
-// (string) img1Path - The path to the root directory for COS image1
-// (string) img2Path - The path to the root directory for COS image2
-// Output:
-// (stdout) terminal ouput - All differences printed to the terminal
+//
+// Input: (string) img1Path - The path to the root directory for COS image1
+// (string) img2Path - The path to the root directory for COS image2
+//
+// Output: (stdout) terminal ouput - All differences printed to the terminal
func BinaryDiff(img1Path, img2Path string) error {
fmt.Println("================== Binary Differences ==================")
@@ -34,13 +34,15 @@
}
// Compare Version (Major)
- _, err := utilities.CmpMapValues(verMap1, verMap2, "VERSION")
+ _, err = utilities.CmpMapValues(verMap1, verMap2, "VERSION")
if err != nil {
return err
}
// Compare BUILD_ID (Minor)
- _, err := utilities.CmpMapValues(verMap1, verMap2, "BUILD_ID")
+ _, err = utilities.CmpMapValues(verMap1, verMap2, "BUILD_ID")
if err != nil {
return err
}
+
+ return nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/input/cleanup_api.go b/src/cmd/cos_image_analyzer/internal/input/cleanup_api.go
new file mode 100644
index 0000000..1d3bc50
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/input/cleanup_api.go
@@ -0,0 +1,23 @@
+package input
+
+import (
+ "os"
+ "os/exec"
+)
+
+// Cleanup is called to remove a mounted directory and its loop device
+// (string) mountDir - Active mount directory ready to close
+// (string) loopDevice - Active loop device ready to close
+// Output: nil on success, else error
+func Cleanup(mountDir, loopDevice string) error {
+ _, err := exec.Command("sudo", "umount", mountDir).Output()
+ if err != nil {
+ return err
+ }
+ _, err1 := exec.Command("sudo", "losetup", "-d", loopDevice).Output()
+ if err1 != nil {
+ return err1
+ }
+ os.Remove(mountDir)
+ return nil
+}
diff --git a/src/cmd/cos_image_analyzer/internal/input/gce_api.go b/src/cmd/cos_image_analyzer/internal/input/gce_api.go
new file mode 100644
index 0000000..c147b4c
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/input/gce_api.go
@@ -0,0 +1,96 @@
+package input
+
+import (
+ "bytes"
+ "encoding/json"
+ // "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "path/filepath"
+ "strings"
+)
+
+const timeOut = "7200"
+const imageFormat = "vmdk"
+const name = "gcr.io/compute-image-tools/gce_vm_image_export:release"
+
+type Steps struct {
+ Args [6]string `json:"args"`
+ Name string `json:"name"`
+ Env [1]string `json:"env"`
+}
+
+type GcePayload struct {
+ Timeout string `json:"timeout"`
+ Steps [1]Steps `json:"steps"`
+ Tags [2]string `json:"tags"`
+}
+
+// gceExport calls the cloud build REST api that exports a public compute
+// image to a specfic GCS bucket.
+// Input:
+// (string) projectID - project ID of the cloud project holding the image
+// (string) bucket - name of the GCS bucket holding the COS Image
+// (string) image - name of the source image to be exported
+// Output: None
+func gceExport(projectID, bucket, image string) error {
+ // API Variables
+ gceURL := "https://cloudbuild.googleapis.com/v1/projects/" + projectID + "/builds"
+ destURI := "gs://" + bucket + "/" + image + "." + imageFormat
+ args := [6]string{"-oauth=/usr/local/google/home/acueva/cos-googlesource/tools/src/cmd/cos_image_analyzer/internal/utilities/oauth.json", "-timeout=" + timeOut, "-source_image=" + image, "-client_id=api", "-format=" + imageFormat, "-destination_uri=" + destURI}
+ env := [1]string{"BUILD_ID=$BUILD_ID"}
+ tags := [2]string{"gce-daisy", "gce-daisy-image-export"}
+
+ // Build API bodies
+ steps := [1]Steps{Steps{Args: args, Name: name, Env: env}}
+ payload := &GcePayload{
+ Timeout: timeOut,
+ Steps: steps,
+ Tags: tags}
+
+ requestBody, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+ log.Println(string(requestBody))
+
+ resp, err := http.Post(gceURL, "application/json", bytes.NewBuffer(requestBody))
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ log.Println(string(body))
+ return nil
+}
+
+// GetCosImage calls the cloud build api to export a public COS image to a
+// a GCS bucket and then calls GetGcsImage() to download that image from GCS.
+// ADC is used for authorization.
+// Input:
+// (string) cosCloudPath - The "projectID/gcs-bucket/image" path of the
+// source image to be exported
+// Output:
+// (string) imageDir - Path to the mounted directory of the COS Image
+func GetCosImage(cosCloudPath string) (string, error) {
+ spiltPath := strings.Split(cosCloudPath, "/")
+ projectID, bucket, image := spiltPath[0], spiltPath[1], spiltPath[2]
+
+ if err := gceExport(projectID, bucket, image); err != nil {
+ return "", err
+ }
+
+ gcsPath := filepath.Join(bucket, image)
+ imageDir, err := GetGcsImage(gcsPath, 1)
+ if err != nil {
+ return "", err
+ }
+
+ return imageDir, nil
+}
diff --git a/src/cmd/cos_image_analyzer/internal/input/gcs_api.go b/src/cmd/cos_image_analyzer/internal/input/gcs_api.go
new file mode 100644
index 0000000..3f657d9
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/input/gcs_api.go
@@ -0,0 +1,155 @@
+package input
+
+import (
+ "bytes"
+ "cloud.google.com/go/storage"
+ "context"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const contextTimeOut = time.Second * 50
+
+// gcsDowndload calls the GCS client api to download a specifed object from
+// a GCS bucket. ADC is used for authorization
+// Input:
+// (io.Writier) w - Output destination for download info
+// (string) bucket - Name of the GCS bucket
+// (string) object - Name of the GCS object
+// (string) destDir - Destination for downloaded GCS object
+// Output:
+// (string) downloadedFile - Path to downloaded GCS object
+func gcsDowndload(w io.Writer, bucket, object, destDir string) (string, error) {
+ // Call API to download GCS object into tempDir
+ ctx := context.Background()
+ client, err := storage.NewClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ defer client.Close()
+
+ ctx, cancel := context.WithTimeout(ctx, contextTimeOut)
+ defer cancel()
+
+ rc, err := client.Bucket(bucket).Object(object).NewReader(ctx)
+ if err != nil {
+ return "", err
+ }
+ defer rc.Close()
+
+ data, err := ioutil.ReadAll(rc)
+ if err != nil {
+ return "", err
+ }
+
+ log.Print(log.New(w, "Blob "+object+" downloaded.\n", log.Ldate|log.Ltime|log.Lshortfile))
+
+ downloadedFile := filepath.Join(destDir, object)
+ if err := ioutil.WriteFile(downloadedFile, data, 0666); err != nil {
+ return "", err
+ }
+ return downloadedFile, nil
+}
+
+// getPartitionStart finds the start partition offset of the disk
+// Input:
+// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
+// (string) parition - The parition number you are pulling the offset from
+// Output:
+// (int) start - The start of the partition on the disk
+func getPartitionStart(partition, diskRaw string) (int, error) {
+ //create command
+ cmd1 := exec.Command("fdisk", "-l", diskRaw)
+ cmd2 := exec.Command("grep", "disk.raw"+partition)
+
+ reader, writer := io.Pipe()
+ var buf bytes.Buffer
+
+ cmd1.Stdout = writer
+ cmd2.Stdin = reader
+ cmd2.Stdout = &buf
+
+ cmd1.Start()
+ cmd2.Start()
+ cmd1.Wait()
+ writer.Close()
+ cmd2.Wait()
+ reader.Close()
+
+ words := strings.Fields(buf.String())
+ start, err := strconv.Atoi(words[1])
+ if err != nil {
+ return -1, err
+ }
+
+ return start, 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)
+// (string) mountDir - Mount Destiination
+// Output: nil on success, else error
+func mountDisk(diskFile, mountDir string, flag int) error {
+ sectorSize := 512
+ startOfPartition, err := getPartitionStart("3", diskFile)
+ if err != nil {
+ return err
+ }
+ offset := strconv.Itoa(sectorSize * startOfPartition)
+ out, err := exec.Command("sudo", "losetup", "--show", "-fP", diskFile).Output()
+ if err != nil {
+ return err
+ }
+ _, err1 := exec.Command("sudo", "mount", "-o", "ro,loop,offset="+offset, string(out[:len(out)-1]), mountDir).Output()
+ if err1 != nil {
+ return err1
+ }
+
+ return nil
+}
+
+// GetGcsImage calls the GCS client api that downloads a specifed object from
+// a GCS bucket and unzips its contents. ADC is used for authorization
+// Input:
+// (string) gcsPath - GCS "bucket/object" path for COS Image (.tar.gz file)
+// Output:
+// (string) imageDir - Path to the mounted directory of the COS Image
+func GetGcsImage(gcsPath string, flag int) (string, error) {
+ bucket := strings.Split(gcsPath, "/")[0]
+ object := strings.Split(gcsPath, "/")[1]
+
+ tempDir, err := ioutil.TempDir(".", "tempDir-"+object) // Removed at end
+ if err != nil {
+ return "", err
+ }
+
+ tarFile, err := gcsDowndload(os.Stdout, bucket, object, tempDir)
+ if err != nil {
+ return "", err
+ }
+
+ imageDir := filepath.Join(tempDir, "Image-"+object)
+ if err = os.Mkdir(imageDir, 0700); err != nil {
+ return "", err
+ }
+
+ _, err1 := exec.Command("tar", "-xzf", tarFile, "-C", imageDir).Output()
+ if err1 != nil {
+ return "", err1
+ }
+
+ diskRaw := filepath.Join(imageDir, "disk.raw")
+ if err = mountDisk(diskRaw, imageDir, flag); err != nil {
+ return "", err
+ }
+
+ return imageDir, nil
+}
diff --git a/src/cmd/cos_image_analyzer/internal/input/parse_input.go b/src/cmd/cos_image_analyzer/internal/input/parse_input.go
new file mode 100644
index 0000000..9e081d1
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/input/parse_input.go
@@ -0,0 +1,94 @@
+package input
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+)
+
+// Custom usage function. See -h flag
+func printUsage() {
+ usageTemplate := `NAME
+cos_image_analyzer - finds all meaningful differences of two COS Images
+(binary, package, commit, and release notes differences)
+
+SYNOPSIS
+%s [-local] DIRECTORY-1 DIRECTORY-2 (default true)
+ DIRECTORY 1/2 - the local directory path to the root of the COS Image
+
+%s [-gcs] GCS-PATH-1 GCS-PATH-2
+ GCS-PATH 1/2 - GCS "bucket/object" path for the COS Image (.tar.gz file)
+ Ex: %s -gcs my-bucket/cos-77-12371-273-0.tar.gz my-bucket/cos-81-12871-119-0.tar.gz
+
+%s [-cos-cloud] COS-CLOUD-PATH-1 COS-CLOUD-PATH-2
+ COS-CLOUD-PATH 1/2 - The "projectID/gcs-bucket/image" path of the source image to be exported
+ Ex: %s -cos-cloud my-project/my-bucket/my-exported-image1 my-project/my-bucket/my-exported-image2
+
+DESCRIPTION
+`
+ usage := fmt.Sprintf(usageTemplate, os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0])
+ fmt.Printf("%s", usage)
+ flag.PrintDefaults()
+ fmt.Println("\nOUTPUT\n(stdout) terminal output - All differences printed to the terminal")
+}
+
+// ParseInput handles the input based on its type and returns the root
+// directory path of both images to the start of the CosImageAnalyzer
+//
+// Input: None (reads command-line args)
+//
+// Output: (string) rootImg1 - The local filesystem path for COS image1
+// (string) rootImg2 - The local filesystem path for COS image2
+func ParseInput() (string, string, error) {
+ // Flag Declaration
+ flag.Usage = printUsage
+ localPtr := flag.Bool("local", true, "input is two mounted images on local filesystem")
+ gcsPtr := flag.Bool("gcs", false, "input is two objects stored on Google Cloud Storage")
+ cosCloudPtr := flag.Bool("cos-cloud", false, "input is two public COS-cloud images")
+ flag.Parse()
+
+ if flag.NFlag() > 1 {
+ printUsage()
+ return "", "", errors.New("Error: Only one flag allowed")
+ }
+
+ // Input Selection
+ if *gcsPtr {
+ if len(flag.Args()) != 2 {
+ printUsage()
+ return "", "", errors.New("Error: GCS input requires two agruments")
+ }
+ rootImg1, err := GetGcsImage(flag.Args()[0], 1)
+ if err != nil {
+ return "", "", err
+ }
+ rootImg2, err := GetGcsImage(flag.Args()[1], 2)
+ if err != nil {
+ return "", "", err
+ }
+ return rootImg1, rootImg2, nil
+ } else if *cosCloudPtr {
+ if len(flag.Args()) != 2 {
+ printUsage()
+ return "", "", errors.New("Error: COS-cloud input requires two agruments")
+ }
+ rootImg1, err := GetCosImage(flag.Args()[0])
+ if err != nil {
+ return "", "", err
+ }
+ rootImg2, err := GetCosImage(flag.Args()[1])
+ if err != nil {
+ return "", "", err
+ }
+ return rootImg1, rootImg2, nil
+ } else if *localPtr {
+ if len(flag.Args()) != 2 {
+ printUsage()
+ return "", "", errors.New("Error: Local input requires two arguments")
+ }
+ return flag.Args()[0], flag.Args()[1], nil
+ }
+ printUsage()
+ return "", "", errors.New("Error: At least one flag needs to be true")
+}
diff --git a/src/cmd/cos_image_analyzer/internal/testData/os-release-77 b/src/cmd/cos_image_analyzer/internal/testdata/os-release-77
similarity index 100%
rename from src/cmd/cos_image_analyzer/internal/testData/os-release-77
rename to src/cmd/cos_image_analyzer/internal/testdata/os-release-77
diff --git a/src/cmd/cos_image_analyzer/internal/testData/os-release-81 b/src/cmd/cos_image_analyzer/internal/testdata/os-release-81
similarity index 100%
rename from src/cmd/cos_image_analyzer/internal/testData/os-release-81
rename to src/cmd/cos_image_analyzer/internal/testdata/os-release-81
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
new file mode 100644
index 0000000..bfdc0ea
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
@@ -0,0 +1,9 @@
+package utilities
+
+// // Helper Function for error checking
+// func check(e error) error {
+// if e != nil {
+// return e
+// }
+// return nil
+// }
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/mapHelpers.go b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
similarity index 69%
rename from src/cmd/cos_image_analyzer/internal/utilities/mapHelpers.go
rename to src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
index 7c8de36..06b1d39 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/mapHelpers.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
@@ -12,11 +12,10 @@
// key: first word split by separator, value: rest of line after separator.
// Ex: Inputs: textLine: "NAME=Container-Optimized OS", sep: "="
// Outputs: map: {"NAME":"Container-Optimized OS"}
-// Input:
-// (string) filePath - The command-line path to the text file
-// (string) sep - The separator string for the key and value pairs
-// Output:
-// (map[string]string) mapOfFile - The map of the read-in text file
+//
+// Input: (string) filePath - The command-line path to the text file
+// (string) sep - The separator string for the key and value pairs
+// Output: (map[string]string) mapOfFile - The map of the read-in text file
func ReadFileToMap(filePath, sep string) (map[string]string, error) {
file, err := os.Open(filePath)
if err != nil {
@@ -32,19 +31,18 @@
}
if scanner.Err() != nil {
- return map[string]string{}, err
+ return map[string]string{}, scanner.Err()
}
return mapOfFile, nil
}
// CmpMapValues is a helper function that compares a value shared by two maps
-// Input:
-// (map[string]string) map1 - First map to be compared
-// (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
+// Input: (map[string]string) map1 - First map to be compared
+// (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 error, 0 for no difference, 1 for difference
func CmpMapValues(map1, map2 map[string]string, key string) (int, error) {
value1, ok1 := map1[key]
value2, ok2 := map2[key]
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/mapHelpers_test.go b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
similarity index 74%
rename from src/cmd/cos_image_analyzer/internal/utilities/mapHelpers_test.go
rename to src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
index 136aa31..8e43712 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/mapHelpers_test.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
@@ -7,9 +7,9 @@
// test ReadFileToMap function
func TestReadFileToMap(t *testing.T) {
// test normal file
- testFile, sep := "../testData/os-release-77", "="
+ testFile, sep := "../testdata/os-release-77", "="
expectedMap := map[string]string{"BUILD_ID": "12371.273.0", "ID": "cos"}
- resultMap := ReadFileToMap(testFile, sep)
+ resultMap, _ := ReadFileToMap(testFile, sep)
// Compare result with expected
if resultMap["BUILD_ID"] != expectedMap["BUILD_ID"] && resultMap["ID"] != expectedMap["ID"] {
@@ -25,12 +25,12 @@
testKey1, testKey2 := "ID", "VERSION"
// test similar keys
- if result1 := CmpMapValues(testMap1, testMap2, testKey1); result1 != 0 { // Expect 0 for same values
+ if result1, _ := CmpMapValues(testMap1, testMap2, testKey1); result1 != 0 { // Expect 0 for same values
t.Errorf("CmpMapValues failed, expected %v, got %v", 0, result1)
}
// test different keys
- if result2 := CmpMapValues(testMap1, testMap2, testKey2); result2 != 1 { // Expect 1 for different values
+ if result2, _ := CmpMapValues(testMap1, testMap2, testKey2); result2 != 1 { // Expect 1 for different values
t.Errorf("CmpMapValues failed, expected %v, got %v", 1, result2)
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/usage.go b/src/cmd/cos_image_analyzer/internal/utilities/usage.go
deleted file mode 100644
index 2f58383..0000000
--- a/src/cmd/cos_image_analyzer/internal/utilities/usage.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package utilities
-
-import (
- "flag"
- "fmt"
- "os"
-)
-
-// Custom usage function. See -h flag
-func printUsage() {
- fmt.Println("NAME\ncos_image_analyzer - finds all meaningful differences of two COS Images")
- fmt.Print("(binary, package, commit, and release notes differences)\n\n")
- fmt.Printf("SYNOPSIS\n%s [OPTION] argument1 argument2\n\nDESCRIPTION\n", os.Args[0])
- fmt.Print("Default: input arguments are two local filesystem paths to root directiory of COS images\n\n")
- flag.PrintDefaults()
- fmt.Println("\nOUPUT\n(stdout) terminal ouput - All differences printed to the terminal")
-}
diff --git a/src/cmd/cos_image_analyzer/main.go b/src/cmd/cos_image_analyzer/main.go
index e81f15e..017d44a 100644
--- a/src/cmd/cos_image_analyzer/main.go
+++ b/src/cmd/cos_image_analyzer/main.go
@@ -1,24 +1,23 @@
// cos_Image_Analyzer finds all the meaningful differences of two COS Images
// (binary, package, commit, and release notes differences)
-// Input:
-// (string) img1Path - The path for COS image1
-// (string) img2Path - The path for COS image2
-// (int) inputFlag - 0-Local filesystem path to root directory,
-// 1-COS cloud names, 2-GCS object names
-// Output:
-// (stdout) terminal ouput - All differences printed to the terminal
+//
+// Input: (string) rootImg1 - The path for COS image1
+// (string) rootImg2 - The path for COS image2
+// (int) inputFlag - 0-Local filesystem path to root directory,
+// 1-COS cloud names, 2-GCS object names
+//
+// Output: (stdout) terminal ouput - All differences printed to the terminal
package main
import (
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/binary"
- "cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
- "flag"
- "log"
+ "cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
+ "fmt"
"os"
"runtime"
)
-func cosImageAnalyzer(img1Path, img2Path string, inputFlag int) error {
+func cosImageAnalyzer(img1Path, img2Path string) error {
err := binary.BinaryDiff(img1Path, img2Path)
if err != nil {
return err
@@ -28,22 +27,21 @@
func main() {
if runtime.GOOS != "linux" {
- log.Fatalf("Error: This is a Linux tool, can not run on %s", runtime.GOOS)
+ fmt.Printf("Error: This is a Linux tool, can not run on %s", runtime.GOOS)
+ os.Exit(1)
}
- // Flag Declartions
- flag.Usage = utilities.printUsage
- cloudPtr := flag.Bool("cloud", false, "input arguments are two cos-cloud images")
- gcsPtr := flag.Bool("gcs", false, "input arguments are two gcs objects")
- flag.Parse()
- if flag.NFlag() > 1 || len(flag.Args()) != 2 {
- log.Fatalf("Error: %s requires at most one flag and two arguments. Use -h flag for usage", os.Args[0])
+ rootImg1, rootImg2, err := input.ParseInput()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
}
- inputFlag := 0
- if *cloudPtr {
- inputFlag = 1
- } else if *gcsPtr {
- inputFlag = 2
+ err1 := cosImageAnalyzer(rootImg1, rootImg2)
+ if err1 != nil {
+ fmt.Println(err1)
+ os.Exit(1)
}
- cosImageAnalyzer(flag.Args()[0], flag.Args()[1], inputFlag)
+ // Cleanup(rootImg1, loop1) Debating on a struct that holds this info
+ // Cleanup(rootImg2, loop2)
+
}