git » gofer » commit 60c4f65

config: Implement config checking

author Alberto Bertogli
2020-06-13 01:28:25 UTC
committer Alberto Bertogli
2020-06-13 12:14:28 UTC
parent 5195e1f005e1512a8a4aaa84095575e461ed6acf

config: Implement config checking

Some restrictions in the configuration can't be expressed in yaml.
For example, for each route, only one action must be set.

This patch implements configuration checking, which can be used to
validate the configuration before starting the daemon, and provides
better error messages when there are issues.

config/config.go +76 -0
gofer.go +15 -2

diff --git a/config/config.go b/config/config.go
index 9e6f236..6fe1ece 100644
--- a/config/config.go
+++ b/config/config.go
@@ -78,6 +78,72 @@ func (c Config) ToString() (string, error) {
 	return string(d), err
 }
 
+func (c Config) Check() []error {
+	errs := []error{}
+	for addr, h := range c.HTTP {
+		errs = append(errs, h.Check(c, addr)...)
+
+	}
+
+	for addr, h := range c.HTTPS {
+		errs = append(errs, h.Check(c, addr)...)
+
+		// Certs must be set for HTTPS.
+		if h.Certs == "" {
+			errs = append(errs,
+				fmt.Errorf("%q: certs must be set", addr))
+		}
+	}
+
+	for addr, r := range c.Raw {
+		if _, ok := c.ReqLog[r.ReqLog]; r.ReqLog != "" && !ok {
+			errs = append(errs,
+				fmt.Errorf("%q: unknown reqlog %q", addr, r.ReqLog))
+		}
+	}
+	return errs
+}
+
+func (h HTTP) Check(c Config, addr string) []error {
+	errs := []error{}
+
+	if len(h.Routes) == 0 {
+		errs = append(errs, fmt.Errorf("%q: missing routes", addr))
+	}
+
+	for path, r := range h.Routes {
+		if len(r.DirOpts.Listing)+len(r.DirOpts.Exclude) > 0 && r.Dir == "" {
+			errs = append(errs,
+				fmt.Errorf("%q: %q: diropts is set on non-dir route",
+					addr, path))
+		}
+
+		nSet := nTrue(
+			r.Dir != "",
+			r.File != "",
+			r.Proxy != nil,
+			r.Redirect != nil,
+			len(r.CGI) > 0,
+			r.Status > 0)
+		if nSet > 1 {
+			errs = append(errs,
+				fmt.Errorf("%q: %q: too many actions set", addr, path))
+		} else if nSet == 0 {
+			errs = append(errs,
+				fmt.Errorf("%q: %q: action missing", addr, path))
+		}
+	}
+
+	for path, name := range h.ReqLog {
+		if _, ok := c.ReqLog[name]; !ok {
+			errs = append(errs,
+				fmt.Errorf("%q: %q: unknown reqlog %q", addr, path, name))
+		}
+	}
+
+	return errs
+}
+
 func Load(filename string) (*Config, error) {
 	contents, err := ioutil.ReadFile(filename)
 	if err != nil {
@@ -138,3 +204,13 @@ func (u URL) String() string {
 	p := u.URL()
 	return p.String()
 }
+
+func nTrue(bs ...bool) int {
+	n := 0
+	for _, b := range bs {
+		if b {
+			n++
+		}
+	}
+	return n
+}
diff --git a/gofer.go b/gofer.go
index 573cb0a..6f003b1 100644
--- a/gofer.go
+++ b/gofer.go
@@ -16,7 +16,9 @@ import (
 
 // Flags.
 var (
-	configfile = flag.String("configfile", "gofer.yaml", "Configuration file")
+	configFile  = flag.String("configfile", "gofer.yaml", "Configuration file")
+	configCheck = flag.Bool("configcheck", false,
+		"Check the configuration and exit afterwards")
 )
 
 func main() {
@@ -24,11 +26,22 @@ func main() {
 	log.Init()
 	log.Infof("gofer starting (%s, %s)", debug.Version, debug.SourceDateStr)
 
-	conf, err := config.Load(*configfile)
+	conf, err := config.Load(*configFile)
 	if err != nil {
 		log.Fatalf("error reading config file: %v", err)
 	}
 
+	if errs := conf.Check(); len(errs) > 0 {
+		for _, err := range errs {
+			log.Errorf("%v", err)
+		}
+		log.Fatalf("invalid configuration")
+	}
+	if *configCheck {
+		log.Infof("config ok")
+		return
+	}
+
 	go signalHandler()
 
 	for name, rlog := range conf.ReqLog {