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 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "io"
10 "os"
11 "path/filepath"
12 "reflect"
13
14 "github.com/carlosframework/platform/internal/activator"
15 "github.com/carlosframework/platform/internal/hostwrite"
16 "github.com/carlosframework/platform/internal/registry"
17 "github.com/carlosframework/platform/internal/release"
18)
19
20func (n namedUnitRecovery) recover(ctx context.Context, host string, c rootRestartChecks, getenv func(string) string, reg *registry.Registry) (bool, error) {
21 if rt, err := reg.Get(host); err == nil && rt != nil && (rt.Unit == "" || rt.Unit == activator.UnitName(host)) {
22 return false, nil
23 }
24 c.preserveOutcome = true
25 n.history, n.attempts = c.history, c.attempts
26 legacy, err := c.allowLegacy()
27 if err != nil {
28 return true, err
29 }
30 if legacy {
31 return false, nil
32 }
33 if n.source == nil || n.readLive == nil {
34 return true, os.ErrPermission
35 }
36 record, _, err := n.source.findRecord(ctx, host)
37 if err != nil {
38 return true, err
39 }
40 if record.Unit == "" {
41 return false, nil
42 }
43 if handled, err := n.repair(ctx, host); handled || err != nil {
44 return handled, err
45 }
46 rec, b, props, err := n.source.unitBinding(ctx, host)
47 if err != nil {
48 return true, err
49 }
50 if props.ActiveState != "active" || props.MainPID <= 0 {
51 return true, os.ErrPermission
52 }
53 old, err := n.source.forHost(ctx, host)
54 if err != nil || old.Source != b.Source || !old.authorizedRecovery() {
55 return true, errors.Join(err, os.ErrPermission)
56 }
57 live := release.LiveLink(n.source.optDir, host)
58 before, err := n.readLive(live)
59 if err != nil || before != release.VersionPath(n.source.optDir, rec.Account, old.Version, rec.App) {
60 return true, errors.Join(err, os.ErrPermission)
61 }
62 pointer, manifest, err := release.StageSignedHead(ctx, n.source.store, n.pub, n.ratchet, n.source.optDir, b.Route)
63 if err != nil {
64 return true, err
65 }
66 if rec.Pin != "" && rec.Pin != pointer.Version {
67 return true, os.ErrPermission
68 }
69 target := release.VersionPath(n.source.optDir, rec.Account, pointer.Version, rec.App)
70 digest := manifest.Artifacts[release.AdoptPlatform()].SHA256
71 work, err := n.writes.Select(ctx, host, "release")
72 if err != nil {
73 return true, err
74 }
75 id := hostwrite.Identity{}
76 if n.writes.Enabled {
77 raw, err := json.Marshal(manifest)
78 if err != nil {
79 return true, err
80 }
81 sum := sha256.Sum256(raw)
82 id.Release = "sha256:" + hex.EncodeToString(sum[:])
83 }
84 effect := func(fn func() error) error { return work.With(ctx, id, fn) }
85 validate := func() error {
86 current, err := n.source.forHost(ctx, host)
87 if err != nil {
88 return err
89 }
90 if current.Source != old.Source || current.Version != old.Version || current.Digest != old.Digest || current.PID != old.PID || current.Start != old.Start || current.Invocation != old.Invocation {
91 return os.ErrPermission
92 }
93 fresh, err := release.Channel(ctx, n.source.store, n.pub, rec.Account, rec.App, b.Route.Channel)
94 if err != nil {
95 return err
96 }
97 if !reflect.DeepEqual(fresh, pointer) {
98 return os.ErrPermission
99 }
100 if err := n.checkHistory(b, pointer); err != nil {
101 return err
102 }
103 still, err := n.readLive(live)
104 if err != nil || still != before {
105 return os.ErrPermission
106 }
107 f, err := n.source.openFile(target)
108 if err != nil {
109 return err
110 }
111 hash := sha256.New()
112 _, readErr := io.Copy(hash, f)
113 f.Close()
114 if readErr != nil {
115 return readErr
116 }
117 if hex.EncodeToString(hash.Sum(nil)) != digest {
118 return os.ErrPermission
119 }
120 return nil
121 }
122 if before == target {
123 c.effect, c.verifyOnly = effect, true
124 if err := effect(validate); err != nil {
125 return true, err
126 }
127 if err := restartBoundUnit(n.source.sys, n.source.optDir, getenv, old, false, nil, c); err != nil {
128 return true, err
129 }
130 return true, effect(func() error {
131 if err := validate(); err != nil {
132 return err
133 }
134 current, err := reg.Get(host)
135 if err != nil || current == nil {
136 return errors.Join(err, os.ErrPermission)
137 }
138 if current.Version == pointer.Version {
139 return nil
140 }
141 return reg.SetVersions(host, pointer.Version, current.Version)
142 })
143 }
144 if c.attempts == nil || c.now == nil {
145 return true, os.ErrPermission
146 }
147 s, err := c.attempts()
148 if err != nil {
149 return true, err
150 }
151 defer s.Close()
152 unlock, err := s.Lock(ctx, rollbackHostKey(host)+".lock")
153 if err != nil {
154 return true, err
155 }
156 defer unlock()
157 var superseded *rollbackAttempt
158 if prior, err := readRollbackAttempt(s, host); err == nil {
159 sameTarget := prior.History != nil && prior.History.Source == old.Source && prior.History.Version == pointer.Version && prior.History.SHA256 == digest && prior.History.PromotedAt.Equal(pointer.PromotedAt)
160 if sameTarget && (!prior.Settled || prior.Failed) {
161 return true, os.ErrPermission
162 }
163 if !prior.Settled {
164 superseded = &prior
165 }
166 } else if !errors.Is(err, os.ErrNotExist) {
167 return true, err
168 }
169 var attempt rollbackAttempt
170 err = effect(func() error {
171 if err := validate(); err != nil {
172 return err
173 }
174 if superseded != nil {
175 if err := settleRollbackAttempt(s, *superseded); err != nil {
176 return err
177 }
178 if superseded.Independent {
179 superseded.Settled = true
180 _ = c.settleHistory(ctx, *superseded, superseded.Recovering)
181 }
182 }
183 var err error
184 attempt, err = captureRollback(s, old, c.now(), unitSettleTimeout(getenv), target, id, historyRollbackLink{
185 Binding: release.HistoryBinding{Host: host, Account: rec.Account, App: rec.App},
186 Attempt: release.HistoryAttempt{ID: "signed-recovery", Source: old.Source, Version: pointer.Version, SHA256: digest, Platform: release.AdoptPlatform(), Started: c.now().UTC(), PromotedAt: pointer.PromotedAt}, Independent: true,
187 })
188 if err != nil {
189 return err
190 }
191 if err := release.PointLive(n.source.optDir, host, rec.Account, pointer.Version, rec.App); err != nil {
192 return err
193 }
194 dir, err := os.Open(filepath.Dir(live))
195 if err != nil {
196 return err
197 }
198 defer dir.Close()
199 return dir.Sync()
200 })
201 if err != nil {
202 return true, err
203 }
204 c.identity, c.effect, c.targetDigest = id, effect, digest
205 if err := restartBoundUnit(n.source.sys, n.source.optDir, getenv, old, false, nil, c); err != nil {
206 attempt.Failed = true
207 if writeErr := writeRollbackState(s, rollbackJournalKey(host), attempt); writeErr != nil {
208 return true, errors.Join(err, writeErr)
209 }
210 return true, errors.Join(err, resumeHistoryRestart(ctx, n.source.sys, n.source.optDir, getenv, c, n.writes, s, attempt))
211 }
212 attempt.Verified = true
213 if err := settleRollbackAttempt(s, attempt); err != nil {
214 return true, err
215 }
216 attempt.Settled = true
217 if err := c.settleHistory(ctx, attempt, false); err != nil {
218 return true, err
219 }
220 return true, effect(func() error {
221 current, err := c.host(ctx, host)
222 if err != nil || current.Source != old.Source || current.Version != pointer.Version || current.Digest != digest {
223 return errors.Join(err, os.ErrPermission)
224 }
225 return reg.SetVersions(host, pointer.Version, old.Version)
226 })
227}
228