feat(config): per repo config

Ayman Bagabas created

Change summary

config/config.go | 63 ++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 51 insertions(+), 12 deletions(-)

Detailed changes

config/config.go 🔗

@@ -27,6 +27,11 @@ import (
 	"github.com/go-git/go-git/v5/storage/memory"
 )
 
+var (
+	// ErrNoConfig is returned when a repo has no config file.
+	ErrNoConfig = errors.New("no config file found")
+)
+
 // Config is the Soft Serve configuration.
 type Config struct {
 	Name         string         `yaml:"name" json:"name"`
@@ -124,31 +129,40 @@ func NewConfig(cfg *config.Config) (*Config, error) {
 	return c, nil
 }
 
+// readConfig reads the config file for the repo. All config files are stored in
+// the config repo.
 func (cfg *Config) readConfig(repo string, v interface{}) error {
-	cr, err := cfg.Source.GetRepo(repo)
+	cr, err := cfg.Source.GetRepo("config")
 	if err != nil {
 		return err
 	}
-	cy, _, err := cr.LatestFile(repo + ".yaml")
-	if err != nil && !errors.Is(err, git.ErrFileNotFound) {
-		return fmt.Errorf("error reading %s.yaml: %w", repo, err)
+	// Parse YAML files
+	var cy string
+	for _, ext := range []string{".yaml", ".yml"} {
+		cy, _, err = cr.LatestFile(repo + ext)
+		if err != nil && !errors.Is(err, git.ErrFileNotFound) {
+			return err
+		} else if err == nil {
+			break
+		}
 	}
+	// Parse JSON files
 	cj, _, err := cr.LatestFile(repo + ".json")
 	if err != nil && !errors.Is(err, git.ErrFileNotFound) {
-		return fmt.Errorf("error reading %s.json: %w", repo, err)
+		return err
 	}
 	if cy != "" {
 		err = yaml.Unmarshal([]byte(cy), v)
 		if err != nil {
-			return fmt.Errorf("bad yaml in %s.yaml: %s", repo, err)
+			return err
 		}
 	} else if cj != "" {
 		err = json.Unmarshal([]byte(cj), v)
 		if err != nil {
-			return fmt.Errorf("bad json in %s.json: %s", repo, err)
+			return err
 		}
 	} else {
-		return fmt.Errorf("no config file found for %q", repo)
+		return ErrNoConfig
 	}
 	return nil
 }
@@ -164,16 +178,41 @@ func (cfg *Config) Reload() error {
 	if err := cfg.readConfig("config", cfg); err != nil {
 		return fmt.Errorf("error reading config: %w", err)
 	}
+	// sanitize repo configs
+	repos := make(map[string]RepoConfig, 0)
+	for _, r := range cfg.Repos {
+		repos[r.Repo] = r
+	}
+	for _, r := range cfg.Source.AllRepos() {
+		var rc RepoConfig
+		repo := r.Repo()
+		if repo == "config" {
+			continue
+		}
+		if err := cfg.readConfig(repo, &rc); err != nil {
+			if !errors.Is(err, ErrNoConfig) {
+				log.Printf("error reading config: %v", err)
+			}
+			continue
+		}
+		repos[r.Repo()] = rc
+	}
+	cfg.Repos = make([]RepoConfig, 0, len(repos))
+	for n, r := range repos {
+		r.Repo = n
+		cfg.Repos = append(cfg.Repos, r)
+	}
+	// Populate readmes and descriptions
 	for _, r := range cfg.Source.AllRepos() {
-		name := r.Name()
+		repo := r.Repo()
 		err = r.UpdateServerInfo()
 		if err != nil {
-			log.Printf("error updating server info for %s: %s", name, err)
+			log.Printf("error updating server info for %s: %s", repo, err)
 		}
 		pat := "README*"
 		rp := ""
 		for _, rr := range cfg.Repos {
-			if name == rr.Repo {
+			if repo == rr.Repo {
 				rp = rr.Readme
 				r.name = rr.Name
 				r.description = rr.Note
@@ -187,7 +226,7 @@ func (cfg *Config) Reload() error {
 		rm := ""
 		fc, fp, _ := r.LatestFile(pat)
 		rm = fc
-		if name == "config" {
+		if repo == "config" {
 			md, err := templatize(rm, cfg)
 			if err != nil {
 				return err