Skip to content

[management] Allow activityStore DSN via env when using postgres#6668

Open
Tarasusrus wants to merge 3 commits into
netbirdio:mainfrom
Tarasusrus:fix/activitystore-dsn-env
Open

[management] Allow activityStore DSN via env when using postgres#6668
Tarasusrus wants to merge 3 commits into
netbirdio:mainfrom
Tarasusrus:fix/activitystore-dsn-env

Conversation

@Tarasusrus

@Tarasusrus Tarasusrus commented Jul 4, 2026

Copy link
Copy Markdown

Summary

  • initializeConfig() rejected server.activityStore.engine: postgres whenever server.activityStore.dsn was empty in config.yaml, even though the store layer (management/server/activity/store/sql_store.go) already reads NB_ACTIVITY_EVENT_POSTGRES_DSN as a fallback.
  • This blocked env-only / Kubernetes-style deployments (secrets via env, not on-disk config) from configuring the activity store on postgres.
  • Fix: the eager check in combined/cmd/root.go now also accepts the DSN via NB_ACTIVITY_EVENT_POSTGRES_DSN before failing.

Fixes #5976.

Test plan

  • Added a red/green unit test (combined/cmd/root_test.go) reproducing the exact reported error, now passing after the fix
  • go test ./combined/cmd/... passes
  • golangci-lint run ./combined/cmd/... clean
  • gofmt clean

Note for maintainers

While investigating this, I found the same class of problem is worse for authStore (embedded IdP storage): combined/cmd/config.go:576 requires authStore.dsn in config.yaml with no environment variable fallback at all anywhere in the codebase (unlike activityStore/main store). Kept out of scope here since it's a separate code path; happy to open a follow-up issue/PR if useful.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Updated PostgreSQL activity storage configuration validation: when server.activityStore.engine is set to postgres and the DSN is missing, the required DSN is now taken from the NB_ACTIVITY_EVENT_POSTGRES_DSN environment variable.
    • Improved the error message when the PostgreSQL DSN is not provided.
  • Tests
    • Added regression tests covering successful DSN loading from the environment variable and failure when the environment variable is unset or empty.

initializeConfig() rejected a postgres activityStore.engine whenever
activityStore.dsn was empty in config.yaml, even though the store layer
already reads NB_ACTIVITY_EVENT_POSTGRES_DSN as a fallback. This blocked
env-only/K8s-style deployments from configuring the activity store.
Fixes netbirdio#5976.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jul 4, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce404dd5-aaaa-4a13-a336-214d70ea4d1d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0f3f7 and fa8d716.

📒 Files selected for processing (1)
  • combined/cmd/root_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • combined/cmd/root_test.go

📝 Walkthrough

Walkthrough

initializeConfig now accepts NB_ACTIVITY_EVENT_POSTGRES_DSN when server.activityStore.engine is postgres and server.activityStore.dsn is empty, and it errors only when neither config nor environment provides a DSN. Tests cover both the env-backed success path and the missing/empty env failure cases.

Changes

Activity Store DSN Env Fallback

Layer / File(s) Summary
DSN env-var validation and test
combined/cmd/root.go, combined/cmd/root_test.go
initializeConfig now checks NB_ACTIVITY_EVENT_POSTGRES_DSN before failing postgres activity-store config, and the new regression tests verify both successful env-based loading and failure when the variable is unset or empty.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • netbirdio/netbird#5406: Also changes initializeConfig around postgres activity-store DSN handling with NB_ACTIVITY_EVENT_POSTGRES_DSN.

Suggested reviewers: mlsmaycon, shuuri-labs

Poem

A little DSN in the breeze,
The rabbit nods: config at ease.
From env to store, the path runs true,
No empty gap to stumble through. 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change and testing, but it does not follow the required template headings or include the checklist/documentation sections. Rewrite it using the repository template: Describe your changes, issue link, stack, checklist, and documentation section with the required selections.
Linked Issues check ⚠️ Warning Issue #5976 also requires auth store DSN env configurability, but this PR only fixes activityStore and its tests. Add the authStore env fallback behavior and tests, or split the auth-store requirement into a separate linked issue/PR if intentionally out of scope.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: allowing the activityStore DSN to come from env when postgres is used.
Out of Scope Changes check ✅ Passed The code changes stay focused on activityStore DSN fallback and tests, with no unrelated modifications evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
combined/cmd/root_test.go (1)

18-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Only the "green" success path is covered; the "red" regression case is missing.

