Files
netbird/tools/idp-migrate/config.go

175 lines
4.5 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"strconv"
"github.com/netbirdio/netbird/util"
)
type migrationConfig struct {
// Data
dashboardURL string
apiURL string
configPath string
dataDir string
idpSeedInfo string
// Options
dryRun bool
force bool
skipConfig bool
skipPopulateUserInfo bool
// Logging
logLevel string
}
func config() (*migrationConfig, error) {
cfg, err := configFromArgs(os.Args[1:])
if err != nil {
return nil, err
}
if err := util.InitLog(cfg.logLevel, util.LogConsole); err != nil {
return nil, fmt.Errorf("init logger: %w", err)
}
return cfg, nil
}
func configFromArgs(args []string) (*migrationConfig, error) {
var cfg migrationConfig
var domain string
fs := flag.NewFlagSet("netbird-idp-migrate", flag.ContinueOnError)
fs.StringVar(&domain, "domain", "", "domain for both dashboard and API")
fs.StringVar(&cfg.dashboardURL, "dashboard-url", "", "dashboard URL")
fs.StringVar(&cfg.apiURL, "api-url", "", "API URL")
fs.StringVar(&cfg.configPath, "config", "", "path to management.json (required)")
fs.StringVar(&cfg.dataDir, "datadir", "", "override data directory from config")
fs.StringVar(&cfg.idpSeedInfo, "idp-seed-info", "", "base64-encoded connector JSON (overrides auto-detection)")
fs.BoolVar(&cfg.dryRun, "dry-run", false, "preview changes without writing")
fs.BoolVar(&cfg.force, "force", false, "skip confirmation prompt")
fs.BoolVar(&cfg.skipConfig, "skip-config", false, "skip config generation (DB migration only)")
fs.BoolVar(&cfg.skipPopulateUserInfo, "skip-populate-user-info", false, "skip populating user info (user id migration only)")
fs.StringVar(&cfg.logLevel, "log-level", "info", "log level (debug, info, warn, error)")
if err := fs.Parse(args); err != nil {
return nil, err
}
applyOverrides(&cfg, domain)
if err := validateConfig(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
// applyOverrides resolves domain configuration from broad to narrow sources.
// The most granular value always wins:
//
// --domain flag (broadest, only fills blanks)
// NETBIRD_DOMAIN env (overrides flags, sets both)
// --api-domain / --dashboard-domain flags (more specific than --domain)
// NETBIRD_API_URL / NETBIRD_DASHBOARD_URL env (most specific, always wins)
//
// Other env vars unconditionally override their corresponding flags.
func applyOverrides(cfg *migrationConfig, domain string) {
// --domain is a convenience shorthand: only fills in values not already
// set by the more specific --api-domain / --dashboard-domain flags.
if domain != "" {
if cfg.apiURL == "" {
cfg.apiURL = domain
}
if cfg.dashboardURL == "" {
cfg.dashboardURL = domain
}
}
// Env vars override flags. Broad env var first, then narrow ones on top,
// so the most granular value always wins.
if val, ok := os.LookupEnv("NETBIRD_DOMAIN"); ok {
cfg.dashboardURL = val
cfg.apiURL = val
}
if val, ok := os.LookupEnv("NETBIRD_API_URL"); ok {
cfg.apiURL = val
}
if val, ok := os.LookupEnv("NETBIRD_DASHBOARD_URL"); ok {
cfg.dashboardURL = val
}
if val, ok := os.LookupEnv("NETBIRD_CONFIG_PATH"); ok {
cfg.configPath = val
}
if val, ok := os.LookupEnv("NETBIRD_DATA_DIR"); ok {
cfg.dataDir = val
}
if val, ok := os.LookupEnv("NETBIRD_IDP_SEED_INFO"); ok {
cfg.idpSeedInfo = val
}
// Enforce dry run if any value is provided
if sval, ok := os.LookupEnv("NETBIRD_DRY_RUN"); ok {
if val, err := strconv.ParseBool(sval); err == nil {
cfg.dryRun = val
}
}
cfg.dryRun = parseBool("NETBIRD_DRY_RUN", cfg.dryRun)
cfg.force = parseBool("NETBIRD_FORCE", cfg.force)
cfg.skipConfig = parseBool("NETBIRD_SKIP_CONFIG", cfg.skipConfig)
cfg.skipPopulateUserInfo = parseBool("NETBIRD_SKIP_POPULATE_USER_INFO", cfg.skipPopulateUserInfo)
if val, ok := os.LookupEnv("NETBIRD_LOG_LEVEL"); ok {
cfg.logLevel = val
}
}
func parseBool(varName string, defaultVal bool) bool {
stringValue, ok := os.LookupEnv(varName)
if !ok {
return defaultVal
}
boolValue, err := strconv.ParseBool(stringValue)
if err != nil {
return defaultVal
}
return boolValue
}
func validateConfig(cfg *migrationConfig) error {
if cfg.configPath == "" {
return fmt.Errorf("--config is required")
}
if cfg.dataDir == "" {
return fmt.Errorf("--datadir is required")
}
if cfg.idpSeedInfo == "" {
return fmt.Errorf("--idp-seed-info is required")
}
if cfg.apiURL == "" {
return fmt.Errorf("--api-domain is required")
}
if cfg.dashboardURL == "" {
return fmt.Errorf("--dashboard-domain is required")
}
return nil
}