@@ -9,10 +9,12 @@ import (
99 "io/ioutil"
1010 "net/http"
1111 "os"
12+ "os/signal"
1213 "path"
1314 "regexp"
1415 "strings"
1516 "sync"
17+ "syscall"
1618 "time"
1719
1820 // Sorry the naming here got kinda bad of note gconfig is for configuration
5254 SshProtocolRegex , _ = regexp .Compile ("^ssh://|.*:.*" )
5355)
5456
57+ var (
58+ globalConf = Config {}
59+ configLock = sync.RWMutex {}
60+ )
61+
5562// Config is the parent level of our YAML data that
5663// all other config should tie back into.
5764type Config struct {
@@ -187,7 +194,6 @@ func (r GithubConfig) toRepos(c Config) []repo {
187194 Username : r .Username ,
188195 CgitSection : r .Username ,
189196 CgitOwner : r .Username ,
190- Description : * v .Description ,
191197 }
192198
193199 // Need to add support here for using different kinds of urls but currently
@@ -503,8 +509,13 @@ func repoWorker(wg *sync.WaitGroup, rc <-chan repo) {
503509}
504510
505511// Feed the channel all the information that it needs
506- func feedChannel (jobs chan repo , c Config , oneshot bool ) {
512+ func feedChannel (jobs chan repo , oneshot bool ) {
507513 for {
514+ // Get the latest config that might have been updated
515+ configLock .RLock ()
516+ c := globalConf
517+ configLock .RUnlock ()
518+
508519 for _ , v := range c .Repos {
509520 jobs <- v .toRepo (c )
510521 }
@@ -532,6 +543,66 @@ func launchWorkers(workers int, wg *sync.WaitGroup, rc <-chan repo) {
532543 }
533544}
534545
546+ // I'm not in love with what is going on here but in practice it should work just fine.
547+ // I think the one edge case is that this will make it so we can't validate a required
548+ // field that could be empty. I can't think of any reason I would need that though. So
549+ // what we are doing, is taking YAML, something that is half sane for human config and
550+ // populating the Config struct. We then turn it into JSON so we can pass it to the JSON
551+ // schema validator. Modern problems require modern solutions.
552+ func loadConfig (config string ) (Config , error ) {
553+ c := Config {}
554+
555+ // Load our config into memory and do some error checking
556+ dat , err := ioutil .ReadFile (config )
557+ if err != nil {
558+ return c , err
559+ }
560+
561+ err = yaml .Unmarshal (dat , & c )
562+ if err != nil {
563+ return c , err
564+ }
565+
566+ b , err := json .Marshal (c )
567+ if err != nil {
568+ return c , err
569+ }
570+
571+ compiler := jsonschema .NewCompiler ()
572+ if err := compiler .AddResource ("schema.json" , strings .NewReader (schema )); err != nil {
573+ return c , err
574+ }
575+
576+ schema , err := compiler .Compile ("schema.json" )
577+ if err != nil {
578+ return c , err
579+ }
580+
581+ if err := schema .Validate (strings .NewReader (string (b ))); err != nil {
582+ return c , err
583+ }
584+
585+ return c , nil
586+ }
587+
588+ func handleSignals (signals <- chan os.Signal , config string ) {
589+ for signal := range signals {
590+ switch signal {
591+ case syscall .SIGHUP :
592+ log .Warn ("Config reload requested" )
593+ newConfig , err := loadConfig (config )
594+ if err != nil {
595+ log .Error ("Config not reloaded :" , err .Error ())
596+ } else {
597+ configLock .RLock ()
598+ globalConf = newConfig
599+ configLock .RUnlock ()
600+ log .Warn ("Config reload finished" )
601+ }
602+ }
603+ }
604+ }
605+
535606func main () {
536607 conf := flag .String ("config" , "config.yaml" , "Config file to start the application with" )
537608 workers := flag .Int ("workers" , 1 , "The number of workers trying to update repos" )
@@ -568,43 +639,13 @@ func main() {
568639 log .SetLevel (log .WarnLevel )
569640 }
570641
571- // Load our config into memory and do some error checking
572- dat , err := ioutil .ReadFile (* conf )
573- if err != nil {
574- log .Fatal (err )
575- }
576-
577- // I'm not in love with what is going on here but in practice it should work just fine.
578- // I think the one edge case is that this will make it so we can't validate a required
579- // field that could be empty. I can't think of any reason I would need that though. So
580- // what we are doing, is taking YAML, something that is half sane for human config and
581- // populating the Config struct. We then turn it into JSON so we can pass it to the JSON
582- // schema validator. Modern problems require modern solutions.
583- config := Config {}
584- err = yaml .Unmarshal (dat , & config )
585- if err != nil {
586- log .Fatal (err )
587- }
588-
589- b , err := json .Marshal (config )
642+ config , err := loadConfig (* conf )
590643 if err != nil {
591- fmt .Println (err )
592- return
644+ log .Fatal (err .Error ())
593645 }
594646
595- compiler := jsonschema .NewCompiler ()
596- if err := compiler .AddResource ("schema.json" , strings .NewReader (schema )); err != nil {
597- log .Fatal (err )
598- }
599-
600- schema , err := compiler .Compile ("schema.json" )
601- if err != nil {
602- log .Fatal (err )
603- }
604-
605- if err := schema .Validate (strings .NewReader (string (b ))); err != nil {
606- log .Fatal (err )
607- }
647+ // Set the global conf no mutex is needed at this time
648+ globalConf = config
608649
609650 // If we have gotten this far and not failed we think it's all good
610651 log .Info ("Configuration has been validated" )
@@ -621,9 +662,14 @@ func main() {
621662 // as a one time service so you can use it in a cron if desired
622663 var wg sync.WaitGroup
623664 queue := make (chan repo , * workers * 2 )
624- go feedChannel (queue , config , * oneshot )
665+ go feedChannel (queue , * oneshot )
625666 launchWorkers (* workers , & wg , queue )
626667
668+ // Handle hot config reloads on SIGHUP
669+ signals := make (chan os.Signal , 1 )
670+ signal .Notify (signals , syscall .SIGHUP )
671+ go handleSignals (signals , * conf )
672+
627673 // Hang for now but later this should do some checking for
628674 // signals that may be sent to the processes as well as managing
629675 // when the queue should have jobs pushed onto it again
0 commit comments