Format status json

This commit is contained in:
Alvaro Muñoz
2023-03-28 23:06:59 +02:00
parent 18e38d441e
commit 14c19d1654
2 changed files with 88 additions and 75 deletions

View File

@@ -5,30 +5,37 @@
## Configuration ## Configuration
A configuration file will be created in `~/.config/mrva/config.yml`. The following options are supported: A configuration file will be created in `~/.config/mrva/config.yml`. The following options are supported:
- `codeql-path`: Path to CodeQL distribution
- `controller`: NWO of the MRVA controller to use - `controller`: NWO of the MRVA controller to use
- `listFile`: Path to the JSON file containing the target repos - `listFile`: Path to the JSON file containing the target repos
## Usage ## Usage
Until the extension gets published you can use `go run .` instead of `gh mrva`
### Submit a new query ### Submit a new query
```bash ```bash
gh mrva submit [--controller <CONTROLLER>] --lang <LANGUAGE> [--list-file <LISTFILE>] --list <LIST> --query <QUERY> [--name <NAME>] gh mrva submit [--codeql-path<path to CodeQL>] [--controller <controller>] --lang <language> --name <run name> [--list-file <list file>] --list <list> [--query <query> | --query-suite <query suite> ]
``` ```
Note: `controller` and `list-file` are only optionals if defined in the configuration file Note: `codeql-dist`, `controller` and `list-file` are only optionals if defined in the configuration file
Note: if a `name` (any arbitrary name) is provided, the resulting run IDs will be stored in the configuration file so they can be referenced later for download
### Download the results ### Download the results
```bash ```bash
gh mrva download [--controller <CONTROLLER>] --lang <LANGUAGE> --output-dir <OUTPUTDIR> [--name <NAME> | --run <ID>] [--download-dbs] gh mrva download --name <run name> --output-dir <output directory> [--download-dbs] [--nwo <owner/repo>]
``` ```
Note: `controller` is only optionals if defined in the configuration file ### List sessions
Note: if a `name` is provided, the run ID is not necessary and instead `gh-mrva` will download the artifacts associated that `name` as found in the configuration file
```bash
gh mrva list [--json]
```
### Check scan status
```bash
gh mrva status --name <run name> [--json]
```
## Contributing ## Contributing

138
main.go
View File

