func followAction(a mig.Action, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("followAction() -> %v", e) } }() fmt.Printf("Entering follower mode for action ID %.0f\n", a.ID) sent := 0 dotter := 0 previousctr := 0 status := "" attempts := 0 var completion float64 for { a, _, err = cli.GetAction(a.ID) if err != nil { attempts++ time.Sleep(1 * time.Second) if attempts >= 30 { panic("failed to retrieve action after 30 seconds. launch may have failed") } continue } if status == "" { status = a.Status } if status != a.Status { fmt.Printf("action status is now '%s'\n", a.Status) status = a.Status } // exit follower mode if status isn't one we follow, // or enough commands have returned // or expiration time has passed if (status != "init" && status != "preparing" && status != "inflight") || (a.Counters.Done > 0 && a.Counters.Done >= a.Counters.Sent) || (time.Now().After(a.ExpireAfter)) { goto finish break } // init counters if sent == 0 { if a.Counters.Sent == 0 { time.Sleep(1 * time.Second) continue } else { sent = a.Counters.Sent fmt.Printf("%d commands have been sent\n", sent) } } if a.Counters.Done > 0 && a.Counters.Done > previousctr { completion = (float64(a.Counters.Done) / float64(a.Counters.Sent)) * 100 if completion > 99.9 && a.Counters.Done != a.Counters.Sent { completion = 99.9 } previousctr = a.Counters.Done } time.Sleep(1 * time.Second) dotter++ if dotter >= 5 { fmt.Printf("%.1f%% done - %d/%d - %s\n", completion, a.Counters.Done, a.Counters.Sent, time.Now().Sub(a.StartTime).String()) dotter = 0 } } finish: fmt.Printf("leaving follower mode after %s\n", a.LastUpdateTime.Sub(a.StartTime).String()) fmt.Printf("%d sent, %d done: %d returned, %d cancelled, %d expired, %d failed, %d timed out, %d still in flight\n", a.Counters.Sent, a.Counters.Done, a.Counters.Done, a.Counters.Cancelled, a.Counters.Expired, a.Counters.Failed, a.Counters.TimeOut, a.Counters.InFlight) return }
// actionReader retrieves an action from the API using its numerical ID // and enters prompt mode to analyze it func actionReader(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("actionReader() -> %v", e) } }() inputArr := strings.Split(input, " ") if len(inputArr) < 2 { panic("wrong order format. must be 'action <actionid>'") } aid, err := strconv.ParseFloat(inputArr[1], 64) if err != nil { panic(err) } a, _, err := cli.GetAction(aid) if err != nil { panic(err) } investigators := investigatorsStringFromAction(a.Investigators, 80) fmt.Println("Entering action reader mode. Type \x1b[32;1mexit\x1b[0m or press \x1b[32;1mctrl+d\x1b[0m to leave. \x1b[32;1mhelp\x1b[0m may help.") fmt.Printf("Action: '%s'.\nLaunched by '%s' on '%s'.\nStatus '%s'.\n", a.Name, investigators, a.StartTime, a.Status) a.PrintCounters() prompt := fmt.Sprintf("\x1b[31;1maction %d>\x1b[0m ", uint64(aid)%1000) for { // completion var symbols = []string{"command", "copy", "counters", "details", "exit", "grep", "help", "investigators", "json", "list", "all", "found", "notfound", "pretty", "r", "results", "times"} readline.Completer = func(query, ctx string) []string { var res []string for _, sym := range symbols { if strings.HasPrefix(sym, query) { res = append(res, sym) } } return res } input, err := readline.String(prompt) if err == io.EOF { break } if err != nil { fmt.Println("error: ", err) break } orders := strings.Split(strings.TrimSpace(input), " ") switch orders[0] { case "command": err = commandReader(input, cli) if err != nil { panic(err) } case "copy": err = actionLauncher(a, cli) if err != nil { panic(err) } goto exit case "counters": a, _, err = cli.GetAction(aid) if err != nil { panic(err) } a.PrintCounters() case "details": actionPrintDetails(a) case "exit": fmt.Printf("exit\n") goto exit case "help": fmt.Printf(`The following orders are available: command <id> jump to command reader mode for command <id> copy enter action launcher mode using current action as template counters display the counters of the action details display the details of the action, including status & times exit exit this mode (also works with ctrl+d) help show this help investigators print the list of investigators that signed the action json show the json of the action list <show> returns the list of commands with their status <show>: * set to "all" to get all results (default) * set to "found" to only display positive results * set to "notfound" for negative results list can be followed by a 'filter' pipe: ex: ls | grep server1.(dom1|dom2) | grep -v example.net r refresh the action (get latest version from upstream) results <show> <render> display results of all commands <show>: * set to "all" to get all results (default) * set to "found" to only display positive results * set to "notfound" for negative results <render>: * set to "text" to print results in console (default) * set to "map" to generate an open a google map times show the various timestamps of the action `) case "investigators": for _, i := range a.Investigators { fmt.Println(i.Name, "- Key ID:", i.PGPFingerprint) } case "json": var ajson []byte ajson, err = json.MarshalIndent(a, "", " ") if err != nil { panic(err) } fmt.Printf("%s\n", ajson) case "list": err = actionPrintList(aid, orders, cli) if err != nil { panic(err) } case "r": a, _, err = cli.GetAction(aid) if err != nil { panic(err) } fmt.Println("reloaded") case "results": show := "all" if len(orders) > 1 { switch orders[1] { case "all", "found", "notfound": show = orders[1] default: panic("invalid show '" + orders[2] + "'") } } render := "text" if len(orders) > 2 { switch orders[2] { case "map", "text": render = orders[2] default: panic("invalid rendering '" + orders[2] + "'") } } err = cli.PrintActionResults(a, show, render) if err != nil { panic(err) } case "times": fmt.Printf("Valid from '%s' until '%s'\nStarted on '%s'\n"+ "Last updated '%s'\nFinished on '%s'\n", a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime) case "": break default: fmt.Printf("Unknown order '%s'. You are in action reader mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }