mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
config/sowerd.toml and ExampleSowerdConfigTOML are the same artifact kept in two places: the constant is what `sowerd -i` writes to /etc/sower/sowerd.toml (internal/install/service.go), the file is what `-c config/sowerd.toml` loads during development. They had drifted in both directions -- the constant carried the [site_routes.routes] path-prefix example and the older cert.domains wording, while the file carried the newer wording and left `domains` uncommented -- so a deployment got guidance contradicting the shipped binary depending on which copy it read. Take the constant as the source of truth, comment out the placeholder cert.domains entry (a placeholder domain is not one the operator owns) and pin the two copies byte for byte with a test that prints the differing lines.
189 lines
4.7 KiB
Go
189 lines
4.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cristalhq/aconfig"
|
|
"github.com/cristalhq/aconfig/aconfigtoml"
|
|
)
|
|
|
|
func TestLoadExampleSowerConfigFiles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
file string
|
|
}{
|
|
{name: "toml", file: "sower.toml"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cfg SowerConfig
|
|
if err := loadConfigFileForTest(tt.file, &cfg); err != nil {
|
|
t.Fatalf("load config file: %v", err)
|
|
}
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Fatalf("validate config: %v", err)
|
|
}
|
|
if cfg.Remote.TLS.ServerName != "" {
|
|
t.Fatalf("unexpected remote tls server name: %q", cfg.Remote.TLS.ServerName)
|
|
}
|
|
if cfg.DNS.Serve != "127.0.0.1" {
|
|
t.Fatalf("unexpected dns serve: %q", cfg.DNS.Serve)
|
|
}
|
|
if cfg.DNS.ServeIface != "" {
|
|
t.Fatalf("unexpected dns serve iface: %q", cfg.DNS.ServeIface)
|
|
}
|
|
if cfg.Socks5.Addr != "127.0.0.1:1080" {
|
|
t.Fatalf("unexpected socks5 addr: %q", cfg.Socks5.Addr)
|
|
}
|
|
if cfg.Router.Block.FilePrefix != "**." {
|
|
t.Fatalf("unexpected block file prefix: %q", cfg.Router.Block.FilePrefix)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadExampleSowerdConfigFiles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
content string
|
|
ext string
|
|
wantRoutes int
|
|
}{
|
|
{
|
|
name: "embedded toml",
|
|
content: strings.ReplaceAll(
|
|
ExampleSowerdConfigTOML,
|
|
`fake_site = "/var/www"`,
|
|
`fake_site = "127.0.0.1:80"`,
|
|
),
|
|
ext: ".toml",
|
|
},
|
|
{
|
|
name: "file toml",
|
|
content: strings.ReplaceAll(
|
|
mustReadFileForTest(t, "sowerd.toml"),
|
|
`fake_site = "/var/www"`,
|
|
`fake_site = "127.0.0.1:80"`,
|
|
),
|
|
ext: ".toml",
|
|
},
|
|
{
|
|
name: "active site routes toml",
|
|
content: `log_level = "info"
|
|
serve_ip = "0.0.0.0"
|
|
password = "change_me"
|
|
fake_site = "127.0.0.1:80"
|
|
|
|
[[site_routes]]
|
|
domains = ["a.example.com", "b.example.com"]
|
|
upstream = "http://127.0.0.1:8080"
|
|
|
|
[[site_routes]]
|
|
domains = ["c.example.com"]
|
|
upstream = "https://backend.example.com"
|
|
`,
|
|
ext: ".toml",
|
|
wantRoutes: 2,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
path := filepath.Join(t.TempDir(), "sowerd"+tt.ext)
|
|
if err := os.WriteFile(path, []byte(tt.content), 0o644); err != nil {
|
|
t.Fatalf("write temp config: %v", err)
|
|
}
|
|
|
|
var cfg SowerdConfig
|
|
if err := loadConfigFileForTest(path, &cfg); err != nil {
|
|
t.Fatalf("load config file: %v", err)
|
|
}
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Fatalf("validate config: %v", err)
|
|
}
|
|
if cfg.ServeIP != "0.0.0.0" {
|
|
t.Fatalf("unexpected serve ip: %q", cfg.ServeIP)
|
|
}
|
|
if cfg.FakeSite != "127.0.0.1:80" {
|
|
t.Fatalf("unexpected fake site: %q", cfg.FakeSite)
|
|
}
|
|
if len(cfg.SiteRoutes) != tt.wantRoutes {
|
|
t.Fatalf("unexpected site route count: %d", len(cfg.SiteRoutes))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestExampleSowerdConfigTOMLMatchesFile pins the embedded example to the
|
|
// checked-in file byte for byte. They are the same artifact in two places:
|
|
// the constant is what the installer writes to /etc/sower/sowerd.toml, and
|
|
// the file is what `-c config/sowerd.toml` loads during development. Let
|
|
// them drift and operators get guidance that contradicts shipped behavior.
|
|
func TestExampleSowerdConfigTOMLMatchesFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
file := mustReadFileForTest(t, "sowerd.toml")
|
|
if ExampleSowerdConfigTOML != file {
|
|
t.Fatalf("ExampleSowerdConfigTOML and config/sowerd.toml have drifted:\n%s",
|
|
exampleConfigDiff(ExampleSowerdConfigTOML, file))
|
|
}
|
|
}
|
|
|
|
func exampleConfigDiff(embedded, file string) string {
|
|
embeddedLines := strings.Split(strings.TrimRight(embedded, "\n"), "\n")
|
|
fileLines := strings.Split(strings.TrimRight(file, "\n"), "\n")
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, " const has %d lines, file has %d lines\n", len(embeddedLines), len(fileLines))
|
|
for i := 0; i < len(embeddedLines) || i < len(fileLines); i++ {
|
|
var e, f string
|
|
if i < len(embeddedLines) {
|
|
e = embeddedLines[i]
|
|
}
|
|
if i < len(fileLines) {
|
|
f = fileLines[i]
|
|
}
|
|
if e != f {
|
|
fmt.Fprintf(&b, " line %d\n const: %q\n file: %q\n", i+1, e, f)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func loadConfigFileForTest(path string, dst any) error {
|
|
return aconfig.LoaderFor(dst, aconfig.Config{
|
|
SkipEnv: true,
|
|
SkipFlags: true,
|
|
AllowUnknownFields: false,
|
|
Files: []string{path},
|
|
FileDecoders: map[string]aconfig.FileDecoder{
|
|
".toml": aconfigtoml.New(),
|
|
},
|
|
}).Load()
|
|
}
|
|
|
|
func mustReadFileForTest(t *testing.T, path string) string {
|
|
t.Helper()
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read file %s: %v", path, err)
|
|
}
|
|
return string(data)
|
|
}
|