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