feat(config): support json config

Ayman Bagabas created

Change summary

config/config.go | 68 ++++++++++++++++++++++++++++++++-----------------
go.mod           |  1 
go.sum           |  8 ++--
3 files changed, 49 insertions(+), 28 deletions(-)

Detailed changes

config/config.go 🔗

@@ -2,6 +2,7 @@ package config
 
 import (
 	"bytes"
+	"encoding/json"
 	"errors"
 	"io/fs"
 	"log"
@@ -17,6 +18,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/charmbracelet/soft-serve/git"
 	"github.com/charmbracelet/soft-serve/server/config"
 	"github.com/go-git/go-billy/v5/memfs"
 	ggit "github.com/go-git/go-git/v5"
@@ -25,35 +27,40 @@ import (
 	"github.com/go-git/go-git/v5/storage/memory"
 )
 
+var (
+	// ErrNoConfig is returned when no config file is found.
+	ErrNoConfig = errors.New("no config file found")
+)
+
 // Config is the Soft Serve configuration.
 type Config struct {
-	Name         string         `yaml:"name"`
-	Host         string         `yaml:"host"`
-	Port         int            `yaml:"port"`
-	AnonAccess   string         `yaml:"anon-access"`
-	AllowKeyless bool           `yaml:"allow-keyless"`
-	Users        []User         `yaml:"users"`
-	Repos        []MenuRepo     `yaml:"repos"`
-	Source       *RepoSource    `yaml:"-"`
-	Cfg          *config.Config `yaml:"-"`
+	Name         string         `yaml:"name" json:"name"`
+	Host         string         `yaml:"host" json:"host"`
+	Port         int            `yaml:"port" json:"port"`
+	AnonAccess   string         `yaml:"anon-access" json:"anon-access"`
+	AllowKeyless bool           `yaml:"allow-keyless" json:"allow-keyless"`
+	Users        []User         `yaml:"users" json:"users"`
+	Repos        []MenuRepo     `yaml:"repos" json:"repos"`
+	Source       *RepoSource    `yaml:"-" json:"-"`
+	Cfg          *config.Config `yaml:"-" json:"-"`
 	mtx          sync.Mutex
 }
 
 // User contains user-level configuration for a repository.
 type User struct {
-	Name        string   `yaml:"name"`
-	Admin       bool     `yaml:"admin"`
-	PublicKeys  []string `yaml:"public-keys"`
-	CollabRepos []string `yaml:"collab-repos"`
+	Name        string   `yaml:"name" json:"name"`
+	Admin       bool     `yaml:"admin" json:"admin"`
+	PublicKeys  []string `yaml:"public-keys" json:"public-keys"`
+	CollabRepos []string `yaml:"collab-repos" json:"collab-repos"`
 }
 
 // Repo contains repository configuration information.
 type MenuRepo struct {
-	Name    string `yaml:"name"`
-	Repo    string `yaml:"repo"`
-	Note    string `yaml:"note"`
-	Private bool   `yaml:"private"`
-	Readme  string `yaml:"readme"`
+	Name    string `yaml:"name" json:"name"`
+	Repo    string `yaml:"repo" json:"repo"`
+	Note    string `yaml:"note" json:"note"`
+	Private bool   `yaml:"private" json:"private"`
+	Readme  string `yaml:"readme" json:"readme"`
 }
 
 // NewConfig creates a new internal Config struct.
@@ -133,13 +140,26 @@ func (cfg *Config) Reload() error {
 	if err != nil {
 		return err
 	}
-	cs, _, err := cr.LatestFile("config.yaml")
-	if err != nil {
-		return err
+	cy, _, err := cr.LatestFile("config.yaml")
+	if err != nil && !errors.Is(err, git.ErrFileNotFound) {
+		return fmt.Errorf("error reading config.yaml: %w", err)
 	}
-	err = yaml.Unmarshal([]byte(cs), cfg)
-	if err != nil {
-		return fmt.Errorf("bad yaml in config.yaml: %s", err)
+	cj, _, err := cr.LatestFile("config.json")
+	if err != nil && !errors.Is(err, git.ErrFileNotFound) {
+		return fmt.Errorf("error reading config.json: %w", err)
+	}
+	if cy != "" {
+		err = yaml.Unmarshal([]byte(cy), cfg)
+		if err != nil {
+			return fmt.Errorf("bad yaml in config.yaml: %s", err)
+		}
+	} else if cj != "" {
+		err = json.Unmarshal([]byte(cj), cfg)
+		if err != nil {
+			return fmt.Errorf("bad json in config.json: %s", err)
+		}
+	} else {
+		return ErrNoConfig
 	}
 	for _, r := range cfg.Source.AllRepos() {
 		name := r.Name()

go.mod 🔗

@@ -66,6 +66,7 @@ require (
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/sahilm/fuzzy v0.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/stretchr/testify v1.8.0 // indirect
 	github.com/xanzy/ssh-agent v0.3.1 // indirect
 	github.com/yuin/goldmark v1.5.2 // indirect
 	github.com/yuin/goldmark-emoji v1.0.1 // indirect

go.sum 🔗

@@ -128,7 +128,6 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV
 github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
-github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
 github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
 github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
 github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
@@ -183,16 +182,18 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
 github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
 github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
 github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
 github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -210,7 +211,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
 golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU=
 golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=