(ctx context.Context, cfg config.Root, logger slog.Logger, customCacheDir string)
| 2352 | } |
| 2353 | |
| 2354 | func startBuiltinPostgres(ctx context.Context, cfg config.Root, logger slog.Logger, customCacheDir string) (string, func() error, error) { |
| 2355 | usr, err := user.Current() |
| 2356 | if err != nil { |
| 2357 | return "", nil, err |
| 2358 | } |
| 2359 | if usr.Uid == "0" { |
| 2360 | return "", nil, xerrors.New("The built-in PostgreSQL cannot run as the root user. Create a non-root user and run again!") |
| 2361 | } |
| 2362 | |
| 2363 | cachePath := filepath.Join(cfg.PostgresPath(), "cache") |
| 2364 | if customCacheDir != "" { |
| 2365 | cachePath = filepath.Join(customCacheDir, "postgres") |
| 2366 | } |
| 2367 | stdlibLogger := slog.Stdlib(ctx, logger.Named("postgres"), slog.LevelDebug) |
| 2368 | |
| 2369 | // If the port is not defined, an available port will be found dynamically. This has |
| 2370 | // implications in CI because here is no way to tell Postgres to use an ephemeral |
| 2371 | // port, so to avoid flaky tests in CI we need to retry EmbeddedPostgres.Start in |
| 2372 | // case of a race condition where the port we quickly listen on and close in |
| 2373 | // embeddedPostgresURL() is not free by the time the embedded postgres starts up. |
| 2374 | // The maximum retry attempts _should_ cover most cases where port conflicts occur |
| 2375 | // in CI and cause flaky tests. |
| 2376 | maxAttempts := 1 |
| 2377 | _, err = cfg.PostgresPort().Read() |
| 2378 | // Important: if retryPortDiscovery is changed to not include testing.Testing(), |
| 2379 | // the retry logic below also needs to be updated to ensure we don't delete an |
| 2380 | // existing database |
| 2381 | retryPortDiscovery := errors.Is(err, os.ErrNotExist) && testing.Testing() |
| 2382 | if retryPortDiscovery { |
| 2383 | maxAttempts = 10 |
| 2384 | } |
| 2385 | |
| 2386 | var startErr error |
| 2387 | for attempt := 0; attempt < maxAttempts; attempt++ { |
| 2388 | if retryPortDiscovery && attempt > 0 { |
| 2389 | // Clean up the data and runtime directories and the port file from the |
| 2390 | // previous failed attempt to ensure a clean slate for the next attempt. |
| 2391 | _ = os.RemoveAll(filepath.Join(cfg.PostgresPath(), "data")) |
| 2392 | _ = os.RemoveAll(filepath.Join(cfg.PostgresPath(), "runtime")) |
| 2393 | _ = cfg.PostgresPort().Delete() |
| 2394 | } |
| 2395 | |
| 2396 | // Ensure a password and port have been generated. |
| 2397 | connectionURL, err := embeddedPostgresURL(cfg) |
| 2398 | if err != nil { |
| 2399 | return "", nil, err |
| 2400 | } |
| 2401 | pgPassword, err := cfg.PostgresPassword().Read() |
| 2402 | if err != nil { |
| 2403 | return "", nil, xerrors.Errorf("read postgres password: %w", err) |
| 2404 | } |
| 2405 | pgPortRaw, err := cfg.PostgresPort().Read() |
| 2406 | if err != nil { |
| 2407 | return "", nil, xerrors.Errorf("read postgres port: %w", err) |
| 2408 | } |
| 2409 | pgPort, err := strconv.ParseUint(pgPortRaw, 10, 16) |
| 2410 | if err != nil { |
| 2411 | return "", nil, xerrors.Errorf("parse postgres port: %w", err) |
no test coverage detected