package store import ( "errors" "os" "path/filepath" ) // The report of the last apply of a declaration from the mesh, kept until the mesh has taken it. // // **An apply the mesh never heard about is one it waits on for ever.** The controller's release // plan waits for a machine to report the exact declaration it was sent; a report lost between the // apply and the publish — a host standing aside for its successor, a crash, a power cut — leaves the // machine applied and the plan waiting until somebody pushes by hand (novox/hq issue 264). The // declaration itself is kept (declared.go), and re-applying it would say something new about the // machine; what was lost is what *that* apply did, so that is what is kept, exactly as it was made, // and said again once the host is linked. // // Opaque bytes here: the report is the link's word, and the store keeps it without reading it. // UnsaidName is where it lives, beside the state. const UnsaidName = "unsaid.json" // UnsaidPath is where the unsaid report lives, given where the state lives. func UnsaidPath(statePath string) string { return filepath.Join(filepath.Dir(statePath), UnsaidName) } // SaveUnsaid keeps a report the mesh has not taken yet, replacing whatever was kept before: only // the last apply's report is worth saying again, because the mesh compares against what it sent last. func SaveUnsaid(path string, report []byte) error { if len(report) == 0 { return errors.New("refusing to keep an empty report") } if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return err } tmp, err := os.CreateTemp(filepath.Dir(path), ".unsaid-*") if err != nil { return err } defer os.Remove(tmp.Name()) if err := tmp.Chmod(0o600); err != nil { tmp.Close() return err } if _, err := tmp.Write(report); err != nil { tmp.Close() return err } if err := tmp.Sync(); err != nil { tmp.Close() return err } if err := tmp.Close(); err != nil { return err } return os.Rename(tmp.Name(), path) } // ReadUnsaid is the report kept unsaid, or nil when there is none — the ordinary case. func ReadUnsaid(path string) ([]byte, error) { raw, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return nil, nil } return raw, err } // ClearUnsaid forgets the kept report, once the mesh has taken it. Absent is already clear. func ClearUnsaid(path string) error { if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return nil }