Own ConfigWatcher API token persistence

This commit is contained in:
rcourtman
2026-04-12 11:19:34 +01:00
parent ef288b250b
commit bf30a094bb
3 changed files with 18 additions and 25 deletions
+12 -3
View File
@@ -31,6 +31,7 @@ type ConfigWatcher struct {
config *Config
envPath string
apiTokensPath string
persistence *ConfigPersistence
watcher *fsnotify.Watcher
stopChan chan struct{}
stopOnce sync.Once // Ensures Stop() can only close channel once
@@ -81,11 +82,19 @@ func NewConfigWatcher(config *Config) (*ConfigWatcher, error) {
}
apiTokensPath := filepath.Join(filepath.Dir(envPath), "api_tokens.json")
persistence, persistenceErr := newConfigPersistence(persistentDataDir)
if persistenceErr != nil {
log.Warn().
Err(persistenceErr).
Str("configDir", persistentDataDir).
Msg("Config watcher persistence unavailable; API token reloads will be disabled")
}
cw := &ConfigWatcher{
config: config,
envPath: envPath,
apiTokensPath: apiTokensPath,
persistence: persistence,
watcher: watcher,
stopChan: make(chan struct{}),
pollInterval: 5 * time.Second,
@@ -412,7 +421,7 @@ func (cw *ConfigWatcher) reloadAPITokens() {
cw.mu.Lock()
defer cw.mu.Unlock()
if globalPersistence == nil {
if cw.persistence == nil {
log.Warn().Str("api_tokens_path", cw.apiTokensPath).Msg("Config persistence unavailable; cannot reload API tokens")
return
}
@@ -428,7 +437,7 @@ func (cw *ConfigWatcher) reloadAPITokens() {
retryDelay := 50 * time.Millisecond
for attempt := 1; attempt <= maxRetries; attempt++ {
tokens, err = globalPersistence.LoadAPITokens()
tokens, err = cw.persistence.LoadAPITokens()
if err == nil {
break
}
@@ -476,7 +485,7 @@ func (cw *ConfigWatcher) reloadAPITokens() {
}
if persistMutations {
if err := globalPersistence.SaveAPITokens(tokens); err != nil {
if err := cw.persistence.SaveAPITokens(tokens); err != nil {
log.Error().
Err(err).
Str("api_tokens_path", cw.apiTokensPath).
+1 -5
View File
@@ -28,6 +28,7 @@ func TestConfigWatcher_WatchForChanges_Live(t *testing.T) {
require.NoError(t, os.WriteFile(apiTokensPath, []byte("[]"), 0o644))
t.Setenv("PULSE_AUTH_CONFIG_DIR", tempDir)
t.Setenv("PULSE_MOCK_TEST", "before")
cfg := &Config{}
cw, err := NewConfigWatcher(cfg)
@@ -38,15 +39,10 @@ func TestConfigWatcher_WatchForChanges_Live(t *testing.T) {
cw.SetMockReloadCallback(func() { mockReloaded <- true })
cw.SetAPITokenReloadCallback(func() { tokensReloaded <- true })
p := NewConfigPersistence(tempDir)
originalPersistence := globalPersistence
globalPersistence = p
err = cw.Start()
require.NoError(t, err)
t.Cleanup(func() {
cw.Stop()
globalPersistence = originalPersistence
})
require.NoError(t, os.WriteFile(envPath, []byte("PULSE_AUTH_USER=something-different\nPULSE_MOCK_TEST=2"), 0o644))
+5 -17
View File
@@ -145,17 +145,13 @@ func TestConfigWatcher_ReloadAPITokens(t *testing.T) {
tempDir := t.TempDir()
p := NewConfigPersistence(tempDir)
// Save globalPersistence to restore later
originalPersistence := globalPersistence
globalPersistence = p
defer func() { globalPersistence = originalPersistence }()
// Setup Watcher
apiTokensPath := filepath.Join(tempDir, "api_tokens.json")
cfg := &Config{}
cw := &ConfigWatcher{
config: cfg,
apiTokensPath: apiTokensPath,
persistence: p,
}
var callbackCalled atomic.Bool
@@ -261,11 +257,7 @@ func TestConfigWatcher_PollForChanges(t *testing.T) {
cw.SetMockReloadCallback(func() { mockCalled.Store(true) })
cw.SetAPITokenReloadCallback(func() { tokenCalled.Store(true) })
// Mock global persistence for API token reloads
p := NewConfigPersistence(tempDir)
originalPersistence := globalPersistence
globalPersistence = p
defer func() { globalPersistence = originalPersistence }()
require.NotNil(t, cw.persistence)
// Run pollForChanges in background
go cw.pollForChanges()
@@ -293,10 +285,9 @@ func TestConfigWatcher_PollForChanges(t *testing.T) {
// 3. Update api_tokens.json
// Write valid JSON
// We need to write to file that Persistence reads.
// ReloadAPITokens uses globalPersistence to load.
// Write to the watcher-owned persistence so reload reads the same installation scope.
tokens := []APITokenRecord{{ID: "new", Hash: "hash", Name: "New"}}
require.NoError(t, p.SaveAPITokens(tokens))
require.NoError(t, cw.persistence.SaveAPITokens(tokens))
future = future.Add(2 * time.Second)
require.NoError(t, os.Chtimes(apiTokensPath, future, future))
@@ -418,10 +409,6 @@ func TestConfigWatcher_ReloadAPITokens_Retries(t *testing.T) {
tempDir := t.TempDir()
p := NewConfigPersistence(tempDir)
originalPersistence := globalPersistence
globalPersistence = p
defer func() { globalPersistence = originalPersistence }()
apiTokensPath := filepath.Join(tempDir, "api_tokens.json")
require.NoError(t, os.WriteFile(apiTokensPath, []byte("{invalid-json"), 0644))
@@ -429,6 +416,7 @@ func TestConfigWatcher_ReloadAPITokens_Retries(t *testing.T) {
cw := &ConfigWatcher{
config: cfg,
apiTokensPath: apiTokensPath,
persistence: p,
}
// Should attempt retries and log errors but continue