carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "log"
7 "os"
8
9 "github.com/carlosframework/platform/internal/hostwrite"
10 "github.com/carlosframework/platform/internal/release"
11 "github.com/carlosframework/platform/internal/rootstate"
12)
13
14type historyRollbackLink struct {
15 TargetDigest string
16 Binding release.HistoryBinding
17 Attempt release.HistoryAttempt
18 Independent bool
19}
20
21func linkRootHistory(h *release.RootHistoryPass, checks rootRestartChecks, optDir string, getenv func(string) string) {
22 h.Prepare = func(ctx context.Context, binding release.HistoryBinding, attempt release.HistoryAttempt, identity hostwrite.Identity) error {
23 b, err := checks.host(ctx, binding.Host)
24 if err != nil {
25 return err
26 }
27 if b.Source != attempt.Source || b.Route.Account != binding.Account || b.Route.App != binding.App {
28 return os.ErrPermission
29 }
30 pending, err := h.History.Pending(binding)
31 if err != nil || pending == nil || *pending != attempt {
32 return errors.Join(err, os.ErrPermission)
33 }
34 s, err := checks.attempts()
35 if err != nil {
36 return err
37 }
38 defer s.Close()
39 unlock, err := s.Lock(ctx, rollbackHostKey(binding.Host)+".lock")
40 if err != nil {
41 return err
42 }
43 defer unlock()
44 live, err := checks.live(optDir, binding.Host)
45 if err != nil || live != release.VersionPath(optDir, binding.Account, b.Version, binding.App) {
46 return errors.Join(err, os.ErrPermission)
47 }
48 _, err = captureRollback(s, b, checks.now(), unitSettleTimeout(getenv), release.VersionPath(optDir, binding.Account, attempt.Version, binding.App), identity, historyRollbackLink{Binding: binding, Attempt: attempt})
49 return err
50 }
51}
52
53func historyRestartAttempt(s rootstate.Store, b proofBinding, c rootRestartChecks, target string, getenv func(string) string) (rollbackAttempt, error) {
54 a, err := readRollbackAttempt(s, b.Route.Host)
55 if err != nil && !errors.Is(err, os.ErrNotExist) {
56 return a, err
57 }
58 if err == nil && a.History != nil && !a.Settled {
59 loaded, old, err := loadRollback(s, b.Route.Host, c.now())
60 if err != nil {
61 return a, err
62 }
63 if loaded.ID != a.ID || a.Identity != c.identity || a.Target != target || old.Source != b.Source || old.Version != b.Version || old.Digest != b.Digest || old.Invocation != b.Invocation || old.PID != b.PID || old.Start != b.Start || a.Failed || a.Recovering {
64 return a, os.ErrPermission
65 }
66 return a, nil
67 }
68 return captureRollback(s, b, c.now(), unitSettleTimeout(getenv), target, c.identity, historyRollbackLink{TargetDigest: c.targetDigest})
69}
70
71func (c rootRestartChecks) settleHistory(ctx context.Context, a rollbackAttempt, rollback bool) error {
72 if a.History == nil {
73 return nil
74 }
75 if a.Independent {
76 if c.history == nil {
77 return nil
78 }
79 h, err := c.history()
80 if err == nil {
81 defer h.History.Store.Close()
82 var outcome release.HistoryRecovery
83 outcome, err = recoveryHistoryOutcome(a)
84 if err == nil {
85 err = h.History.RetainRecovery(outcome)
86 if err == nil && h.Reconstruct != nil {
87 err = h.Reconstruct(ctx, outcome.Binding)
88 }
89 }
90 }
91 if err != nil {
92 log.Print(err)
93 }
94 return nil
95 }
96 if c.history == nil {
97 return os.ErrPermission
98 }
99 h, err := c.history()
100 if err != nil {
101 return err
102 }
103 defer h.History.Store.Close()
104 pending, err := h.History.Pending(a.HistoryBinding)
105 if err != nil {
106 return err
107 }
108 if pending == nil {
109 return nil
110 }
111 if *pending != *a.History {
112 return os.ErrPermission
113 }
114 b, err := c.host(ctx, a.Host)
115 if err != nil {
116 return err
117 }
118 if b.Source != a.History.Source || b.Route.Account != a.HistoryBinding.Account || b.Route.App != a.HistoryBinding.App {
119 return os.ErrPermission
120 }
121 if !rollback {
122 if b.Version != a.History.Version || b.Digest != a.History.SHA256 {
123 return os.ErrPermission
124 }
125 return h.CompleteAttempt(ctx, b.Route, *a.History)
126 }
127 if b.Version != a.Previous || b.Digest != a.PreviousDigest {
128 return os.ErrPermission
129 }
130 if _, err := h.Installed(ctx, b.Route, b.Version); err != nil {
131 return err
132 }
133 again, err := c.host(ctx, a.Host)
134 if err != nil || again.Source != b.Source || again.Version != b.Version || again.Digest != b.Digest || again.PID != b.PID || again.Start != b.Start || again.Invocation != b.Invocation {
135 return errors.Join(err, os.ErrPermission)
136 }
137 ref := release.Refusal{Host: a.Host, App: b.Route.App, Version: a.History.Version, SHA256: a.History.SHA256, Platform: a.History.Platform, PointerPromotedAt: a.History.PromotedAt, RefusedAt: c.now().UTC(), Kind: release.RefusalNotUp, Recovered: true, RestoredVersion: b.Version}
138 return h.History.Settle(a.HistoryBinding, a.History.ID, &ref)
139}
140
141func resumeHistoryRestart(ctx context.Context, sys unitSystem, optDir string, getenv func(string) string, checks rootRestartChecks, writes hostwrite.Config, s rootstate.Store, a rollbackAttempt) error {
142 checks.verifyOnly = false
143 if a.Settled {
144 if (a.Failed || a.Recovering) && !a.Verified {
145 return nil
146 }
147 return checks.settleHistory(ctx, a, a.Recovering)
148 }
149 legacy, err := checks.allowLegacy()
150 if err != nil {
151 return err
152 }
153 if legacy {
154 if a.Independent {
155 return checks.settleHistory(ctx, a, a.Recovering)
156 }
157 return nil
158 }
159 _, old, err := loadRollback(s, a.Host, checks.now())
160 if err != nil {
161 return err
162 }
163 w, err := writes.Select(ctx, a.Host, "release")
164 if err != nil {
165 return err
166 }
167 checks.identity = a.Identity
168 checks.effect = func(fn func() error) error { return w.With(ctx, a.Identity, fn) }
169 var forward bool
170 err = checks.effect(func() error {
171 var err error
172 old, err = checks.prepareRollback(ctx, old)
173 if err != nil {
174 return err
175 }
176 p, err := sys.Show(old.Route.Unit)
177 if err != nil {
178 return err
179 }
180 if p.ActiveState == "inactive" {
181 return errUnitStopped
182 }
183 live, err := checks.live(optDir, a.Host)
184 previous := release.VersionPath(optDir, old.Route.Account, old.Version, old.Route.App)
185 if err != nil || (live != previous && live != a.Target) {
186 return os.ErrPermission
187 }
188 if p.ActiveState == "active" && p.MainPID > 0 {
189 observed, err := checks.host(ctx, a.Host)
190 if err != nil && checks.recovered != nil {
191 observed, err = checks.recovered(ctx, old)
192 }
193 if err != nil {
194 return err
195 }
196 if observed.Source != old.Source {
197 return os.ErrPermission
198 }
199 if live == a.Target && observed.Version == a.History.Version && observed.Digest == a.History.SHA256 && !a.Failed && !a.Recovering {
200 forward, checks.verifyOnly = true, true
201 return nil
202 }
203 if observed.Version == old.Version && observed.Digest == old.Digest {
204 checks.verifyOnly = true
205 } else if !a.Failed || observed.Version != a.History.Version || observed.Digest != a.History.SHA256 {
206 return os.ErrPermission
207 }
208 } else if p.ActiveState != "failed" && p.MainPID != 0 && !a.Failed {
209 return os.ErrPermission
210 }
211 a.Recovering = true
212 if err := writeRollbackState(s, rollbackJournalKey(a.Host), a); err != nil {
213 return err
214 }
215 if live != previous {
216 return release.PointLive(optDir, a.Host, old.Route.Account, old.Version, old.Route.App)
217 }
218 return nil
219 })
220 if err != nil {
221 return err
222 }
223 checks.targetDigest = a.PreviousDigest
224 if forward {
225 checks.targetDigest = a.History.SHA256
226 }
227 if err := restartBoundUnit(sys, optDir, getenv, old, !forward, nil, checks); err != nil {
228 if forward {
229 a.Failed = true
230 if writeErr := writeRollbackState(s, rollbackJournalKey(a.Host), a); writeErr != nil {
231 return errors.Join(err, writeErr)
232 }
233 return resumeHistoryRestart(ctx, sys, optDir, getenv, checks, writes, s, a)
234 }
235 settledErr := settleRollbackAttempt(s, a)
236 if settledErr == nil && a.Independent {
237 a.Settled = true
238 _ = checks.settleHistory(ctx, a, !forward)
239 }
240 return errors.Join(err, settledErr)
241 }
242 a.Verified = true
243 if err := settleRollbackAttempt(s, a); err != nil {
244 return err
245 }
246 a.Settled = true
247 return checks.settleHistory(ctx, a, !forward)
248}
249