carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "io"
9 "os"
10 "path/filepath"
11 "time"
12
13 "github.com/carlosframework/platform/internal/rootstate"
14)
15
16// Public history is separate from the private claim journals. Agents may read
17// these records, but only root can create or replace them.
18const RootHealthyDir = "/var/lib/carlos-release"
19
20func rootHealthyStore(acct, app string, create bool) (rootstate.Store, error) {
21 if acct == "" {
22 acct = OpsAccount
23 }
24 if !ValidAccount(acct) || !ValidApp(app) {
25 return nil, os.ErrPermission
26 }
27 return rootstate.Open(filepath.Join(RootHealthyDir, "healthy", acct), create, true)
28}
29
30func decodeRootHealthy(raw []byte) ([]HealthyBuild, error) {
31 var f healthyFile
32 d := json.NewDecoder(bytes.NewReader(raw))
33 d.DisallowUnknownFields()
34 if err := d.Decode(&f); err != nil {
35 return nil, err
36 }
37 if d.Decode(new(any)) != io.EOF || f.Schema != 1 || len(f.Builds) > healthyMax {
38 return nil, os.ErrPermission
39 }
40 for _, b := range f.Builds {
41 if !sha256Re.MatchString(b.SHA256) || b.At.IsZero() || (b.How != "live" && b.How != "settle") {
42 return nil, os.ErrPermission
43 }
44 }
45 return f.Builds, nil
46}
47
48func readRootHealthy(acct, app string) []HealthyBuild {
49 d, err := rootHealthyStore(acct, app, false)
50 if err != nil {
51 return nil
52 }
53 defer d.Close()
54 raw, err := d.Read(app + ".json")
55 if err != nil {
56 return nil
57 }
58 b, err := decodeRootHealthy(raw)
59 if err != nil {
60 return nil
61 }
62 return b
63}
64
65func recordRootHealthy(acct, app string, b HealthyBuild) error {
66 if !sha256Re.MatchString(b.SHA256) || b.At.IsZero() || (b.How != "live" && b.How != "settle") {
67 return os.ErrPermission
68 }
69 d, err := rootHealthyStore(acct, app, true)
70 if err != nil {
71 return err
72 }
73 defer d.Close()
74 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
75 defer cancel()
76 unlock, err := d.Lock(ctx, app+".lock")
77 if err != nil {
78 return err
79 }
80 defer unlock()
81 return addRootHealthy(d, app, b)
82}
83
84func addRootHealthy(d rootstate.Store, app string, b HealthyBuild) error {
85 var have []HealthyBuild
86 raw, err := d.Read(app + ".json")
87 if err == nil {
88 have, err = decodeRootHealthy(raw)
89 }
90 if err != nil && !errors.Is(err, os.ErrNotExist) {
91 return err
92 }
93 for _, h := range have {
94 if h.SHA256 == b.SHA256 {
95 // A prior rename may be visible despite a failed parent fsync.
96 // Rewrite the exact original evidence through the durable store.
97 return d.Write(app+".json", raw)
98 }
99 }
100 have = append([]HealthyBuild{b}, have...)
101 if len(have) > healthyMax {
102 have = have[:healthyMax]
103 }
104 body, err := json.Marshal(healthyFile{Schema: 1, Builds: have})
105 if err != nil {
106 return err
107 }
108 return d.Write(app+".json", append(body, '\n'))
109}
110