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 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "io"
9 "net"
10 "os"
11 "path/filepath"
12 "strconv"
13 "strings"
14
15 "github.com/carlosframework/platform/internal/hostguard"
16 "github.com/carlosframework/platform/internal/instancectl"
17 "github.com/carlosframework/platform/internal/provision"
18 "github.com/carlosframework/platform/internal/registry"
19 "github.com/carlosframework/platform/internal/release"
20 "github.com/carlosframework/platform/internal/rootstate"
21)
22
23type claimUnitShape struct {
24 Host, Account, App, Kind, Unit, Backing, Socket, Addr, DBPath string
25 Program string `json:",omitempty"`
26 PredecessorDigest string `json:",omitempty"`
27}
28
29type claimUnitBinding struct {
30 Schema int
31 Owner hostguard.RootInstallation
32 Shape claimUnitShape
33 Retiring, Retired bool
34}
35
36type claimUnitBindings struct {
37 admit func() error
38 handover func(context.Context, claimUnitBinding, claimUnitBinding) error
39 state rootstate.Store
40 installation func(context.Context, string) (hostguard.RootInstallation, error)
41 canonical func(context.Context, string) (claimUnitShape, error)
42}
43
44func (s claimUnitShape) valid() bool {
45 if (s.Program != "" && !claimHexDigest(s.Program)) || (s.PredecessorDigest != "" && !claimHexDigest(s.PredecessorDigest)) {
46 return false
47 }
48 if !provision.ValidHostShape(s.Host) || !release.ValidAccount(s.Account) || !release.ValidApp(s.App) || s.Kind != "instance" || !instancectl.ValidBoundClaimUnit(s.Unit) {
49 return false
50 }
51 if s.Backing != "" && (s.Backing != registry.BackingUnit || s.Unit != "carlos-app@"+s.Host+".service") {
52 return false
53 }
54 if (s.Socket == "") == (s.Addr == "") {
55 return false
56 }
57 for _, p := range []string{s.Socket, s.DBPath} {
58 if p != "" && (!filepath.IsAbs(p) || filepath.Clean(p) != p || p == "/" || strings.ContainsAny(p, "\x00\r\n")) {
59 return false
60 }
61 }
62 if s.Addr != "" {
63 host, port, err := net.SplitHostPort(s.Addr)
64 n, e := strconv.Atoi(port)
65 if err != nil || e != nil || n < 1 || n > 65535 || net.ParseIP(host) == nil || !net.ParseIP(host).IsLoopback() {
66 return false
67 }
68 }
69 return true
70}
71
72func (s claimUnitShape) route() registry.Route {
73 unit := s.Unit
74 if s.Backing == registry.BackingUnit {
75 unit = ""
76 }
77 return registry.Route{Host: s.Host, Account: s.Account, App: s.App, Kind: s.Kind, Unit: unit, Backing: s.Backing, Socket: s.Socket, Addr: s.Addr, DBPath: s.DBPath}
78}
79
80func (s claimUnitShape) matches(r registry.Route) bool {
81 return s.Host == r.Host && s.Account == r.Account && s.App == r.App && s.Kind == r.Kind && s.route().Unit == r.Unit && s.Backing == r.Backing && s.Socket == r.Socket && s.Addr == r.Addr && s.DBPath == r.DBPath && r.Addrs == "" && r.Tunnel == "" && r.Seed == "" && !r.Replica
82}
83
84func unitBindingOwner(in hostguard.RootInstallation) hostguard.RootInstallation {
85 in.Artifacts = hostguard.InstallationArtifacts{}
86 in.Registration = hostguard.InstallationRegistration{}
87 in.InventoryRevision = 0
88 in.Empty = false
89 return in
90}
91
92func (b claimUnitBinding) valid() bool {
93 in := b.Owner
94 return b.Schema == 1 && b.Shape.valid() && in == unitBindingOwner(in) && in.Host == b.Shape.Host && in.Account == b.Shape.Account && in.App == b.Shape.App && in.Incarnation != "" && in.Stamp > 0 && in.Migration > 0 && in.Record != "" && in.Box != "" && in.Enrollment != "" && (!b.Retired || b.Retiring)
95}
96
97func (s *claimUnitBindings) read(host string) (claimUnitBinding, error) {
98 var b claimUnitBinding
99 if s == nil || s.state == nil || !provision.ValidHostShape(host) {
100 return b, os.ErrPermission
101 }
102 raw, err := s.state.Read(host + ".json")
103 if err != nil {
104 return b, err
105 }
106 dec := json.NewDecoder(bytes.NewReader(raw))
107 dec.DisallowUnknownFields()
108 if len(raw) > rootstate.MaxRecord || dec.Decode(&b) != nil || dec.Decode(new(any)) != io.EOF || !b.valid() || b.Owner.Host != host {
109 return claimUnitBinding{}, os.ErrPermission
110 }
111 return b, nil
112}
113
114func (s *claimUnitBindings) write(b claimUnitBinding) error {
115 if !b.valid() {
116 return os.ErrPermission
117 }
118 raw, err := json.Marshal(b)
119 if err != nil {
120 return err
121 }
122 return s.state.Write(b.Owner.Host+".json", raw)
123}
124
125func (s *claimUnitBindings) current(ctx context.Context, host string) (claimUnitBinding, error) {
126 var b claimUnitBinding
127 if s == nil || s.installation == nil || s.canonical == nil {
128 return b, os.ErrPermission
129 }
130 in, err := s.installation(ctx, host)
131 if err != nil {
132 return b, err
133 }
134 shape, err := s.canonical(ctx, host)
135 if err != nil {
136 return b, err
137 }
138 b = claimUnitBinding{Schema: 1, Owner: unitBindingOwner(in), Shape: shape}
139 if in.InventoryRevision == 0 || b.Owner.Host != host || !b.valid() {
140 return claimUnitBinding{}, os.ErrPermission
141 }
142 return b, nil
143}
144
145func (s *claimUnitBindings) establish(ctx context.Context, host string, resolved ...claimUnitBinding) (claimUnitBinding, error) {
146 if s.admit != nil {
147 if err := s.admit(); err != nil {
148 return claimUnitBinding{}, err
149 }
150 }
151 if s == nil || s.state == nil {
152 return claimUnitBinding{}, os.ErrPermission
153 }
154 unlock, err := s.state.Lock(ctx, "bindings")
155 if err != nil {
156 return claimUnitBinding{}, err
157 }
158 defer unlock()
159 var next claimUnitBinding
160 if len(resolved) == 1 {
161 next = resolved[0]
162 } else {
163 next, err = s.current(ctx, host)
164 }
165 if err != nil {
166 return next, err
167 }
168 if !next.valid() || next.Owner.Host != host {
169 return next, os.ErrPermission
170 }
171 names, err := s.state.Names()
172 if err != nil {
173 return claimUnitBinding{}, err
174 }
175 for _, name := range names {
176 if name == "bindings" || name == host+".json" {
177 continue
178 }
179 otherHost, ok := strings.CutSuffix(name, ".json")
180 if !ok {
181 return claimUnitBinding{}, os.ErrPermission
182 }
183 other, err := s.read(otherHost)
184 if err != nil {
185 continue
186 }
187 if other.Shape.Unit == next.Shape.Unit {
188 return claimUnitBinding{}, os.ErrPermission
189 }
190 }
191 old, err := s.read(host)
192 if err == nil {
193 if old != next {
194 if !old.Retired || old.Shape != next.Shape || old.Owner == next.Owner || s.handover == nil {
195 return claimUnitBinding{}, os.ErrPermission
196 }
197 if err := s.handover(ctx, old, next); err != nil {
198 return claimUnitBinding{}, err
199 }
200 }
201 } else if !errors.Is(err, os.ErrNotExist) {
202 return claimUnitBinding{}, err
203 }
204 // Replay syncs the original binding before relying on an earlier rename.
205 return next, s.write(next)
206}
207
208func (s *claimUnitBindings) check(ctx context.Context, host string, candidate *registry.Route) (claimUnitBinding, error) {
209 saved, err := s.read(host)
210 if err != nil {
211 return saved, err
212 }
213 current, err := s.current(ctx, host)
214 if err != nil {
215 return claimUnitBinding{}, err
216 }
217 if saved.Retiring || saved.Retired || saved != current || (candidate != nil && !saved.Shape.matches(*candidate)) {
218 return claimUnitBinding{}, os.ErrPermission
219 }
220 return saved, nil
221}
222
223func (s *claimUnitBindings) retire(ctx context.Context, captured claimUnitBinding, verify func(claimUnitBinding) error) error {
224 if s == nil || s.state == nil || !captured.valid() || captured.Retiring || verify == nil {
225 return os.ErrPermission
226 }
227 unlock, err := s.state.Lock(ctx, "bindings")
228 if err != nil {
229 return err
230 }
231 defer unlock()
232 old, err := s.read(captured.Owner.Host)
233 if err != nil {
234 return err
235 }
236 if old.Owner != captured.Owner || old.Shape != captured.Shape {
237 return os.ErrPermission
238 }
239 if old.Retired {
240 return s.write(old)
241 }
242 old.Retiring = true
243 if err := s.write(old); err != nil {
244 return err
245 }
246 if err := verify(old); err != nil {
247 return err
248 }
249 old.Retired = true
250 return s.write(old)
251}
252