90 lines
1.8 KiB
Go
90 lines
1.8 KiB
Go
package cluster
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ChangeRecord struct {
|
|
At time.Time `json:"at"`
|
|
Action string `json:"action"`
|
|
Target string `json:"target"`
|
|
Details string `json:"details,omitempty"`
|
|
}
|
|
|
|
func (s *Service) changeHistoryPath() string {
|
|
return filepath.Join(s.DataDir(), "history", "changes.log")
|
|
}
|
|
|
|
func (s *Service) AppendChange(action, target, details string) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
rec := ChangeRecord{
|
|
At: s.now().UTC(),
|
|
Action: strings.TrimSpace(action),
|
|
Target: strings.TrimSpace(target),
|
|
Details: strings.TrimSpace(details),
|
|
}
|
|
if rec.Action == "" {
|
|
return nil
|
|
}
|
|
path := s.changeHistoryPath()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
|
return err
|
|
}
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
b, err := json.Marshal(rec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(append(b, '\n'))
|
|
return err
|
|
}
|
|
|
|
func (s *Service) ListChanges(limit int) ([]ChangeRecord, error) {
|
|
path := s.changeHistoryPath()
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
capHint := 32
|
|
if limit > capHint {
|
|
capHint = limit
|
|
}
|
|
items := make([]ChangeRecord, 0, capHint)
|
|
sc := bufio.NewScanner(f)
|
|
sc.Buffer(make([]byte, 0, 64*1024), 2*1024*1024)
|
|
for sc.Scan() {
|
|
line := strings.TrimSpace(sc.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
var rec ChangeRecord
|
|
if err := json.Unmarshal([]byte(line), &rec); err != nil {
|
|
continue
|
|
}
|
|
items = append(items, rec)
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return nil, fmt.Errorf("scan change history: %w", err)
|
|
}
|
|
if limit > 0 && len(items) > limit {
|
|
items = items[len(items)-limit:]
|
|
}
|
|
return items, nil
|
|
}
|