package store import ( "encoding/json" "errors" "fmt" "io/fs" "os" "path/filepath" "sort" "time" ) // HandedOverName is the file, beside the node's state, of the directories a person handed to the mesh at the // machine (novox/hq issue 339). Its own file rather than a field of the state: the engine saves its state at // the end of every apply, from what it loaded at the start, and a hand-over written into the state during an // apply would be lost to that save. const HandedOverName = "handed-over.json" // HandedOverPath is that file beside the state at statePath. func HandedOverPath(statePath string) string { return filepath.Join(filepath.Dir(statePath), HandedOverName) } // HandOver is one directory a person handed to the mesh. type HandOver struct { // ID is the resource that was used as found there: the hand-over is for it alone, and lapses if another // resource later points at the path. ID string `json:"id"` Path string `json:"path"` At time.Time `json:"at"` // By is who ran the command, as the machine named them. By string `json:"by,omitempty"` } // LoadHandedOver reads every hand-over. None is an empty list; a file that cannot be read is an error. func LoadHandedOver(statePath string) ([]HandOver, error) { raw, err := os.ReadFile(HandedOverPath(statePath)) if errors.Is(err, fs.ErrNotExist) { return nil, nil } if err != nil { return nil, fmt.Errorf("reading the directories handed to the mesh: %w", err) } var out []HandOver if err := json.Unmarshal(raw, &out); err != nil { return nil, fmt.Errorf("the directories handed to the mesh (%s) are unreadable: %w", HandedOverPath(statePath), err) } return out, nil } // AddHandedOver records a hand-over, once per path, atomically. func AddHandedOver(statePath string, h HandOver) error { all, err := LoadHandedOver(statePath) if err != nil { return err } for _, had := range all { if had.Path == h.Path && had.ID == h.ID { return nil } } all = append(all, h) sort.Slice(all, func(a, b int) bool { return all[a].Path < all[b].Path }) raw, err := json.MarshalIndent(all, "", " ") if err != nil { return err } path := HandedOverPath(statePath) tmp, err := os.CreateTemp(filepath.Dir(path), ".handed-over-*.json") if err != nil { return err } defer os.Remove(tmp.Name()) if _, err := tmp.Write(append(raw, '\n')); 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) }