carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "log"
6 "strings"
7 "time"
8
9 "github.com/carlosframework/platform/internal/release"
10)
11
12// evidenceFunc answers the empty-bucket tripwire (temporary static sites spec
13// §2): does the apps/ listing hold at least one key that is not a trash
14// marker? Every branch that reasons from absence to "the purge finished" asks
15// it first, and an empty or failed listing is "no evidence".
16type evidenceFunc func(ctx context.Context) bool
17
18// isTrashMarkerKey reports whether key is some app's trash marker,
19// apps/<sqid>/<app>/deleted.json exactly.
20func isTrashMarkerKey(key string) bool {
21 parts := strings.SplitN(key, "/", 4)
22 return len(parts) == 4 && parts[0] == "apps" && key == release.AppTombstoneKey(parts[1], parts[2])
23}
24
25// bucketEvidence is the tripwire over a listing already taken. Markers do not
26// count: they are the one thing this feature writes into a bucket that holds
27// nothing else, so a wrong, empty bucket would otherwise vouch for itself
28// with the pass's own markers (astra 5.2).
29func bucketEvidence(keys []string) bool {
30 for _, key := range keys {
31 if !isTrashMarkerKey(key) {
32 return true
33 }
34 }
35 return false
36}
37
38// listEvidence lists apps/ once. listed is false when the listing failed;
39// evidence is the tripwire's answer, false for a failed listing too.
40func (s *Server) listEvidence(ctx context.Context) (keys []string, listed, evidence bool) {
41 if s.cfg.Releases == nil {
42 return nil, false, false
43 }
44 keys, err := s.cfg.Releases.List(ctx, "apps/")
45 if err != nil {
46 log.Printf("console: list apps/ for the empty-bucket tripwire: %v", err)
47 return nil, false, false
48 }
49 return keys, true, bucketEvidence(keys)
50}
51
52// doorEvidence is the evidenceFunc a door passes to settleApp: it lists only
53// if settleApp actually reaches an absence branch.
54func (s *Server) doorEvidence(ctx context.Context) bool {
55 _, _, ev := s.listEvidence(ctx)
56 return ev
57}
58
59// settleApp brings one app's row into line with its trash marker before any
60// transition acts (spec §2 "Every transition settles first", §6). It is
61// MaterializeApps for one app, plus the restoring cases. The CALLER holds
62// s.lockApp(sqid, app), and re-reads the row afterwards: what it listed
63// before the lock is only a hint.
64//
65// marker, row live -> trashed with the marker's clock (and the
66// 3-day window if temporary, TrashAppRow)
67// marker, row restoring -> restore step 4 failed: restoring = 0, the
68// app stays trashed with its deadline
69// no marker, row trashed, keys -> restored elsewhere, or step 5 failed:
70// FinishRestoreRow
71// no marker, row trashed, none -> purged elsewhere: PurgeAppRow, only if
72// the tripwire finds evidence
73// no marker, row live -> nothing
74//
75// An unreadable marker is an error and changes nothing.
76func (s *Server) settleApp(ctx context.Context, accountID int64, sqid, app string, now time.Time, evidence evidenceFunc) error {
77 if s.cfg.Releases == nil {
78 return nil
79 }
80 life, err := s.store.AppLife(accountID, app)
81 if err != nil || life == nil {
82 return err
83 }
84 tomb, err := release.ReadAppTombstone(ctx, s.cfg.Releases, sqid, app)
85 if err != nil {
86 return err
87 }
88 if tomb != nil {
89 switch {
90 case !life.Trashed():
91 if err := s.store.TrashAppRowContext(ctx, accountID, app, tomb.DeletedAt); err != nil {
92 return err
93 }
94 log.Printf("console: %s/%s is in the trash in the bucket; marking it trashed here", sqid, app)
95 case life.Restoring:
96 if _, err := s.store.SetRestoringContext(ctx, accountID, app, false); err != nil {
97 return err
98 }
99 log.Printf("console: %s/%s: a restore did not remove the trash marker; it stays in the trash", sqid, app)
100 }
101 return nil
102 }
103 if !life.Trashed() {
104 return nil
105 }
106 keys, err := s.cfg.Releases.List(ctx, release.AppPrefix(sqid, app))
107 if err != nil {
108 return err
109 }
110 if len(keys) > 0 {
111 if err := s.store.FinishRestoreRow(accountID, app, now); err != nil {
112 return err
113 }
114 log.Printf("console: %s/%s has no trash marker and still has objects; restoring it here", sqid, app)
115 return nil
116 }
117 if !evidence(ctx) {
118 log.Printf("console: %s/%s has no trash marker and no objects, but the apps/ listing holds nothing else either; "+
119 "leaving the row. Is CARLOS_DEPLOYMENT_BUCKET pointing at the right bucket?", sqid, app)
120 return nil
121 }
122 if err := s.store.PurgeAppRow(accountID, app); err != nil {
123 return err
124 }
125 log.Printf("console: %s/%s is gone from the bucket; releasing the name here", sqid, app)
126 return nil
127}
128