From 289f02924471cc33a0bea04aa57f97895008aedd Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Wed, 6 Nov 2019 09:09:41 +0000
Subject: [PATCH] Load external source specs in 'iprep server'

---
 cmd/iprep/server.go | 67 +++++++++++++++++++++++++++++++++++++++------
 db/db.go            |  4 +++
 2 files changed, 63 insertions(+), 8 deletions(-)

diff --git a/cmd/iprep/server.go b/cmd/iprep/server.go
index 8347b5c..620fc61 100644
--- a/cmd/iprep/server.go
+++ b/cmd/iprep/server.go
@@ -3,12 +3,15 @@ package main
 import (
 	"context"
 	"flag"
+	"fmt"
+	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
 	_ "net/http/pprof"
 	"os"
 	"os/signal"
+	"path/filepath"
 	"syscall"
 	"time"
 
@@ -18,18 +21,21 @@ import (
 	"golang.org/x/sync/errgroup"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
+	"gopkg.in/yaml.v2"
 
+	"git.autistici.org/ai3/tools/iprep/ext"
 	ippb "git.autistici.org/ai3/tools/iprep/proto"
 	"git.autistici.org/ai3/tools/iprep/server"
 )
 
 type serverCommand struct {
-	rpcAddr    string
-	httpAddr   string
-	dbURI      string
-	scriptPath string
-	tlsCert    string
-	tlsKey     string
+	rpcAddr        string
+	httpAddr       string
+	dbURI          string
+	scriptPath     string
+	externalSrcDir string
+	tlsCert        string
+	tlsKey         string
 }
 
 func (c *serverCommand) Name() string     { return "server" }
@@ -45,8 +51,9 @@ func (c *serverCommand) Usage() string {
 func (c *serverCommand) SetFlags(f *flag.FlagSet) {
 	f.StringVar(&c.rpcAddr, "rpc-addr", ":7170", "`address` of GRPC listener")
 	f.StringVar(&c.httpAddr, "http-addr", ":7180", "`address` of HTTP debug listener")
-	f.StringVar(&c.dbURI, "db", "/var/lib/iprep/data", "database `uri` (sqlite:// or leveldb://)")
+	f.StringVar(&c.dbURI, "db", "leveldb:///var/lib/iprep/data", "database `uri` (sqlite:// or leveldb://)")
 	f.StringVar(&c.scriptPath, "scoring-script", "/etc/iprep/score.td", "`path` to a custom scoring script")
+	f.StringVar(&c.externalSrcDir, "ext-sources-dir", "/etc/iprep/external", "`path` to a directory containing external source definitions")
 
 	f.StringVar(&c.tlsCert, "tls-cert", "", "TLS certificate `path` (grpc only)")
 	f.StringVar(&c.tlsKey, "tls-key", "", "TLS private key `path` (grpc only)")
@@ -66,8 +73,52 @@ func (c *serverCommand) Execute(ctx context.Context, f *flag.FlagSet, args ...in
 	return subcommands.ExitSuccess
 }
 
+// Definition of an ExternalSource, represented as YAML.
+type externalSourceSpec struct {
+	Name   string                 `yaml:"name"`
+	Type   string                 `yaml:"type"`
+	Params map[string]interface{} `yaml:"params"`
+}
+
+// Read all files in externalSrcDir and build a map of ExternalSources.
+func (c *serverCommand) loadExternalSources() (map[string]ext.ExternalSource, error) {
+	files, err := filepath.Glob(filepath.Join(c.externalSrcDir, "*.yml"))
+	if err != nil {
+		return nil, err
+	}
+
+	m := make(map[string]ext.ExternalSource)
+	for _, f := range files {
+		data, err := ioutil.ReadFile(f)
+		if err != nil {
+			return nil, err
+		}
+		var spec externalSourceSpec
+		if err := yaml.Unmarshal(data, &spec); err != nil {
+			return nil, err
+		}
+		if spec.Name == "" {
+			return nil, fmt.Errorf("error in external source spec %s: missing name", f)
+		}
+		if _, ok := m[spec.Name]; ok {
+			return nil, fmt.Errorf("duplicate external source '%s'", spec.Name)
+		}
+		src, err := ext.New(spec.Type, spec.Params)
+		if err != nil {
+			return nil, fmt.Errorf("error creating external source '%s': %v", spec.Name, err)
+		}
+		m[spec.Name] = src
+	}
+
+	return m, nil
+}
+
 func (c *serverCommand) run(ctx context.Context) error {
-	srv, err := server.New(c.dbURI, c.scriptPath)
+	srcs, err := c.loadExternalSources()
+	if err != nil {
+		return err
+	}
+	srv, err := server.New(c.dbURI, c.scriptPath, srcs)
 	if err != nil {
 		return err
 	}
diff --git a/db/db.go b/db/db.go
index 9ca9c5d..aaf84f6 100644
--- a/db/db.go
+++ b/db/db.go
@@ -1,6 +1,7 @@
 package db
 
 import (
+	"errors"
 	"fmt"
 	"net/url"
 	"time"
@@ -23,6 +24,9 @@ func Open(path string) (DB, error) {
 	if err != nil {
 		return nil, err
 	}
+	if u.Path == "" {
+		return nil, errors.New("empty path in DB URI")
+	}
 
 	switch u.Scheme {
 	case "", "leveldb":
-- 
GitLab