Skip to content

Commit

Permalink
Reload config on update
Browse files Browse the repository at this point in the history
Rep off of hound-search#357 plus
removal up of the cvs- directory as well
  • Loading branch information
panine committed Aug 8, 2023
1 parent 4cbc3cf commit ee18e02
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 97 deletions.
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

0 comments on commit ee18e02

Please sign in to comment.