mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Own ConfigWatcher API token persistence
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user