first commit
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultAddr = ":8080"
|
||||
const defaultDataDir = ".platform-data"
|
||||
const defaultStorageBackend = "file"
|
||||
|
||||
type Config struct {
|
||||
Addr string
|
||||
StorageBackend string
|
||||
MySQLDSN string
|
||||
DataDir string
|
||||
MetadataPath string
|
||||
LogDir string
|
||||
LogBodyBackend string
|
||||
}
|
||||
|
||||
func Load() Config {
|
||||
loadLocalEnvFiles()
|
||||
|
||||
addr := os.Getenv("PLATFORM_ADDR")
|
||||
if addr == "" {
|
||||
addr = defaultAddr
|
||||
}
|
||||
dataDir := strings.TrimSpace(os.Getenv("PLATFORM_DATA_DIR"))
|
||||
if dataDir == "" {
|
||||
dataDir = defaultDataDir
|
||||
}
|
||||
metadataPath := strings.TrimSpace(os.Getenv("PLATFORM_METADATA_PATH"))
|
||||
if metadataPath == "" {
|
||||
metadataPath = filepath.Join(dataDir, "metadata.json")
|
||||
}
|
||||
logDir := strings.TrimSpace(os.Getenv("PLATFORM_LOG_DIR"))
|
||||
if logDir == "" {
|
||||
logDir = filepath.Join(dataDir, "logs")
|
||||
}
|
||||
storageBackend := strings.TrimSpace(os.Getenv("PLATFORM_STORAGE_BACKEND"))
|
||||
if storageBackend == "" {
|
||||
storageBackend = defaultStorageBackend
|
||||
}
|
||||
logBodyBackend := strings.TrimSpace(os.Getenv("PLATFORM_LOG_BODY_BACKEND"))
|
||||
|
||||
return Config{
|
||||
Addr: addr,
|
||||
StorageBackend: storageBackend,
|
||||
MySQLDSN: strings.TrimSpace(os.Getenv("PLATFORM_MYSQL_DSN")),
|
||||
DataDir: dataDir,
|
||||
MetadataPath: metadataPath,
|
||||
LogDir: logDir,
|
||||
LogBodyBackend: logBodyBackend,
|
||||
}
|
||||
}
|
||||
|
||||
func loadLocalEnvFiles() {
|
||||
candidates := []string{".env", filepath.Join("platform", ".env")}
|
||||
for _, path := range candidates {
|
||||
loadEnvFile(path)
|
||||
}
|
||||
}
|
||||
|
||||
func loadEnvFile(path string) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
key, value, ok := parseEnvLine(scanner.Text())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, exists := os.LookupEnv(key); exists {
|
||||
continue
|
||||
}
|
||||
_ = os.Setenv(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func parseEnvLine(line string) (string, string, bool) {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
return "", "", false
|
||||
}
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
||||
key, value, found := strings.Cut(line, "=")
|
||||
if !found {
|
||||
return "", "", false
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" || strings.ContainsAny(key, " \t") {
|
||||
return "", "", false
|
||||
}
|
||||
value = strings.TrimSpace(stripInlineComment(strings.TrimSpace(value)))
|
||||
if len(value) >= 2 {
|
||||
if (value[0] == '"' && value[len(value)-1] == '"') || (value[0] == '\'' && value[len(value)-1] == '\'') {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
}
|
||||
return key, value, true
|
||||
}
|
||||
|
||||
func stripInlineComment(value string) string {
|
||||
inSingleQuote := false
|
||||
inDoubleQuote := false
|
||||
for index, char := range value {
|
||||
switch char {
|
||||
case '\'':
|
||||
if !inDoubleQuote {
|
||||
inSingleQuote = !inSingleQuote
|
||||
}
|
||||
case '"':
|
||||
if !inSingleQuote {
|
||||
inDoubleQuote = !inDoubleQuote
|
||||
}
|
||||
case '#':
|
||||
if !inSingleQuote && !inDoubleQuote && index > 0 && value[index-1] == ' ' {
|
||||
return strings.TrimSpace(value[:index])
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadUsesDefaultAddress(t *testing.T) {
|
||||
t.Setenv("PLATFORM_ADDR", "")
|
||||
t.Setenv("PLATFORM_STORAGE_BACKEND", "")
|
||||
t.Setenv("PLATFORM_MYSQL_DSN", "")
|
||||
t.Setenv("PLATFORM_DATA_DIR", "")
|
||||
t.Setenv("PLATFORM_METADATA_PATH", "")
|
||||
t.Setenv("PLATFORM_LOG_DIR", "")
|
||||
t.Setenv("PLATFORM_LOG_BODY_BACKEND", "")
|
||||
|
||||
cfg := Load()
|
||||
if cfg.Addr != defaultAddr {
|
||||
t.Fatalf("expected default addr %q, got %q", defaultAddr, cfg.Addr)
|
||||
}
|
||||
if cfg.StorageBackend != "file" || cfg.MySQLDSN != "" || cfg.DataDir != ".platform-data" || cfg.LogBodyBackend != "" {
|
||||
t.Fatalf("unexpected default storage config: %+v", cfg)
|
||||
}
|
||||
if cfg.MetadataPath != filepath.Join(".platform-data", "metadata.json") || cfg.LogDir != filepath.Join(".platform-data", "logs") {
|
||||
t.Fatalf("unexpected default storage paths: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUsesConfiguredAddress(t *testing.T) {
|
||||
t.Setenv("PLATFORM_ADDR", ":18080")
|
||||
t.Setenv("PLATFORM_STORAGE_BACKEND", "mysql")
|
||||
t.Setenv("PLATFORM_MYSQL_DSN", "platform:platform@tcp(127.0.0.1:3306)/platform?parseTime=true")
|
||||
t.Setenv("PLATFORM_DATA_DIR", "/tmp/platform-data")
|
||||
t.Setenv("PLATFORM_METADATA_PATH", "/tmp/platform-metadata.json")
|
||||
t.Setenv("PLATFORM_LOG_DIR", "/tmp/platform-logs")
|
||||
t.Setenv("PLATFORM_LOG_BODY_BACKEND", "file")
|
||||
|
||||
cfg := Load()
|
||||
if cfg.Addr != ":18080" {
|
||||
t.Fatalf("expected configured addr, got %q", cfg.Addr)
|
||||
}
|
||||
if cfg.StorageBackend != "mysql" || cfg.MySQLDSN != "platform:platform@tcp(127.0.0.1:3306)/platform?parseTime=true" || cfg.DataDir != "/tmp/platform-data" || cfg.MetadataPath != "/tmp/platform-metadata.json" || cfg.LogDir != "/tmp/platform-logs" || cfg.LogBodyBackend != "file" {
|
||||
t.Fatalf("unexpected configured storage: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadReadsPlatformEnvFile(t *testing.T) {
|
||||
clearPlatformEnv(t)
|
||||
chdirTemp(t)
|
||||
|
||||
if err := os.Mkdir("platform", 0o755); err != nil {
|
||||
t.Fatalf("create platform dir: %v", err)
|
||||
}
|
||||
env := strings.Join([]string{
|
||||
"PLATFORM_ADDR=:19090",
|
||||
"PLATFORM_STORAGE_BACKEND=mysql",
|
||||
"PLATFORM_MYSQL_DSN='platform:platform@tcp(127.0.0.1:3306)/platform?parseTime=true'",
|
||||
"PLATFORM_LOG_BODY_BACKEND=file",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(filepath.Join("platform", ".env"), []byte(env), 0o600); err != nil {
|
||||
t.Fatalf("write env file: %v", err)
|
||||
}
|
||||
|
||||
cfg := Load()
|
||||
if cfg.Addr != ":19090" || cfg.StorageBackend != "mysql" || cfg.MySQLDSN != "platform:platform@tcp(127.0.0.1:3306)/platform?parseTime=true" || cfg.LogBodyBackend != "file" {
|
||||
t.Fatalf("expected config from platform/.env, got %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadKeepsProcessEnvOverEnvFile(t *testing.T) {
|
||||
clearPlatformEnv(t)
|
||||
chdirTemp(t)
|
||||
t.Setenv("PLATFORM_STORAGE_BACKEND", "memory")
|
||||
|
||||
if err := os.WriteFile(".env", []byte("PLATFORM_STORAGE_BACKEND=mysql\nPLATFORM_MYSQL_DSN=file-dsn\n"), 0o600); err != nil {
|
||||
t.Fatalf("write env file: %v", err)
|
||||
}
|
||||
|
||||
cfg := Load()
|
||||
if cfg.StorageBackend != "memory" {
|
||||
t.Fatalf("expected process env storage backend, got %+v", cfg)
|
||||
}
|
||||
if cfg.MySQLDSN != "file-dsn" {
|
||||
t.Fatalf("expected missing process DSN to come from env file, got %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func clearPlatformEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, key := range []string{
|
||||
"PLATFORM_ADDR",
|
||||
"PLATFORM_STORAGE_BACKEND",
|
||||
"PLATFORM_MYSQL_DSN",
|
||||
"PLATFORM_DATA_DIR",
|
||||
"PLATFORM_METADATA_PATH",
|
||||
"PLATFORM_LOG_DIR",
|
||||
"PLATFORM_LOG_BODY_BACKEND",
|
||||
} {
|
||||
t.Setenv(key, "")
|
||||
if err := os.Unsetenv(key); err != nil {
|
||||
t.Fatalf("unset %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chdirTemp(t *testing.T) {
|
||||
t.Helper()
|
||||
previous, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("get working directory: %v", err)
|
||||
}
|
||||
if err := os.Chdir(t.TempDir()); err != nil {
|
||||
t.Fatalf("chdir temp: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := os.Chdir(previous); err != nil {
|
||||
t.Fatalf("restore working directory: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user