// check cannot be called async as we set r.file on the review pointer. This // has to remain until we've finished walking. func (r *review) check(f File) error { log.Printf("checking file %s", f.Filename()) // set current file being reviewed. r.file = f b := r.baseTenet() r.fileDone = func() { r.fileDoneMap[f.Filename()] = true } // TODO(waigani) this should be r.recursiveASTWalk() for _, visitor := range b.astVisitors { r.walkAST(&visitor) } // first walk all ast nodes. // r.recursiveASTWalk(b, f) // TODO(waigani) support recursive line visits. if len(b.lineVisitors) > 0 { // then check all src lines r.visitLines() } return nil }
func (t *tenet) OpenService() (TenetService, error) { ds, err := t.Driver.Service() if err != nil { log.Print(err.Error()) // TODO(waigani) this logs are quick hacks. Work out the error paths and log them all at the root. return nil, errors.Trace(err) } cfg := &api.Config{} for k, v := range t.options { cfg.Options = append(cfg.Options, &api.Option{ Name: k, Value: fmt.Sprintf("%v", v), }) } s := &tenetService{ Service: ds, cfg: cfg, editFilename: t.Driver.EditFilename, editIssue: t.Driver.EditIssue, mutex: &sync.Mutex{}, } if err := s.start(); err != nil { log.Println("got err opening service") // TODO(waigani) add retry logic here. 1. Keep retrying until service // is up. 2. Keep retrying until service is connected. log.Printf("err: %#v", errors.ErrorStack(err)) return nil, errors.Trace(err) } log.Print("opened service, no issue") return s, nil }
func (*Binary) EditFilename(filename string) (editedFilename string) { var absPath string var err error if absPath, err = filepath.Abs(filename); err == nil { return absPath } log.Printf("could not get absolute path for %q:%v", filename, err) return filename }
func docs(c *cli.Context) { fmt.Println("Opening tenet documentation in a new browser window ...") // TODO(waigani) DEMOWARE writeTenetDoc(c, "", "/tmp/tenets.md") err := browser.OpenFile("/tmp/tenets.md") if err != nil { log.Printf("ERROR opening docs in browser: %v", err) } }
func (*Binary) EditIssue(issue *api.Issue) (editedIssue *api.Issue) { start := issue.Position.Start.Filename end := issue.Position.End.Filename pwd, err := os.Getwd() if err != nil { log.Printf("could not get relative path for %q:%v", start, err) } issue.Position.Start.Filename, err = filepath.Rel(pwd, start) if err != nil { log.Printf("could not get relative path for %q:%v", start, err) } issue.Position.End.Filename, err = filepath.Rel(pwd, end) if err != nil { log.Printf("could not get relative path for %q:%v", end, err) } return issue }
func PullImage(client *docker.Client, name string, registry string, tag string) error { opts := docker.PullImageOptions{ Repository: name, Registry: registry, Tag: tag, OutputStream: os.Stdout, } auth, err := apiAuth() if err != nil { // just log err. We should be able to pull without auth. log.Printf("could not get auth config: %s. We'll try pulling image without auth", err.Error()) fmt.Printf("could not get auth config: %s. We'll try pulling image without auth", err.Error()) } return client.PullImage(opts, auth) }
// Serve starts an RPC server hosting the api methods. It will first return // its socket address. func Serve(t tenet.Tenet) { lis, err := listener() if err != nil { log.Fatal("listen error:", err) } defer lis.Close() s := grpc.NewServer() api.RegisterTenetServer(s, newAPI(t)) b := t.(tenet.BaseTenet) b.Init() // log non-fatal errors. TODO(waigani) make non-fatal errors avaliable to // client and do something a litte more interesting with them. go func() { errorsc := b.Errors() for err := range errorsc { log.Printf("%v", err) } }() s.Serve(lis) }
// Review sets up two goroutines. One to send files to the service from filesc, // the other to recieve issues from the service on issuesc. func (t *tenetService) Review(filesc <-chan *api.File, issuesc chan<- *api.Issue, filesTM *tomb.Tomb) error { stream, err := t.client.Review(context.Background()) if err != nil { return err } // first setup our issues chan to read from the service. go func(issuesc chan<- *api.Issue) { for { log.Println("waiting for issues") issue, err := stream.Recv() if err != nil { if err == io.EOF || grpc.ErrorDesc(err) == "transport is closing" || err.Error() == "timed out waiting for issues" { // TODO(waigani) error type log.Println("closing issuesc") // Close our local issues channel. close(issuesc) return } // TODO(waigani) in what error cases should we close issuesc? // Any other err we keep calm and carry on. log.Println("ERROR receiving an issue : %s", err.Error()) continue } issuesc <- t.editIssue(issue) } }(issuesc) // next, setup a goroutine to send our files to the service to review. go func(filesc <-chan *api.File) { for { select { case file, ok := <-filesc: if !ok && file == nil { log.Println("client filesc closed. Closing send stream.") // Close the file send stream. err := stream.CloseSend() if err != nil { log.Println(err.Error()) } return } file.Name = t.editFilename(file.Name) if err := stream.Send(file); err != nil { log.Println("failed to send a file %q: %v", file.Name, err) } log.Printf("sent file %q\n", file.Name) // Each tenet has a 5 second idle time. If we don't find any // files to send it in that time, we close this tenet down. case <-time.After(5 * time.Second): // this will close this instance of the tenet. filesTM.Kill(errors.New("timed out waiting for a filename")) return } } }(filesc) return nil }
cli.StringFlag{ Name: "group", Value: "default", Usage: "group to remove tenet from"}, }, Action: remove, BashComplete: func(c *cli.Context) { // This will complete if no args are passed if len(c.Args()) > 0 { return } // TODO(waigani) read from .lingo not bin tenets tenets, err := util.BinTenets() if err != nil { log.Printf("auto complete error %v", err) return } for _, t := range tenets { fmt.Println(t) } }, } func remove(c *cli.Context) { if l := len(c.Args()); l != 1 { common.OSErrf("error: expected 1 argument, got %d", l) return }
// returns a chan of tenet reviews and a cancel chan that blocks until the user cancels. func reviewQueue(ctx *cli.Context, mappings <-chan cfgMap, changed *map[string][]int, errc chan error) (<-chan *tenetReview, chan struct{}) { reviews := make(map[string]*tenetReview) reviewChannel := make(chan *tenetReview) cleanupWG := &sync.WaitGroup{} // setup a cancel exit path. cancelc := make(chan os.Signal, 1) signal.Notify(cancelc, os.Interrupt) signal.Notify(cancelc, syscall.SIGTERM) cancelledc := make(chan struct{}) cancelled := func() bool { select { case _, ok := <-cancelledc: if ok { close(cancelc) } return true default: return false } } // Kill all open tenets on cancel. go func() { var i int for { <-cancelc if i > 0 { // on the second exit, just do it. fmt.Print("failed.\nSome docker containers may still be running.") os.Exit(1) } i++ go func() { // TODO(waigani) add progress bar here fmt.Print("\ncleaning up tenets ... ") // Anything waiting on the cancelled chan will now fire. close(cancelledc) // Wait for all tenets to be cleaned up. cleanupWG.Wait() // say bye. fmt.Println("done.") os.Exit(1) }() } }() // TODO(waigani) reenable buffering to: // 1. Allow found tenets to keep running. // 2. Stop building new tenets until there is room in the buffer. // TODO(waigani) make cfg vars // buffLimit := 3 // if ctx.Bool("keep-all") { // buffLimit = 100 // } // buff := util.NewBuffer(buffLimit, cancelledc) go func() { for m := range mappings { // Glob all the files in the associated directories for this config, and assign to each tenet by hash for _, tc := range m.cfg.AllTenets() { if cancelled() { // empty files and dirs to stop feeding tenet reviews in progress. m.files = []string{} m.dirs = []string{} return } // Open the tenet service if we haven't seen this config before. configHash := tc.hash() r, found := reviews[configHash] if !found { // Don't build a new tenet until there is room in the buffer. // Found tenets will keep running until they are not fed files for 5 seconds. // WaitRoom will not block if we get a cancel signal. // buff.WaitRoom() tn, err := newTenet(ctx, tc) if err != nil { errc <- err continue } // Note: service should not be called outside this if block. service, err := tn.OpenService() if err != nil { errc <- err continue } info, err := service.Info() if err != nil { errc <- err continue } r = &tenetReview{ configHash: configHash, filesc: make(chan *api.File), issuesc: make(chan *api.Issue), info: info, issuesWG: &sync.WaitGroup{}, filesTM: &tomb.Tomb{}, } reviews[configHash] = r // Setup the takedown of this review. r.issuesWG.Add(1) cleanupWG.Add(1) // buff.Add(1) go func(r *tenetReview) { // The following fires when: select { // 1. all files have been sent or timed out // 2. the tenet buffer is full case <-r.filesTM.Dying(): // 3. lingo has been stopped case <-cancelledc: } // make room for another tenet to start and ensure // that any configHash's matching this one will have // to start a new tenet instance. delete(reviews, configHash) // buff.Add(-1) // signal to the tenet that no more files are coming. close(r.filesc) // wait for the tenet to signal to us that it's finished it's review. r.issuesWG.Wait() // we can now safely close the backing service. if err := service.Close(); err != nil { log.Println("ERROR closing sevice:", err) } log.Println("cleanup done") cleanupWG.Done() }(r) // Make sure we're ready to handle results before we start // the review. reviewChannel <- r // Start this tenet's review. service.Review(r.filesc, r.issuesc, r.filesTM) } regexPattern, globPattern := fileExtFilterForLang(r.info.Language) for _, d := range m.dirs { files, err := filepath.Glob(path.Join(d, globPattern)) if err != nil { // Non-fatal log.Printf("Error reading files in %s: %v\n", d, err) } l: for i, f := range files { select { case <-cancelledc: log.Println("user cancelled, dropping files.") case <-r.filesTM.Dying(): dropped := len(files) - i log.Print("WARNING a tenet review timed out waiting for files to be sent. %d files dropped", dropped) break l case r.filesc <- &api.File{Name: f}: } } } z: for i, f := range m.files { if matches, err := regexp.MatchString(regexPattern, f); !matches { if err != nil { log.Println("error in regex: ", regexPattern) } continue } fileinfo := &api.File{Name: f} if changed != nil { if diffLines, ok := (*changed)[f]; ok { // This can be false if --diff and fileargs are specified for _, l := range diffLines { fileinfo.Lines = append(fileinfo.Lines, int64(l)) } } } // TODO: Refactor so as not to have copy/pasted code with above dir handler select { case <-cancelledc: log.Println("user cancelled, dropping files.") case <-r.filesTM.Dying(): dropped := len(m.files) - i log.Print("WARNING a tenet review timed out waiting for files to be sent. %d files dropped", dropped) break z case r.filesc <- fileinfo: } } } } for _, r := range reviews { // this says all files have been sent. For this review. r.filesTM.Done() } // wait for all tenets to be cleaned up. cleanupWG.Wait() // Closing this chan will start the wind down to end the lingo // process. close(reviewChannel) }() return reviewChannel, cancelledc }