From b386965bb8b3b80275b10c9c00623b8d51bee307 Mon Sep 17 00:00:00 2001 From: Kylesoda <249518290+kylesoda@users.noreply.github.com> Date: Fri, 15 May 2026 09:59:42 -0500 Subject: [PATCH] refactor: enhance database configuration handling with individual parameters and URL resolution methods --- .env.example | 17 +++++++ cmd/go_migrate/main.go | 13 ++++- internal/app/config/main.go | 97 +++++++++++++++++++++++++++++++++++-- 3 files changed, 121 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index d139d2d..50afc22 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,23 @@ SOURCE_DB_URL=sqlserver://sa:password@localhost:1433?database=master&packet+size=32767&loc=UTC&dial+timeout=120&connection+timeout=120&KeepAlive=30 + +# used only when SOURCE_DB_URL is not set +# SOURCE_DB_HOST=localhost +# SOURCE_DB_PORT=1433 +# SOURCE_DB_NAME=master +# SOURCE_DB_USER=sa +# SOURCE_DB_PWD=secure_password!123 +# SOURCE_DB_OPTIONS="packet+size=32767&loc=UTC&dial+timeout=120&connection+timeout=120&KeepAlive=30" + TARGET_DB_URL=postgresql://postgres:password@localhost:5432/db +# used only when TARGET_DB_URL is not set +# TARGET_DB_HOST=localhost +# TARGET_DB_PORT=5432 +# TARGET_DB_NAME=db +# TARGET_DB_USER=postgres +# TARGET_DB_PWD=secure_password!123 +# TARGET_DB_OPTIONS="" + LOG_LEVEL=INFO AZ_STORAGE_ENABLED=false diff --git a/cmd/go_migrate/main.go b/cmd/go_migrate/main.go index eca532f..c17e5f1 100644 --- a/cmd/go_migrate/main.go +++ b/cmd/go_migrate/main.go @@ -43,6 +43,15 @@ func main() { startTime := time.Now() + sourceDbUrl, err := config.App.ResolveSourceDbUrl(migrationConfig.SourceDbType) + if err != nil { + log.Fatalf("source DB config error: %v", err) + } + targetDbUrl, err := config.App.ResolveTargetDbUrl(migrationConfig.TargetDbType) + if err != nil { + log.Fatalf("target DB config error: %v", err) + } + ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -51,7 +60,7 @@ func main() { wgConnect.Go(func() error { var err error - sourceDb, err = connectWithTimeout(ctx, migrationConfig.SourceDbType, config.App.SourceDbUrl, 20*time.Second) + sourceDb, err = connectWithTimeout(ctx, migrationConfig.SourceDbType, sourceDbUrl, 20*time.Second) if err != nil { return err } @@ -62,7 +71,7 @@ func main() { wgConnect.Go(func() error { var err error - targetDb, err = connectWithTimeout(ctx, migrationConfig.TargetDbType, config.App.TargetDbUrl, 20*time.Second) + targetDb, err = connectWithTimeout(ctx, migrationConfig.TargetDbType, targetDbUrl, 20*time.Second) if err != nil { return err } diff --git a/internal/app/config/main.go b/internal/app/config/main.go index 5e5e6db..2270a5e 100644 --- a/internal/app/config/main.go +++ b/internal/app/config/main.go @@ -1,6 +1,10 @@ package config import ( + "fmt" + "maps" + "net/url" + "github.com/ilyakaznacheev/cleanenv" log "github.com/sirupsen/logrus" ) @@ -16,10 +20,95 @@ type AzureStorageConfig struct { } type appConfig struct { - SourceDbUrl string `env:"SOURCE_DB_URL" env-required:"true"` - TargetDbUrl string `env:"TARGET_DB_URL" env-required:"true"` - LogLevel string `env:"LOG_LEVEL" env-default:"INFO"` - AzureStorage AzureStorageConfig + SourceDbUrl string `env:"SOURCE_DB_URL"` + SourceDbHost string `env:"SOURCE_DB_HOST"` + SourceDbPort string `env:"SOURCE_DB_PORT"` + SourceDbName string `env:"SOURCE_DB_NAME"` + SourceDbUser string `env:"SOURCE_DB_USER"` + SourceDbPwd string `env:"SOURCE_DB_PWD"` + SourceDbOptions string `env:"SOURCE_DB_OPTIONS"` + TargetDbUrl string `env:"TARGET_DB_URL"` + TargetDbHost string `env:"TARGET_DB_HOST"` + TargetDbPort string `env:"TARGET_DB_PORT"` + TargetDbName string `env:"TARGET_DB_NAME"` + TargetDbUser string `env:"TARGET_DB_USER"` + TargetDbPwd string `env:"TARGET_DB_PWD"` + TargetDbOptions string `env:"TARGET_DB_OPTIONS"` + LogLevel string `env:"LOG_LEVEL" env-default:"INFO"` + AzureStorage AzureStorageConfig +} + +func (c *appConfig) ResolveSourceDbUrl(dbType string) (string, error) { + if c.SourceDbUrl != "" { + return c.SourceDbUrl, nil + } + u, err := buildDbUrl(dbType, c.SourceDbHost, c.SourceDbPort, c.SourceDbName, c.SourceDbUser, c.SourceDbPwd, c.SourceDbOptions) + if err != nil { + return "", fmt.Errorf("source DB: %w", err) + } + return u, nil +} + +func (c *appConfig) ResolveTargetDbUrl(dbType string) (string, error) { + if c.TargetDbUrl != "" { + return c.TargetDbUrl, nil + } + u, err := buildDbUrl(dbType, c.TargetDbHost, c.TargetDbPort, c.TargetDbName, c.TargetDbUser, c.TargetDbPwd, c.TargetDbOptions) + if err != nil { + return "", fmt.Errorf("target DB: %w", err) + } + return u, nil +} + +func buildDbUrl(dbType, host, port, name, user, pwd, options string) (string, error) { + if host == "" { + return "", fmt.Errorf("DB_HOST is required when DB_URL is not set") + } + if name == "" { + return "", fmt.Errorf("DB_NAME is required when DB_URL is not set") + } + if user == "" { + return "", fmt.Errorf("DB_USER is required when DB_URL is not set") + } + + switch dbType { + case "sqlserver": + if port == "" { + port = "1433" + } + q := url.Values{} + if options != "" { + extra, err := url.ParseQuery(options) + if err != nil { + return "", fmt.Errorf("invalid DB_OPTIONS: %w", err) + } + maps.Copy(q, extra) + } + q.Set("database", name) + u := &url.URL{ + Scheme: "sqlserver", + Host: host + ":" + port, + User: url.UserPassword(user, pwd), + RawQuery: q.Encode(), + } + return u.String(), nil + + case "postgres": + if port == "" { + port = "5432" + } + u := &url.URL{ + Scheme: "postgres", + Host: host + ":" + port, + User: url.UserPassword(user, pwd), + Path: "/" + name, + RawQuery: options, + } + return u.String(), nil + + default: + return "", fmt.Errorf("unknown db type %q — cannot build URL from individual components", dbType) + } } func getAppConfig() appConfig {