The PR description states this adds a "red/green" test reproducing the original error and verifying the fix, but only the success path (env var set) is present here. Without a companion test asserting initializeConfig() still errors when activityStore.engine: postgres and neither dsn nor the env var is set, a future refactor could silently reintroduce the original bug (e.g., accidentally checking the wrong env var name) without failing any test.

✅ Suggested companion test
func TestInitializeConfig_ActivityStorePostgresMissingDSN(t *testing.T) {
	dir := t.TempDir()
	cfgFile := filepath.Join(dir, "config.yaml")

	yamlContent := `
server:
  exposedAddress: "https://netbird.example.com:443"
  dataDir: "` + dir + `"
  authSecret: "test-secret"
  activityStore:
    engine: postgres
`
	require.NoError(t, os.WriteFile(cfgFile, []byte(yamlContent), 0o600))

	oldConfigPath := configPath
	oldConfig := config
	t.Cleanup(func() {
		configPath = oldConfigPath
		config = oldConfig
	})
	configPath = cfgFile

	err := initializeConfig()
	require.Error(t, err, "initializeConfig should reject missing activityStore DSN when no env var is set")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@combined/cmd/root_test.go` around lines 18 - 44, The current test only covers
the successful env-var path in
TestInitializeConfig_ActivityStorePostgresDSNFromEnv; add a companion regression
test around initializeConfig that uses the same postgres activityStore config
but does not set NB_ACTIVITY_EVENT_POSTGRES_DSN and asserts an error is
returned. Keep the setup aligned with the existing test helpers and
configPath/config cleanup so the pair of tests covers both the green and red
cases.
combined/cmd/root.go (1)

152-163: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Presence check doesn't guard against an explicitly-empty env var.

os.LookupEnv only confirms the variable is set, not that it has a non-empty value. If NB_ACTIVITY_EVENT_POSTGRES_DSN is set to "" (common in some deployment tooling, e.g. templated secrets that resolve to empty), this check passes but the DSN is effectively still missing, silently deferring to a less informative failure deep in the store layer.

🐛 Proposed fix
 		if engineLower == "postgres" && config.Server.ActivityStore.DSN == "" {
-			if _, ok := os.LookupEnv("NB_ACTIVITY_EVENT_POSTGRES_DSN"); !ok {
+			if envDSN := os.Getenv("NB_ACTIVITY_EVENT_POSTGRES_DSN"); envDSN == "" {
 				return fmt.Errorf("activityStore.dsn is required when activityStore.engine is postgres (or set NB_ACTIVITY_EVENT_POSTGRES_DSN)")
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@combined/cmd/root.go` around lines 152 - 163, The activity store DSN
validation in the root command only checks whether
NB_ACTIVITY_EVENT_POSTGRES_DSN is present, so an explicitly empty value can
still slip through and fail later. Update the validation around
config.Server.ActivityStore.Engine in root handling to treat a blank environment
value the same as missing by checking the retrieved DSN value for emptiness, not
just os.LookupEnv presence. Keep the existing postgres/DSN guard in place, but
ensure the error is returned when either config.Server.ActivityStore.DSN or
NB_ACTIVITY_EVENT_POSTGRES_DSN is empty, and keep the os.Setenv calls using the
normalized engine and resolved DSN as they are.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@combined/cmd/root_test.go`:
- Around line 18-44: The current test only covers the successful env-var path in
TestInitializeConfig_ActivityStorePostgresDSNFromEnv; add a companion regression
test around initializeConfig that uses the same postgres activityStore config
but does not set NB_ACTIVITY_EVENT_POSTGRES_DSN and asserts an error is
returned. Keep the setup aligned with the existing test helpers and
configPath/config cleanup so the pair of tests covers both the green and red
cases.

In `@combined/cmd/root.go`:
- Around line 152-163: The activity store DSN validation in the root command
only checks whether NB_ACTIVITY_EVENT_POSTGRES_DSN is present, so an explicitly
empty value can still slip through and fail later. Update the validation around
config.Server.ActivityStore.Engine in root handling to treat a blank environment
value the same as missing by checking the retrieved DSN value for emptiness, not
just os.LookupEnv presence. Keep the existing postgres/DSN guard in place, but
ensure the error is returned when either config.Server.ActivityStore.DSN or
NB_ACTIVITY_EVENT_POSTGRES_DSN is empty, and keep the os.Setenv calls using the
normalized engine and resolved DSN as they are.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e02aa0e7-5397-4209-b229-67f1fdb4c0f9

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa6c02 and 6fdbfc9.

📒 Files selected for processing (2)
  • combined/cmd/root.go
  • combined/cmd/root_test.go

… test

- Treat NB_ACTIVITY_EVENT_POSTGRES_DSN="" the same as unset (os.Getenv
  instead of os.LookupEnv), so templated-but-empty secrets don't silently
  pass validation.
- Add companion test covering both env unset and env set to "", ensuring
  initializeConfig still rejects a postgres activityStore with no DSN.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
combined/cmd/root_test.go (2)

72-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

os.Unsetenv doesn't restore prior env state.

Unlike the sibling subtest which uses t.Setenv (auto-restored), this subtest calls os.Unsetenv directly with no save/restore. If NB_ACTIVITY_EVENT_POSTGRES_DSN happens to be set in the ambient environment when tests run (plausible given this PR's own use case of env-injected DSNs in CI/K8s), this permanently clears it for the remainder of the test binary process, potentially affecting other tests in the package.

🧪 Proposed fix to save/restore env state
 	t.Run("env var unset", func(t *testing.T) {
-		os.Unsetenv("NB_ACTIVITY_EVENT_POSTGRES_DSN")
+		orig, wasSet := os.LookupEnv("NB_ACTIVITY_EVENT_POSTGRES_DSN")
+		os.Unsetenv("NB_ACTIVITY_EVENT_POSTGRES_DSN")
+		if wasSet {
+			t.Cleanup(func() { os.Setenv("NB_ACTIVITY_EVENT_POSTGRES_DSN", orig) })
+		}
 		err := initializeConfig()
 		require.Error(t, err, "initializeConfig should reject missing activityStore DSN when no env var is set")
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@combined/cmd/root_test.go` around lines 72 - 76, The "env var unset" subtest
in cmd/root_test.go is mutating NB_ACTIVITY_EVENT_POSTGRES_DSN with os.Unsetenv
without restoring the prior value. Update this test to use t.Setenv or
explicitly save and restore the original environment state around
initializeConfig so the change is isolated to the subtest. Keep the fix near the
existing subtests for initializeConfig and preserve the current assertion
behavior.

46-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate temp-config/configPath-swap boilerplate across tests.

This setup (write temp config.yaml, save/swap/restore configPath and config globals) mirrors TestInitializeConfig_ActivityStorePostgresDSNFromEnv (lines 11-44 per the change summary). Consider extracting a small test helper (e.g., withTempConfig(t, yamlContent) func()) to reduce duplication across both tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@combined/cmd/root_test.go` around lines 46 - 70, The temp config setup in
TestInitializeConfig_ActivityStorePostgresMissingDSN duplicates the same
configPath/config swap boilerplate used by
TestInitializeConfig_ActivityStorePostgresDSNFromEnv. Extract a small shared
test helper around writing the temporary config.yaml and saving/restoring the
configPath and config globals, then use it in both tests to remove duplication
while keeping the same setup behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@combined/cmd/root_test.go`:
- Around line 72-76: The "env var unset" subtest in cmd/root_test.go is mutating
NB_ACTIVITY_EVENT_POSTGRES_DSN with os.Unsetenv without restoring the prior
value. Update this test to use t.Setenv or explicitly save and restore the
original environment state around initializeConfig so the change is isolated to
the subtest. Keep the fix near the existing subtests for initializeConfig and
preserve the current assertion behavior.
- Around line 46-70: The temp config setup in
TestInitializeConfig_ActivityStorePostgresMissingDSN duplicates the same
configPath/config swap boilerplate used by
TestInitializeConfig_ActivityStorePostgresDSNFromEnv. Extract a small shared
test helper around writing the temporary config.yaml and saving/restoring the
configPath and config globals, then use it in both tests to remove duplication
while keeping the same setup behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 467dbb89-aad6-4d1c-8886-207bf3f5f26c

📥 Commits

Reviewing files that changed from the base of the PR and between 6fdbfc9 and 2b0f3f7.

📒 Files selected for processing (2)
  • combined/cmd/root.go
  • combined/cmd/root_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • combined/cmd/root.go

os.Unsetenv had no save/restore, so if NB_ACTIVITY_EVENT_POSTGRES_DSN
was already set in the ambient test environment, it stayed cleared for
the rest of the test binary process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity and auth datastore DSN are not configurable via environment variables

2 participants