Files
deploy-felhom-compose/controller/internal/selfupdate/state.go
T
admin 8e61cd7ec4 feat: comprehensive INFO/WARN/ERROR logging across all controller modules
Add structured operational logging at INFO, WARN, and ERROR levels to
every controller module. Standardize custom prefixes ([GEO], [SCHED],
[SYNC]) to use [INFO/WARN/ERROR] [module] format. Fix misleveled logs
(WARN->ERROR for data loss scenarios, WARN->INFO for routine operations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:58:27 +01:00

74 lines
2.2 KiB
Go

package selfupdate
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
)
const stateFileName = "update-state.json"
// UpdateState tracks the last update attempt. Persisted to disk as audit log.
type UpdateState struct {
Status string `json:"status"` // "pending", "success", "failed"
PreviousVersion string `json:"previous_version"`
PreviousImage string `json:"previous_image"`
TargetVersion string `json:"target_version"`
TargetImage string `json:"target_image"`
InitiatedAt string `json:"initiated_at"` // RFC3339
InitiatedBy string `json:"initiated_by"` // "manual" or "auto"
CompletedAt string `json:"completed_at,omitempty"`
Error string `json:"error,omitempty"`
}
// LoadState reads the update state file. Returns nil, nil if file doesn't exist.
func LoadState(dataDir string) (*UpdateState, error) {
path := filepath.Join(dataDir, stateFileName)
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("reading state file: %w", err)
}
var state UpdateState
if err := json.Unmarshal(data, &state); err != nil {
return nil, fmt.Errorf("parsing state file: %w", err)
}
return &state, nil
}
// SaveState writes the state file atomically (write to .tmp, then rename).
func SaveState(dataDir string, state *UpdateState) error {
path := filepath.Join(dataDir, stateFileName)
tmpPath := path + ".tmp"
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return fmt.Errorf("marshaling state: %w", err)
}
if err := os.WriteFile(tmpPath, data, 0644); err != nil {
return fmt.Errorf("writing temp state file: %w", err)
}
if err := os.Rename(tmpPath, path); err != nil {
return fmt.Errorf("renaming state file: %w", err)
}
return nil
}
// ClearState removes the state file. Used for cleanup.
func ClearState(dataDir string, logger *log.Logger) {
path := filepath.Join(dataDir, stateFileName)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
logger.Printf("[WARN] Failed to clear update state file: %v", err)
return
}
logger.Printf("[INFO] [selfupdate] Update state cleared")
}