Beispiel #1
0
// 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
}
Beispiel #2
0
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
}
Beispiel #3
0
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
}
Beispiel #4
0
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)
	}
}
Beispiel #5
0
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
}
Beispiel #6
0
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)
}
Beispiel #7
0
// 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)
}
Beispiel #8
0
// 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
}
Beispiel #9
0
		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
	}
Beispiel #10
0
// 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
}