cmd/report2cve,internal/report: refactor

Logic for cmd/report2cve is moved to internal/report.

Change-Id: Iaa087765fc2ee7528e3a818651a9a81bfed469cf
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/373002
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/srv/cmd/report2cve/main.go b/srv/cmd/report2cve/main.go
index fec35d7..55c99b6 100644
--- a/srv/cmd/report2cve/main.go
+++ b/srv/cmd/report2cve/main.go
@@ -8,150 +8,21 @@
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
-	"io/ioutil"
+	"log"
 	"os"
-	"strings"
 
-	"golang.org/x/vuln/srv/internal/cveschema"
-	"golang.org/x/vuln/srv/internal/derrors"
 	"golang.org/x/vuln/srv/internal/report"
-	"gopkg.in/yaml.v2"
 )
 
-func fromReport(r *report.Report) (_ *cveschema.CVE, err error) {
-	defer derrors.Wrap(&err, "fromReport(r)")
-	if r.CVE != "" || len(r.CVEs) > 0 {
-		return nil, errors.New("report has CVE ID is wrong section (should be in cve_metadata for self-issued CVEs)")
-	}
-	if r.CVEMetadata == nil {
-		return nil, errors.New("report missing cve_metadata section")
-	}
-	if r.CVEMetadata.ID == "" {
-		return nil, errors.New("report missing CVE ID")
-	}
-
-	c := &cveschema.CVE{
-		DataType:    "CVE",
-		DataFormat:  "MITRE",
-		DataVersion: "4.0",
-		Metadata: cveschema.Metadata{
-			ID:       r.CVEMetadata.ID,
-			Assigner: "security@golang.org",
-			State:    cveschema.StatePublic,
-		},
-
-		Description: cveschema.Description{
-			Data: []cveschema.LangString{
-				{
-					Lang:  "eng",
-					Value: strings.TrimSuffix(r.CVEMetadata.Description, "\n"),
-				},
-			},
-		},
-
-		ProblemType: cveschema.ProblemType{
-			Data: []cveschema.ProblemTypeDataItem{
-				{
-					Description: []cveschema.LangString{
-						{
-							Lang:  "eng",
-							Value: r.CVEMetadata.CWE,
-						},
-					},
-				},
-			},
-		},
-
-		Affects: cveschema.Affects{
-			Vendor: cveschema.Vendor{
-				Data: []cveschema.VendorDataItem{
-					{
-						VendorName: "n/a", // ???
-						Product: cveschema.Product{
-							Data: []cveschema.ProductDataItem{
-								{
-									ProductName: r.Package,
-									Version:     versionToVersion(r.Versions),
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	for _, additional := range r.AdditionalPackages {
-		c.Affects.Vendor.Data = append(c.Affects.Vendor.Data, cveschema.VendorDataItem{
-			VendorName: "n/a",
-			Product: cveschema.Product{
-				Data: []cveschema.ProductDataItem{
-					{
-						ProductName: additional.Package,
-						Version:     versionToVersion(additional.Versions),
-					},
-				},
-			},
-		})
-	}
-
-	if r.Links.PR != "" {
-		c.References.Data = append(c.References.Data, cveschema.Reference{URL: r.Links.PR})
-	}
-	if r.Links.Commit != "" {
-		c.References.Data = append(c.References.Data, cveschema.Reference{URL: r.Links.Commit})
-	}
-	for _, url := range r.Links.Context {
-		c.References.Data = append(c.References.Data, cveschema.Reference{URL: url})
-	}
-
-	return c, nil
-}
-
-func versionToVersion(versions []report.VersionRange) cveschema.VersionData {
-	vd := cveschema.VersionData{}
-	for _, vr := range versions {
-		if vr.Introduced != "" {
-			vd.Data = append(vd.Data, cveschema.VersionDataItem{
-				VersionValue:    vr.Introduced,
-				VersionAffected: ">=",
-			})
-		}
-		if vr.Fixed != "" {
-			vd.Data = append(vd.Data, cveschema.VersionDataItem{
-				VersionValue:    vr.Fixed,
-				VersionAffected: "<",
-			})
-		}
-	}
-	return vd
-}
-
 func main() {
 	if len(os.Args) != 2 {
 		fmt.Fprint(os.Stderr, "usage: report2cve report.yaml")
 		os.Exit(1)
 	}
-
-	reportPath := os.Args[1]
-	b, err := ioutil.ReadFile(reportPath)
+	cve, err := report.ToCVE(os.Args[1])
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "failed to read %q: %s\n", reportPath, err)
-		os.Exit(1)
-	}
-
-	var r report.Report
-	if err = yaml.UnmarshalStrict(b, &r); err != nil {
-		fmt.Fprintf(os.Stderr, "failed to parse %q: %s\n", reportPath, err)
-		os.Exit(1)
-	}
-
-	cve, err := fromReport(&r)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "failed to generate CVE: %s\n", err)
-		os.Exit(1)
+		log.Fatal(err)
 	}
 
 	// We need to use an encoder so that it doesn't escape angle
diff --git a/srv/internal/report/cve.go b/srv/internal/report/cve.go
new file mode 100644
index 0000000..94e32f0
--- /dev/null
+++ b/srv/internal/report/cve.go
@@ -0,0 +1,136 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package report
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"strings"
+
+	"golang.org/x/vuln/srv/internal/cveschema"
+	"golang.org/x/vuln/srv/internal/derrors"
+	"gopkg.in/yaml.v2"
+)
+
+func ToCVE(reportPath string) (_ *cveschema.CVE, err error) {
+	defer derrors.Wrap(&err, "report.ToCVE(%q)", reportPath)
+
+	b, err := ioutil.ReadFile(reportPath)
+	if err != nil {
+		return nil, fmt.Errorf("ioutil.ReadFile(%q): %v", reportPath, err)
+	}
+
+	var r Report
+	if err = yaml.UnmarshalStrict(b, &r); err != nil {
+		return nil, fmt.Errorf("yaml.Unmarshal:: %v", err)
+	}
+
+	if r.CVE != "" || len(r.CVEs) > 0 {
+		return nil, errors.New("report has CVE ID is wrong section (should be in cve_metadata for self-issued CVEs)")
+	}
+	if r.CVEMetadata == nil {
+		return nil, errors.New("report missing cve_metadata section")
+	}
+	if r.CVEMetadata.ID == "" {
+		return nil, errors.New("report missing CVE ID")
+	}
+
+	c := &cveschema.CVE{
+		DataType:    "CVE",
+		DataFormat:  "MITRE",
+		DataVersion: "4.0",
+		Metadata: cveschema.Metadata{
+			ID:       r.CVEMetadata.ID,
+			Assigner: "security@golang.org",
+			State:    cveschema.StatePublic,
+		},
+
+		Description: cveschema.Description{
+			Data: []cveschema.LangString{
+				{
+					Lang:  "eng",
+					Value: strings.TrimSuffix(r.CVEMetadata.Description, "\n"),
+				},
+			},
+		},
+
+		ProblemType: cveschema.ProblemType{
+			Data: []cveschema.ProblemTypeDataItem{
+				{
+					Description: []cveschema.LangString{
+						{
+							Lang:  "eng",
+							Value: r.CVEMetadata.CWE,
+						},
+					},
+				},
+			},
+		},
+
+		Affects: cveschema.Affects{
+			Vendor: cveschema.Vendor{
+				Data: []cveschema.VendorDataItem{
+					{
+						VendorName: "n/a", // ???
+						Product: cveschema.Product{
+							Data: []cveschema.ProductDataItem{
+								{
+									ProductName: r.Package,
+									Version:     versionToVersion(r.Versions),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, additional := range r.AdditionalPackages {
+		c.Affects.Vendor.Data = append(c.Affects.Vendor.Data, cveschema.VendorDataItem{
+			VendorName: "n/a",
+			Product: cveschema.Product{
+				Data: []cveschema.ProductDataItem{
+					{
+						ProductName: additional.Package,
+						Version:     versionToVersion(additional.Versions),
+					},
+				},
+			},
+		})
+	}
+
+	if r.Links.PR != "" {
+		c.References.Data = append(c.References.Data, cveschema.Reference{URL: r.Links.PR})
+	}
+	if r.Links.Commit != "" {
+		c.References.Data = append(c.References.Data, cveschema.Reference{URL: r.Links.Commit})
+	}
+	for _, url := range r.Links.Context {
+		c.References.Data = append(c.References.Data, cveschema.Reference{URL: url})
+	}
+
+	return c, nil
+}
+
+func versionToVersion(versions []VersionRange) cveschema.VersionData {
+	vd := cveschema.VersionData{}
+	for _, vr := range versions {
+		if vr.Introduced != "" {
+			vd.Data = append(vd.Data, cveschema.VersionDataItem{
+				VersionValue:    vr.Introduced,
+				VersionAffected: ">=",
+			})
+		}
+		if vr.Fixed != "" {
+			vd.Data = append(vd.Data, cveschema.VersionDataItem{
+				VersionValue:    vr.Fixed,
+				VersionAffected: "<",
+			})
+		}
+	}
+	return vd
+}