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 "encoding/json"
6 "errors"
7 "os"
8 "strings"
9
10 "github.com/carlosframework/platform/internal/hostwrite"
11 "github.com/carlosframework/platform/internal/release"
12 "github.com/carlosframework/platform/internal/rootstate"
13)
14
15func recoverRootRestarts(ctx context.Context, sys unitSystem, optDir string, getenv func(string) string, checks rootRestartChecks, writes hostwrite.Config) error {
16 legacy, err := checks.allowLegacy()
17 if err != nil {
18 return err
19 }
20 if legacy {
21 checks.noteLegacy()
22 // D3 still records root health without a census. Preparing its
23 // public parent must not create or recover bound restart state.
24 return rootstate.EnsurePublicDir(release.RootHealthyDir)
25 }
26 if checks.attempts == nil || checks.prepareRollback == nil {
27 return os.ErrPermission
28 }
29 s, err := checks.attempts()
30 if err != nil {
31 return err
32 }
33 defer s.Close()
34 names, err := s.Names()
35 if err != nil {
36 return err
37 }
38 var failures []error
39 for _, name := range names {
40 if !strings.HasSuffix(name, ".attempt") {
41 continue
42 }
43 body, err := s.Read(name)
44 var a rollbackAttempt
45 if err != nil || json.Unmarshal(body, &a) != nil || name != rollbackJournalKey(a.Host) {
46 failures = append(failures, errors.Join(err, os.ErrPermission))
47 continue
48 }
49 if a.Settled && a.History == nil {
50 if err := removeRollbackCapabilities(s, a.Host); err != nil {
51 failures = append(failures, err)
52 }
53 continue
54 }
55 if err := resumeRootRestart(ctx, sys, optDir, getenv, checks, writes, s, a); err != nil {
56 failures = append(failures, err)
57 }
58 }
59 return errors.Join(failures...)
60}
61
62func resumeRootRestart(ctx context.Context, sys unitSystem, optDir string, getenv func(string) string, checks rootRestartChecks, writes hostwrite.Config, s rootstate.Store, expected rollbackAttempt) (result error) {
63 unlock, err := s.Lock(ctx, rollbackHostKey(expected.Host)+".lock")
64 if err != nil {
65 return err
66 }
67 defer unlock()
68 current, err := readRollbackAttempt(s, expected.Host)
69 if err != nil || current.ID != expected.ID {
70 return errors.Join(err, os.ErrPermission)
71 }
72 if current.History != nil {
73 return resumeHistoryRestart(ctx, sys, optDir, getenv, checks, writes, s, current)
74 }
75 if current.Settled {
76 return nil
77 }
78 a, b, err := loadRollback(s, expected.Host, checks.now())
79 if a.ID != expected.ID {
80 return errors.Join(err, os.ErrPermission)
81 }
82 defer func() { result = errors.Join(result, settleRollbackAttempt(s, a)) }()
83 if err != nil {
84 return err
85 }
86 w, err := writes.Select(ctx, a.Host, "release")
87 if err != nil {
88 return err
89 }
90 checks.identity = a.Identity
91 checks.effect = func(fn func() error) error { return w.With(ctx, a.Identity, fn) }
92 err = checks.effect(func() error {
93 var err error
94 b, err = checks.prepareRollback(ctx, b)
95 if err != nil {
96 return err
97 }
98 p, err := sys.Show(b.Route.Unit)
99 if err != nil {
100 return err
101 }
102 if p.ActiveState == "inactive" {
103 return errUnitStopped
104 }
105 live, err := checks.live(optDir, a.Host)
106 old := release.VersionPath(optDir, b.Route.Account, b.Version, b.Route.App)
107 if err != nil || (live != a.Target && live != old) {
108 return os.ErrPermission
109 }
110 if !a.Recovering && !a.Failed && p.ActiveState == "active" && p.MainPID > 0 && live == a.Target {
111 checks.verifyOnly = true
112 checks.targetDigest = a.TargetDigest
113 return nil
114 }
115 if a.Recovering && p.ActiveState == "active" && p.MainPID > 0 && live == old {
116 checks.verifyOnly = true
117 } else if p.ActiveState != "failed" && p.MainPID != 0 && !a.Failed {
118 return os.ErrPermission
119 }
120 a.Recovering = true
121 if err := writeRollbackState(s, rollbackJournalKey(a.Host), a); err != nil {
122 return err
123 }
124 if live != old {
125 return release.PointLive(optDir, a.Host, b.Route.Account, b.Version, b.Route.App)
126 }
127 return nil
128 })
129 if err != nil {
130 return err
131 }
132 if checks.verifyOnly && !a.Recovering {
133 b, err = checks.host(ctx, a.Host)
134 if err != nil {
135 return err
136 }
137 return restartBoundUnit(sys, optDir, getenv, b, false, nil, checks)
138 }
139 b, err = checks.rollback(ctx, b)
140 if err != nil {
141 return err
142 }
143 return restartBoundUnit(sys, optDir, getenv, b, true, nil, checks)
144}
145