mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-14 05:30:55 +00:00
Own ConfigWatcher API token persistence
This commit is contained in:
parent
ef288b250b
commit
bf30a094bb
3 changed files with 18 additions and 25 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue