Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reload config on update #486

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 39 additions & 71 deletions cmds/houndd/main.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"github.com/blang/semver/v4"
"github.com/fsnotify/fsnotify"
"github.com/hound-search/hound/config"
"github.com/hound-search/hound/searcher"
"github.com/hound-search/hound/web"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"

"github.com/blang/semver/v4"
"github.com/hound-search/hound/api"
"github.com/hound-search/hound/config"
"github.com/hound-search/hound/searcher"
"github.com/hound-search/hound/ui"
"github.com/hound-search/hound/web"
)

const gracefulShutdownSignal = syscall.SIGTERM
Expand All @@ -31,30 +27,30 @@ var (
basepath = filepath.Dir(b)
)

func makeSearchers(cfg *config.Config) (map[string]*searcher.Searcher, bool, error) {
func makeSearchers(cfg *config.Config, searchers map[string]*searcher.Searcher) (bool, error) {
// Ensure we have a dbpath
if _, err := os.Stat(cfg.DbPath); err != nil {
if err := os.MkdirAll(cfg.DbPath, os.ModePerm); err != nil {
return nil, false, err
return false, err
}
}

searchers, errs, err := searcher.MakeAll(cfg)
errs, err := searcher.MakeAll(cfg, searchers)
if err != nil {
return nil, false, err
return false, err
}

if len(errs) > 0 {
// NOTE: This mutates the original config so the repos
// are not even seen by other code paths.
for name, _ := range errs { //nolint
for name := range errs { //nolint
delete(cfg.Repos, name)
}

return searchers, false, nil
return false, nil
}

return searchers, true, nil
return true, nil
}

func handleShutdown(shutdownCh <-chan os.Signal, searchers map[string]*searcher.Searcher) {
Expand All @@ -79,42 +75,6 @@ func registerShutdownSignal() <-chan os.Signal {
return shutdownCh
}

func makeTemplateData(cfg *config.Config) (interface{}, error) { //nolint
var data struct {
ReposAsJson string
}

res := map[string]*config.Repo{}
for name, repo := range cfg.Repos {
res[name] = repo
}

b, err := json.Marshal(res)
if err != nil {
return nil, err
}

data.ReposAsJson = string(b)
return &data, nil
}

func runHttp( //nolint
addr string,
dev bool,
cfg *config.Config,
idx map[string]*searcher.Searcher) error {
m := http.DefaultServeMux

h, err := ui.Content(dev, cfg)
if err != nil {
return err
}

m.Handle("/", h)
api.Setup(m, idx, cfg.ResultLimit)
return http.ListenAndServe(addr, m)
}

// TODO: Automatically increment this when building a release
func getVersion() semver.Version {
return semver.Version{
Expand All @@ -141,29 +101,38 @@ func main() {
os.Exit(0)
}

idx := make(map[string]*searcher.Searcher)
var cfg config.Config
if err := cfg.LoadFromFile(*flagConf); err != nil {
panic(err)

loadConfig := func() {
if err := cfg.LoadFromFile(*flagConf); err != nil {
panic(err)
}
// It's not safe to be killed during makeSearchers, so register the
// shutdown signal here and defer processing it until we are ready.
shutdownCh := registerShutdownSignal()
ok, err := makeSearchers(&cfg, idx)
if err != nil {
log.Panic(err)
}
if !ok {
info_log.Println("Some repos failed to index, see output above")
} else {
info_log.Println("All indexes built!")
}
handleShutdown(shutdownCh, idx)
}
loadConfig()

// watch for config file changes
configWatcher := config.NewWatcher(*flagConf)
configWatcher.OnChange(func(fsnotify.Event) {
loadConfig()
})

// Start the web server on a background routine.
ws := web.Start(&cfg, *flagAddr, *flagDev)

// It's not safe to be killed during makeSearchers, so register the
// shutdown signal here and defer processing it until we are ready.
shutdownCh := registerShutdownSignal()
idx, ok, err := makeSearchers(&cfg)
if err != nil {
log.Panic(err)
}
if !ok {
info_log.Println("Some repos failed to index, see output above")
} else {
info_log.Println("All indexes built!")
}

handleShutdown(shutdownCh, idx)

host := *flagAddr
if strings.HasPrefix(host, ":") { //nolint
host = "localhost" + host
Expand All @@ -175,8 +144,7 @@ func main() {
webpack.Dir = basepath + "/../../"
webpack.Stdout = os.Stdout
webpack.Stderr = os.Stderr
err = webpack.Start()
if err != nil {
if err := webpack.Start(); err != nil {
error_log.Println(err)
}
}
Expand Down
79 changes: 79 additions & 0 deletions config/watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package config

import (
"log"
"sync"

"github.com/fsnotify/fsnotify"
)

// WatcherListenerFunc defines the signature for listner functions
type WatcherListenerFunc func(fsnotify.Event)

// Watcher watches for configuration updates and provides hooks for
// triggering post events
type Watcher struct {
listeners []WatcherListenerFunc
}

// NewWatcher returns a new file watcher
func NewWatcher(cfgPath string) *Watcher {
log.Printf("setting up watcher for %s", cfgPath)
w := Watcher{}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Panic(err)
}
defer watcher.Close()
// Event listener setup
eventWG := sync.WaitGroup{}
eventWG.Add(1)
go func() {
defer eventWG.Done()
for {
select {
case event, ok := <-watcher.Events:
if !ok {
// events channel is closed
log.Printf("error: events channel is closed\n")
return
}
// only trigger on creates and writes of the watched config file
if event.Name == cfgPath && event.Op&fsnotify.Write == fsnotify.Write {
log.Printf("change in config file (%s) detected\n", cfgPath)
for _, listener := range w.listeners {
listener(event)
}
}
case err, ok := <-watcher.Errors:
if !ok {
// errors channel is closed
log.Printf("error: errors channel is closed\n")
return
}
log.Println("error:", err)
return
}
}
}()
// add config file
if err := watcher.Add(cfgPath); err != nil {
log.Fatalf("failed to watch %s", cfgPath)
}
// setup is complete
wg.Done()
// wait for the event listener to complete before exiting
eventWG.Wait()
}()
// wait for watcher setup to complete
wg.Wait()
return &w
}

// OnChange registers a listener function to be called if a file changes
func (w *Watcher) OnChange(listener WatcherListenerFunc) {
w.listeners = append(w.listeners, listener)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.16

require (
github.com/blang/semver/v4 v4.0.0
github.com/fsnotify/fsnotify v1.6.0
golang.org/x/mod v0.10.0
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand All @@ -16,6 +18,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
31 changes: 21 additions & 10 deletions index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"compress/gzip"
"encoding/gob"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -80,16 +81,17 @@ type IndexRef struct {
Url string
Rev string
Time time.Time
dir string
IdxDir string
VcsDir string
AutoGeneratedFiles []string
}

func (r *IndexRef) Dir() string {
return r.dir
return r.IdxDir
}

func (r *IndexRef) writeManifest() error {
w, err := os.Create(filepath.Join(r.dir, manifestFilename))
w, err := os.Create(filepath.Join(r.IdxDir, manifestFilename))
if err != nil {
return err
}
Expand All @@ -101,12 +103,16 @@ func (r *IndexRef) writeManifest() error {
func (r *IndexRef) Open() (*Index, error) {
return &Index{
Ref: r,
idx: index.Open(filepath.Join(r.dir, "tri")),
idx: index.Open(filepath.Join(r.IdxDir, "tri")),
}, nil
}

func (r *IndexRef) Remove() error {
return os.RemoveAll(r.dir)
if err := os.RemoveAll(r.IdxDir); err != nil {
return err
}
return os.RemoveAll(r.VcsDir)
return nil
}

func (n *Index) Close() error {
Expand All @@ -125,7 +131,7 @@ func (n *Index) Destroy() error {
}

func (n *Index) GetDir() string {
return n.Ref.dir
return n.Ref.IdxDir
}

func toStrings(lines [][]byte) []string {
Expand Down Expand Up @@ -206,7 +212,7 @@ func (n *Index) Search(pat string, opt *SearchOptions) (*SearchResponse, error)
}

filesOpened++
if err := g.grep2File(filepath.Join(n.Ref.dir, "raw", name), re, int(opt.LinesOfContext),
if err := g.grep2File(filepath.Join(n.Ref.IdxDir, "raw", name), re, int(opt.LinesOfContext),
func(line []byte, lineno int, before [][]byte, after [][]byte) (bool, error) {

hasMatch = true
Expand Down Expand Up @@ -469,7 +475,7 @@ func indexAllFiles(opt *IndexOptions, dst, src string) error {
// include only the path)
func Read(dir string) (*IndexRef, error) {
m := &IndexRef{
dir: dir,
IdxDir: dir,
}

r, err := os.Open(filepath.Join(dir, manifestFilename))
Expand All @@ -482,6 +488,10 @@ func Read(dir string) (*IndexRef, error) {
return m, err
}

if m.VcsDir == "" {
return m, errors.New("Metadata missing!\n Did you just upgrade to thos version? If so, please remove all data")
}

return m, nil
}

Expand All @@ -504,7 +514,8 @@ func Build(opt *IndexOptions, dst, src, url, rev string) (*IndexRef, error) {
Url: url,
Rev: rev,
Time: time.Now(),
dir: dst,
VcsDir: src,
IdxDir: dst,
AutoGeneratedFiles: opt.AutoGeneratedFiles,
}

Expand All @@ -515,7 +526,7 @@ func Build(opt *IndexOptions, dst, src, url, rev string) (*IndexRef, error) {
return r, nil
}

// Open the index in dir for searching.
// Open the index in IdxDir for searching.
func Open(dir string) (*Index, error) {
r, err := Read(dir)
if err != nil {
Expand Down
Loading