Loading internal/config/config.go +15 −0 Changes for internal/config/config.go: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ import ( // takes in its comma-separated --accept flag. var scanIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`) // dnsLabel is a Kubernetes namespace or object name (RFC 1123 label). var dnsLabel = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$`) // Config is the parsed felis.toml. type Config struct { Server ServerConfig `toml:"server"` Loading Loading @@ -120,6 +123,12 @@ type ServerConfig struct { // DatabaseConfig is the [database] table. type DatabaseConfig struct { URL string `toml:"url"` // Deployment is the k3s Deployment the database runs as, "namespace/name" // ("felis/felis-postgres"), written into the host copy only. The host // carries no PostgreSQL client, so `felis db backup`/`restore` and the // pre-migration snapshot run pg_dump, pg_restore and psql inside its // postgres container. Empty runs the tools on PATH against url. Deployment string `toml:"deployment"` } // VelocityConfig is the [velocity] table. Loading Loading @@ -531,6 +540,12 @@ func (c *Config) Validate() error { if c.Database.URL == "" { return fmt.Errorf("config: [database] url is required") } if d := c.Database.Deployment; d != "" { ns, name, ok := strings.Cut(d, "/") if !ok || !dnsLabel.MatchString(ns) || !dnsLabel.MatchString(name) { return fmt.Errorf("config: [database] deployment %q is not namespace/name", d) } } if c.Server.RootDomain == "" { return fmt.Errorf("config: [server] root_domain is required") } Loading internal/config/config_test.go +19 −0 Changes for internal/config/config_test.go: 19 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -171,6 +171,25 @@ root_domain = "mc.example.net" } } func TestLoadDatabaseDeployment(t *testing.T) { withDeployment := func(v string) string { return strings.Replace(validTOML, `url = "postgres://felis:secret@db:5432/felis"`, `url = "postgres://felis:secret@db:5432/felis"`+"\ndeployment = \""+v+"\"", 1) } cfg, err := config.Load(writeTOML(t, withDeployment("felis/felis-postgres"))) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Database.Deployment != "felis/felis-postgres" { t.Fatalf("deployment = %q", cfg.Database.Deployment) } for _, bad := range []string{"felis-postgres", "felis/", "/felis-postgres", "Felis/pg", "felis/pg; rm -rf /", "a/b/c"} { if _, err := config.Load(writeTOML(t, withDeployment(bad))); err == nil || !strings.Contains(err.Error(), "namespace/name") { t.Errorf("deployment %q: err = %v, want a namespace/name refusal", bad, err) } } } func TestLoadRejectsMissingRootDomain(t *testing.T) { _, err := config.Load(writeTOML(t, ` [database] Loading Loading
internal/config/config.go +15 −0 Changes for internal/config/config.go: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ import ( // takes in its comma-separated --accept flag. var scanIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`) // dnsLabel is a Kubernetes namespace or object name (RFC 1123 label). var dnsLabel = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$`) // Config is the parsed felis.toml. type Config struct { Server ServerConfig `toml:"server"` Loading Loading @@ -120,6 +123,12 @@ type ServerConfig struct { // DatabaseConfig is the [database] table. type DatabaseConfig struct { URL string `toml:"url"` // Deployment is the k3s Deployment the database runs as, "namespace/name" // ("felis/felis-postgres"), written into the host copy only. The host // carries no PostgreSQL client, so `felis db backup`/`restore` and the // pre-migration snapshot run pg_dump, pg_restore and psql inside its // postgres container. Empty runs the tools on PATH against url. Deployment string `toml:"deployment"` } // VelocityConfig is the [velocity] table. Loading Loading @@ -531,6 +540,12 @@ func (c *Config) Validate() error { if c.Database.URL == "" { return fmt.Errorf("config: [database] url is required") } if d := c.Database.Deployment; d != "" { ns, name, ok := strings.Cut(d, "/") if !ok || !dnsLabel.MatchString(ns) || !dnsLabel.MatchString(name) { return fmt.Errorf("config: [database] deployment %q is not namespace/name", d) } } if c.Server.RootDomain == "" { return fmt.Errorf("config: [server] root_domain is required") } Loading
internal/config/config_test.go +19 −0 Changes for internal/config/config_test.go: 19 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -171,6 +171,25 @@ root_domain = "mc.example.net" } } func TestLoadDatabaseDeployment(t *testing.T) { withDeployment := func(v string) string { return strings.Replace(validTOML, `url = "postgres://felis:secret@db:5432/felis"`, `url = "postgres://felis:secret@db:5432/felis"`+"\ndeployment = \""+v+"\"", 1) } cfg, err := config.Load(writeTOML(t, withDeployment("felis/felis-postgres"))) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Database.Deployment != "felis/felis-postgres" { t.Fatalf("deployment = %q", cfg.Database.Deployment) } for _, bad := range []string{"felis-postgres", "felis/", "/felis-postgres", "Felis/pg", "felis/pg; rm -rf /", "a/b/c"} { if _, err := config.Load(writeTOML(t, withDeployment(bad))); err == nil || !strings.Contains(err.Error(), "namespace/name") { t.Errorf("deployment %q: err = %v, want a namespace/name refusal", bad, err) } } } func TestLoadRejectsMissingRootDomain(t *testing.T) { _, err := config.Load(writeTOML(t, ` [database] Loading