diff --git a/README.md b/README.md index e29ea98b38..b21b74026f 100644 --- a/README.md +++ b/README.md @@ -625,6 +625,59 @@ For more details, see [Architecture section](https://github.com/future-architect containers = ["container_name_a", "4aa37a8b63b9"] ``` +# Usage: TUI + +## Display the latest scan results + +``` +$ vuls tui -h +tui: + tui [-dbpath=/path/to/vuls.sqlite3] + + -dbpath string + /path/to/sqlite3 (default "$PWD/vuls.sqlite3") + -debug-sql + debug SQL + +``` + +Key binding is bellow. + +| key | | +|:-----------------|:-------|:------| +| TAB | move cursor among the panes | +| Arrow up/down | move cursor to up/down | +| Ctrl+j, Ctrl+k | move cursor to up/donw | +| Ctrl+u, Ctrl+d | page up/donw | + +For details, see https://github.com/future-architect/vuls/blob/master/report/tui.go + +## Display the previous scan results + +- Display the list of scan results. +``` +$ ./vuls history +2 2016-05-24 19:49 scanned 1 servers: amazon2 +1 2016-05-24 19:48 scanned 2 servers: amazon1, romantic_goldberg +``` + +- Display the result of scanID 1 +``` +$ ./vuls tui 1 +``` + +- Display the result of scanID 2 +``` +$ ./vuls tui 2 +``` + +# Display the previous scan results using peco + +``` +$ ./vuls history | peco | vuls tui +``` + +[![asciicast](https://asciinema.org/a/emi7y7docxr60bq080z10t7v8.png)](https://asciinema.org/a/emi7y7docxr60bq080z10t7v8) # Usage: Update NVD Data. diff --git a/commands/history.go b/commands/history.go new file mode 100644 index 0000000000..6e34dc6d79 --- /dev/null +++ b/commands/history.go @@ -0,0 +1,108 @@ +/* Vuls - Vulnerability Scanner +Copyright (C) 2016 Future Architect, Inc. Japan. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package commands + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/net/context" + + "github.com/Sirupsen/logrus" + c "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/db" + "github.com/future-architect/vuls/models" + "github.com/google/subcommands" +) + +// HistoryCmd is Subcommand of list scanned results +type HistoryCmd struct { + debug bool + debugSQL bool + + dbpath string +} + +// Name return subcommand name +func (*HistoryCmd) Name() string { return "history" } + +// Synopsis return synopsis +func (*HistoryCmd) Synopsis() string { + return `List history of scanning.` +} + +// Usage return usage +func (*HistoryCmd) Usage() string { + return `history: + history + [-dbpath=/path/to/vuls.sqlite3] + ` +} + +// SetFlags set flag +func (p *HistoryCmd) SetFlags(f *flag.FlagSet) { + f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode") + + wd, _ := os.Getwd() + defaultDBPath := filepath.Join(wd, "vuls.sqlite3") + f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3") +} + +// Execute execute +func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + + c.Conf.DebugSQL = p.debugSQL + c.Conf.DBPath = p.dbpath + + // _, err := scanHistories() + histories, err := scanHistories() + if err != nil { + logrus.Error("Failed to select scan histories: ", err) + return subcommands.ExitFailure + } + const timeLayout = "2006-01-02 15:04" + for _, history := range histories { + names := []string{} + for _, result := range history.ScanResults { + if 0 < len(result.Container.ContainerID) { + names = append(names, result.Container.Name) + } else { + names = append(names, result.ServerName) + } + } + fmt.Printf("%-3d %s scanned %d servers: %s\n", + history.ID, + history.ScannedAt.Format(timeLayout), + len(history.ScanResults), + strings.Join(names, ", "), + ) + } + return subcommands.ExitSuccess +} + +func scanHistories() (histories []models.ScanHistory, err error) { + if err := db.OpenDB(); err != nil { + return histories, fmt.Errorf( + "Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err) + } + histories, err = db.SelectScanHistories() + return +} diff --git a/commands/tui.go b/commands/tui.go index 610e57fd4a..e8b9a1126e 100644 --- a/commands/tui.go +++ b/commands/tui.go @@ -20,12 +20,16 @@ package commands import ( "flag" "fmt" + "io/ioutil" "os" "path/filepath" + "strconv" + "strings" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/report" "github.com/google/subcommands" + "github.com/labstack/gommon/log" "golang.org/x/net/context" ) @@ -67,5 +71,24 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s c.Conf.Lang = "en" c.Conf.DebugSQL = p.debugSQL c.Conf.DBPath = p.dbpath - return report.RunTui() + + historyID := "" + if 0 < len(f.Args()) { + if _, err := strconv.Atoi(f.Args()[0]); err != nil { + log.Errorf("First Argument have to be scan_histores record ID: %s", err) + return subcommands.ExitFailure + } + historyID = f.Args()[0] + } else { + bytes, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Errorf("Failed to read stdin: %s", err) + return subcommands.ExitFailure + } + fields := strings.Fields(string(bytes)) + if 0 < len(fields) { + historyID = fields[0] + } + } + return report.RunTui(historyID) } diff --git a/db/db.go b/db/db.go index 516fba7c73..27ca983f10 100644 --- a/db/db.go +++ b/db/db.go @@ -20,6 +20,7 @@ package db import ( "fmt" "sort" + "strconv" "time" "github.com/future-architect/vuls/config" @@ -229,10 +230,22 @@ func resetGormIDs(infos []m.CveInfo) []m.CveInfo { return infos } -// SelectLatestScanHistory select latest scan history from DB -func SelectLatestScanHistory() (m.ScanHistory, error) { +// SelectScanHistory select scan history from DB +func SelectScanHistory(historyID string) (m.ScanHistory, error) { + var err error + scanHistory := m.ScanHistory{} - db.Order("scanned_at desc").First(&scanHistory) + if historyID == "" { + // select latest + db.Order("scanned_at desc").First(&scanHistory) + } else { + var id int + if id, err = strconv.Atoi(historyID); err != nil { + return m.ScanHistory{}, + fmt.Errorf("historyID have to be numeric number: %s", err) + } + db.First(&scanHistory, id) + } if scanHistory.ID == 0 { return m.ScanHistory{}, fmt.Errorf("No scanHistory records") @@ -286,3 +299,26 @@ func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo { } return cveInfos } + +// SelectScanHistories select latest scan history from DB +func SelectScanHistories() ([]m.ScanHistory, error) { + scanHistories := []m.ScanHistory{} + db.Order("scanned_at desc").Find(&scanHistories) + + if len(scanHistories) == 0 { + return []m.ScanHistory{}, fmt.Errorf("No scanHistory records") + } + + for i, history := range scanHistories { + results := m.ScanResults{} + db.Model(&history).Related(&results, "ScanResults") + scanHistories[i].ScanResults = results + + for j, r := range results { + di := m.Container{} + db.Model(&r).Related(&di, "Container") + scanHistories[i].ScanResults[j].Container = di + } + } + return scanHistories, nil +} diff --git a/main.go b/main.go index cd3625a527..6e96e5ad18 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func main() { subcommands.Register(&commands.TuiCmd{}, "tui") subcommands.Register(&commands.ScanCmd{}, "scan") subcommands.Register(&commands.PrepareCmd{}, "prepare") + subcommands.Register(&commands.HistoryCmd{}, "history") var version = flag.Bool("v", false, "Show version") diff --git a/report/tui.go b/report/tui.go index 9f38d7f2dd..abf2a62363 100644 --- a/report/tui.go +++ b/report/tui.go @@ -40,9 +40,9 @@ var currentCveInfo int var currentDetailLimitY int // RunTui execute main logic -func RunTui() subcommands.ExitStatus { +func RunTui(historyID string) subcommands.ExitStatus { var err error - scanHistory, err = latestScanHistory() + scanHistory, err = selectScanHistory(historyID) if err != nil { log.Fatal(err) return subcommands.ExitFailure @@ -70,12 +70,12 @@ func RunTui() subcommands.ExitStatus { return subcommands.ExitSuccess } -func latestScanHistory() (latest models.ScanHistory, err error) { +func selectScanHistory(historyID string) (latest models.ScanHistory, err error) { if err := db.OpenDB(); err != nil { return latest, fmt.Errorf( "Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err) } - latest, err = db.SelectLatestScanHistory() + latest, err = db.SelectScanHistory(historyID) return }