@@ -10,7 +10,7 @@ import (
"fmt" "fmt"
"github.com/cli/go-gh" "github.com/cli/go-gh"
"github.com/cli/go-gh/pkg/api" "github.com/cli/go-gh/pkg/api"
"github.com/cli/go-gh/pkg/jsonpretty" // "github.com/cli/go-gh/pkg/jsonpretty"
"github.com/google/uuid" "github.com/google/uuid"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
@@ -476,7 +476,7 @@ func downloadDatabase(nwo string, language string, outputDir string) error {
return nil return nil
} }
func saveInHistory(name string, controller string, runIds []int, language string, listFile string, list string, query string, count int) error { func saveInHistory(name string, controller string, runs []Run, language string, listFile string, list string, query string, count int) error {
configData, err := getConfig(configFilePath) configData, err := getConfig(configFilePath)
if err != nil { if err != nil {
return err return err
@@ -490,13 +490,12 @@ func saveInHistory(name string, controller string, runIds []int, language string
} else { } else {
configData.History[name] = HistoryEntry{ configData.History[name] = HistoryEntry{
Name: name, Name: name,
RunIds: runIds, Runs: runs,
Timestamp: time.Now(), Timestamp: time.Now(),
Controller: controller, Controller: controller,
Language: language, Language: language,
ListFile: listFile, ListFile: listFile,
List: list, List: list,
Query: query,
RepositoryCount: count, RepositoryCount: count,
} }
} }
@@ -513,14 +512,14 @@ func saveInHistory(name string, controller string, runIds []int, language string
return nil return nil
} }
func loadFromHistory(name string) (string, []int, string, error) { func loadFromHistory(name string) (string, []Run, string, error) {
configData, err := getConfig(configFilePath) configData, err := getConfig(configFilePath)
if err != nil { if err != nil {
return "", nil, "", err return "", nil, "", err
} }
if configData.History != nil { if configData.History != nil {
if entry, ok := configData.History[name]; ok { if entry, ok := configData.History[name]; ok {
return entry.Controller, entry.RunIds, entry.Language, nil return entry.Controller, entry.Runs, entry.Language, nil
} }
} }
return "", nil, "", errors.New("No history entry found for " + name) return "", nil, "", errors.New("No history entry found for " + name)
@@ -539,15 +538,19 @@ func getConfig(path string) (Config, error) {
return configData, nil return configData, nil
} }
type Run struct {
Id int `yaml:"run_id"`
Query string `yaml:"query"`
}
type HistoryEntry struct { type HistoryEntry struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Timestamp time.Time `yaml:"timestamp"` Timestamp time.Time `yaml:"timestamp"`
RunIds []int `yaml:"runIds"` Runs []Run `yaml:"runIds"`
Controller string `yaml:"controller"` Controller string `yaml:"controller"`
ListFile string `yaml:"listFile"` ListFile string `yaml:"listFile"`
List string `yaml:"list"` List string `yaml:"list"`
Language string `yaml:"language"` Language string `yaml:"language"`
Query string `yaml:"query"`
RepositoryCount int `yaml:"repositoryCount"` RepositoryCount int `yaml:"repositoryCount"`
} }
type Config struct { type Config struct {
@@ -589,13 +592,12 @@ func main() {
helpFlag := flag.String("help", "", "This help documentation.") helpFlag := flag.String("help", "", "This help documentation.")
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, ` fmt.Fprintf(os.Stderr, `gh mrva - Run CodeQL queries at scale using Multi-Repository Variant Analysis (MRVA)
gh mrva - submit and download CodeQL queries from MRVA
Usage: Usage:
gh mrva submit [--codeql-dist <path to CodeQL dist>] [--controller <controller>] --lang <language> --name <run name> [--list-file <list file>] --list <list> [--query <query> | --query-suite <query suite>] gh mrva submit [--codeql-path <path to CodeQL>] [--controller <controller>] --lang <language> --name <run name> [--list-file <list file>] --list <list> [--query <query> | --query-suite <query suite>]
gh mrva download --name <run name> --output-dir <output directory> [--download-dbs] gh mrva download --name <run name> --output-dir <output directory> [--download-dbs] [--nwo <owner/repo>]
gh mrva status --name <run name> [--json] gh mrva status --name <run name> [--json]
@@ -638,8 +640,7 @@ func status(args []string) {
jsonFlag := flag.Bool("json", false, "Output in JSON format (default: false)") jsonFlag := flag.Bool("json", false, "Output in JSON format (default: false)")
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, ` fmt.Fprintf(os.Stderr, `gh mrva - Run CodeQL queries at scale using Multi-Repository Variant Analysis (MRVA)
gh mrva - submit and download CodeQL queries from MRVA
Usage: Usage:
gh mrva status --name <run name> [--json] gh mrva status --name <run name> [--json]
@@ -662,45 +663,47 @@ Usage:
os.Exit(1) os.Exit(1)
} }
controller, runIds, _, err := loadFromHistory(runName) controller, runs, _, err := loadFromHistory(runName)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if len(runIds) == 0 { if len(runs) == 0 {
log.Fatal("No runs found for run name", runName) log.Fatal("No runs found for run name", runName)
} }
type Run struct { type RunStatus struct {
Id int Id int `json:"id"`
Status string Query string `json:"query"`
FailureReason string Status string `json:"status"`
FailureReason string `json:"failure_reason"`
} }
type RepoWithFindings struct { type RepoWithFindings struct {
Nwo string Nwo string `json:"nwo"`
Count int Count int `json:"count"`
RunId int `json:"run_id"`
} }
type Results struct { type Results struct {
Runs []Run Runs []RunStatus `json:"runs"`
ResositoriesWithFindings []RepoWithFindings ResositoriesWithFindings []RepoWithFindings `json:"repositories_with_findings"`
TotalFindingsCount int TotalFindingsCount int `json:"total_findings_count"`
TotalSuccessfulScans int TotalSuccessfulScans int `json:"total_successful_scans"`
TotalFailedScans int TotalFailedScans int `json:"total_failed_scans"`
TotalRepositoriesWithFindings int TotalRepositoriesWithFindings int `json:"total_repositories_with_findings"`
TotalSkippedRepositories int TotalSkippedRepositories int `json:"total_skipped_repositories"`
TotalSkippedAccessMismatchRepositories int TotalSkippedAccessMismatchRepositories int `json:"total_skipped_access_mismatch_repositories"`
TotalSkippedNotFoundRepositories int TotalSkippedNotFoundRepositories int `json:"total_skipped_not_found_repositories"`
TotalSkippedNoDatabaseRepositories int TotalSkippedNoDatabaseRepositories int `json:"total_skipped_no_database_repositories"`
TotalSkippedOverLimitRepositories int TotalSkippedOverLimitRepositories int `json:"total_skipped_over_limit_repositories"`
} }
var results Results var results Results
for _, runId := range runIds { for _, run := range runs {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
runDetails, err := getRunDetails(controller, runId) runDetails, err := getRunDetails(controller, run.Id)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -713,8 +716,9 @@ Usage:
failure_reason = "" failure_reason = ""
} }
results.Runs = append(results.Runs, Run{ results.Runs = append(results.Runs, RunStatus{
Id: runId, Id: run.Id,
Query: run.Query,
Status: status, Status: status,
FailureReason: failure_reason, FailureReason: failure_reason,
}) })
@@ -729,6 +733,7 @@ Usage:
results.ResositoriesWithFindings = append(results.ResositoriesWithFindings, RepoWithFindings{ results.ResositoriesWithFindings = append(results.ResositoriesWithFindings, RepoWithFindings{
Nwo: repoInfo["full_name"].(string), Nwo: repoInfo["full_name"].(string),
Count: int(repo.(map[string]interface{})["result_count"].(float64)), Count: int(repo.(map[string]interface{})["result_count"].(float64)),
RunId: run.Id,
}) })
} }
} else if repo.(map[string]interface{})["analysis_status"].(string) == "failed" { } else if repo.(map[string]interface{})["analysis_status"].(string) == "failed" {
@@ -755,9 +760,10 @@ Usage:
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
w := &bytes.Buffer{} fmt.Println(string(data))
jsonpretty.Format(w, bytes.NewReader(data), " ", true) // w := &bytes.Buffer{}
fmt.Println(w.String()) // jsonpretty.Format(w, bytes.NewReader(data), " ", true)
// fmt.Println(w.String())
} else { } else {
// Print results in a nice way // Print results in a nice way
fmt.Println("Run name:", runName) fmt.Println("Run name:", runName)
@@ -791,11 +797,10 @@ func submit(configData Config, args []string) {
nameFlag := flag.String("name", "", "Name of run") nameFlag := flag.String("name", "", "Name of run")
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, ` fmt.Fprintf(os.Stderr, `gh mrva - Run CodeQL queries at scale using Multi-Repository Variant Analysis (MRVA)
gh mrva - submit and download CodeQL queries from MRVA
Usage: Usage:
gh mrva submit [--codeql-dist <path to CodeQL dist>] [--controller <controller>] --lang <language> --name <run name> [--list-file <list file>] --list <list> [--query <query> | --query-suite <query suite>] gh mrva submit [--codeql-path <path to CodeQL>] [--controller <controller>] --lang <language> --name <run name> [--list-file <list file>] --list <list> [--query <query> | --query-suite <query suite>]
`) `)
fmt.Fprintf(os.Stderr, "Flags:\n") fmt.Fprintf(os.Stderr, "Flags:\n")
@@ -865,8 +870,8 @@ Usage:
queries = resolveQueries(codeqlPath, querySuiteFile) queries = resolveQueries(codeqlPath, querySuiteFile)
} }
fmt.Printf("Requesting running %d queries for %d repositories\n", len(queries), len(repositories)) fmt.Printf("Submitting %d queries for %d repositories\n", len(queries), len(repositories))
var runIds []int var runs []Run
for _, query := range queries { for _, query := range queries {
encodedBundle, err := generateQueryPack(codeqlPath, query, language) encodedBundle, err := generateQueryPack(codeqlPath, query, language)
if err != nil { if err != nil {
@@ -887,26 +892,26 @@ Usage:
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
runIds = append(runIds, id) runs = append(runs, Run{Id: id, Query: query})
} }
} }
fmt.Printf("Submitted runs: %v\n", runIds)
if querySuiteFile != "" { if querySuiteFile != "" {
err = saveInHistory(runName, controller, runIds, language, listFile, list, querySuiteFile, len(repositories)) err = saveInHistory(runName, controller, runs, language, listFile, list, querySuiteFile, len(repositories))
} else if queryFile != "" { } else if queryFile != "" {
err = saveInHistory(runName, controller, runIds, language, listFile, list, queryFile, len(repositories)) err = saveInHistory(runName, controller, runs, language, listFile, list, queryFile, len(repositories))
} }
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
fmt.Println("Done!")
} }
func list(args []string) { func list(args []string) {
flag := flag.NewFlagSet("mrva list", flag.ExitOnError) flag := flag.NewFlagSet("mrva list", flag.ExitOnError)
jsonFlag := flag.Bool("json", false, "Output in JSON format (default: false)") jsonFlag := flag.Bool("json", false, "Output in JSON format (default: false)")
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, ` fmt.Fprintf(os.Stderr, `gh mrva - Run CodeQL queries at scale using Multi-Repository Variant Analysis (MRVA)
gh mrva - submit and download CodeQL queries from MRVA
Usage: Usage:
gh mrva list [--json] gh mrva list [--json]
@@ -932,10 +937,10 @@ Usage:
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
fmt.Println(string(data))
w := &bytes.Buffer{} // w := &bytes.Buffer{}
jsonpretty.Format(w, bytes.NewReader(data), " ", true) // jsonpretty.Format(w, bytes.NewReader(data), " ", true)
fmt.Println(w.String()) // fmt.Println(w.String())
} }
} else { } else {
for name, entry := range configData.History { for name, entry := range configData.History {
@@ -945,7 +950,10 @@ Usage:
fmt.Printf(" List file: %s\n", entry.ListFile) fmt.Printf(" List file: %s\n", entry.ListFile)
fmt.Printf(" List: %s\n", entry.List) fmt.Printf(" List: %s\n", entry.List)
fmt.Printf(" Repository count: %d\n", entry.RepositoryCount) fmt.Printf(" Repository count: %d\n", entry.RepositoryCount)
fmt.Printf(" Query(s) : %s\n", entry.Query) for _, run := range entry.Runs {
fmt.Printf(" Run ID: %s\n", run.Id)
fmt.Printf(" Query(s) : %s\n", run.Query)
}
} }
} }
} }
@@ -959,8 +967,7 @@ func download(args []string) {
nwoFlag := flag.String("nwo", "", "Repository to download artifacts for (optional)") nwoFlag := flag.String("nwo", "", "Repository to download artifacts for (optional)")
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, ` fmt.Fprintf(os.Stderr, `gh mrva - Run CodeQL queries at scale using Multi-Repository Variant Analysis (MRVA)
gh mrva - submit and download CodeQL queries from MRVA
Usage: Usage:
gh mrva download --name <run name> --output-dir <output directory> [--download-dbs] [--nwo <owner/repo>] gh mrva download --name <run name> --output-dir <output directory> [--download-dbs] [--nwo <owner/repo>]
@@ -993,22 +1000,22 @@ Usage:
} }
} }
controller, runIds, language, err := loadFromHistory(runName) controller, runs, language, err := loadFromHistory(runName)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} else if len(runIds) == 0 { } else if len(runs) == 0 {
log.Fatal("No runs found for name " + runName) log.Fatal("No runs found for name " + runName)
} }
var downloadTasks []DownloadTask var downloadTasks []DownloadTask
for _, runId := range runIds { for _, run := range runs {
runDetails, err := getRunDetails(controller, runId) runDetails, err := getRunDetails(controller, run.Id)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if runDetails["status"] == "in_progress" { if runDetails["status"] == "in_progress" {
log.Printf("Run %d is not complete yet. Please try again later.", runId) log.Printf("Run %d is not complete yet. Please try again later.", run.Id)
return return
} }
for _, r := range runDetails["scanned_repositories"].([]interface{}) { for _, r := range runDetails["scanned_repositories"].([]interface{}) {
@@ -1030,7 +1037,7 @@ Usage:
_, sarifErr := os.Stat(sarifPath) _, sarifErr := os.Stat(sarifPath)
if errors.Is(bqrsErr, os.ErrNotExist) && errors.Is(sarifErr, os.ErrNotExist) { if errors.Is(bqrsErr, os.ErrNotExist) && errors.Is(sarifErr, os.ErrNotExist) {
downloadTasks = append(downloadTasks, DownloadTask{ downloadTasks = append(downloadTasks, DownloadTask{
runId: runId, runId: run.Id,
nwo: nwo, nwo: nwo,
controller: controller, controller: controller,
artifact: "artifact", artifact: "artifact",
@@ -1042,7 +1049,7 @@ Usage:
// check if the database already exists // check if the database already exists
if _, err := os.Stat(targetPath); errors.Is(err, os.ErrNotExist) { if _, err := os.Stat(targetPath); errors.Is(err, os.ErrNotExist) {
downloadTasks = append(downloadTasks, DownloadTask{ downloadTasks = append(downloadTasks, DownloadTask{
runId: runId, runId: run.Id,
nwo: nwo, nwo: nwo,
controller: controller, controller: controller,
artifact: "database", artifact: "database",
@@ -1092,5 +1099,4 @@ Usage:
// drain the progress channel // drain the progress channel
<-progressDone <-progressDone
} }