| 1 | // Program history — the box half of D2 (spec §3.2). Every Go main package |
| 2 | // path this box has pointed live for a route, in the bake directory. A build |
| 3 | // whose program is in its route's history is never "the wrong program", which |
| 4 | // is what lets a deliberate A -> B change be rolled back to A. |
| 5 | package release |
| 6 | |
| 7 | import ( |
| 8 | "encoding/json" |
| 9 | "path/filepath" |
| 10 | "slices" |
| 11 | "strings" |
| 12 | |
| 13 | "github.com/carlosframework/platform/internal/safefile" |
| 14 | ) |
| 15 | |
| 16 | func programPath(bakeDir, host string) string { |
| 17 | return filepath.Join(bakeDir, "programs", host+".json") |
| 18 | } |
| 19 | |
| 20 | func ReadPrograms(bakeDir, host string) []string { |
| 21 | if strings.ContainsAny(host, `/\`) { |
| 22 | return nil |
| 23 | } |
| 24 | b, err := safefile.ReadFile(programPath(bakeDir, host)) |
| 25 | if err != nil { |
| 26 | return nil |
| 27 | } |
| 28 | var rec struct { |
| 29 | Programs []string `json:"programs"` |
| 30 | } |
| 31 | if json.Unmarshal(b, &rec) != nil { |
| 32 | return nil |
| 33 | } |
| 34 | return rec.Programs |
| 35 | } |
| 36 | |
| 37 | func RecordProgram(bakeDir, host, program string) error { |
| 38 | if program == "" || strings.ContainsAny(host, `/\`) { |
| 39 | return nil |
| 40 | } |
| 41 | have := ReadPrograms(bakeDir, host) |
| 42 | if slices.Contains(have, program) { |
| 43 | return nil |
| 44 | } |
| 45 | b, err := json.Marshal(struct { |
| 46 | Schema int `json:"schema"` |
| 47 | Programs []string `json:"programs"` |
| 48 | }{1, append(have, program)}) |
| 49 | if err != nil { |
| 50 | return err |
| 51 | } |
| 52 | p := programPath(bakeDir, host) |
| 53 | if err := safefile.MkdirAll(filepath.Dir(p), 0o755); err != nil { |
| 54 | return err |
| 55 | } |
| 56 | return safefile.WriteFile(p, b, 0o644) |
| 57 | } |
| 58 | |