86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"os"
|
|
"reflect"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"git.cer.sh/axodouble/quptime/internal/config"
|
|
"git.cer.sh/axodouble/quptime/internal/transport"
|
|
)
|
|
|
|
// manualEditPollInterval is how often the daemon checks cluster.yaml's
|
|
// hash against the last value it wrote. Short enough that an operator
|
|
// `vim`-ing the file sees their change applied within a few seconds.
|
|
const manualEditPollInterval = 2 * time.Second
|
|
|
|
// watchManualEdits polls cluster.yaml. When the on-disk content
|
|
// diverges from what the daemon last wrote, the file is parsed and
|
|
// pushed through the master as a MutationReplaceConfig — so a
|
|
// hand-edit on any node ends up replicated everywhere.
|
|
//
|
|
// The poll uses sha256 of the file contents rather than mtime so we
|
|
// don't race against `os.Rename` from our own AtomicWrite or against
|
|
// editors that touch mtime without changing content.
|
|
func (d *Daemon) watchManualEdits(ctx context.Context) {
|
|
t := time.NewTicker(manualEditPollInterval)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
d.checkManualEdit(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *Daemon) checkManualEdit(ctx context.Context) {
|
|
raw, err := os.ReadFile(config.ClusterFilePath())
|
|
if err != nil {
|
|
// A missing file during early boot or temp-file races is fine;
|
|
// the next tick will re-read it.
|
|
return
|
|
}
|
|
sum := sha256.Sum256(raw)
|
|
if sum == d.cluster.LastSavedSum() {
|
|
return
|
|
}
|
|
|
|
var edited config.ClusterConfig
|
|
if err := yaml.Unmarshal(raw, &edited); err != nil {
|
|
d.logger.Printf("manual-edit: parse cluster.yaml: %v — ignoring", err)
|
|
// Pin the hash so we don't loop on a broken file. The operator
|
|
// must save a valid YAML for the next attempt.
|
|
d.cluster.SetLastSavedSum(sum)
|
|
return
|
|
}
|
|
|
|
current := d.cluster.Snapshot()
|
|
if reflect.DeepEqual(current.Peers, edited.Peers) &&
|
|
reflect.DeepEqual(current.Checks, edited.Checks) &&
|
|
reflect.DeepEqual(current.Alerts, edited.Alerts) {
|
|
// Only cosmetic (whitespace/comments) — accept it.
|
|
d.cluster.SetLastSavedSum(sum)
|
|
return
|
|
}
|
|
|
|
d.logger.Printf("manual-edit: cluster.yaml changed externally — replicating via master")
|
|
d.cluster.SetLastSavedSum(sum)
|
|
|
|
callCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
payload := &config.ClusterConfig{
|
|
Peers: edited.Peers,
|
|
Checks: edited.Checks,
|
|
Alerts: edited.Alerts,
|
|
}
|
|
if _, err := d.replicator.LocalMutate(callCtx, transport.MutationReplaceConfig, payload); err != nil {
|
|
d.logger.Printf("manual-edit: forward to master: %v", err)
|
|
}
|
|
}
|