carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "os"
8 "time"
9
10 "github.com/carlosframework/platform/internal/domains"
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/provision"
13 "github.com/carlosframework/platform/internal/release"
14)
15
16type productionCreateAuthority interface {
17 StartCreate(context.Context, hostclaim.Observed, Record, string, string) (hostclaim.Observed, error)
18 RecordEffects(context.Context, hostclaim.Observed, Record) error
19}
20
21// RecordEffects writes the ownership marker and the record as two effects
22// under the request's own registration. The runner comes from the
23// StartHandle the console put in ctx; without it (recovery, another
24// process) this refuses rather than impersonating the registration owner.
25func (a ProductionAuthority) RecordEffects(ctx context.Context, obs hostclaim.Observed, r Record) error {
26 if obs.Absent || obs.Claim.Holder == nil || obs.Claim.Op.Kind != hostclaim.KindCreating || obs.Claim.Op.Phase == "cancelling" || !matches(r, obs.Claim.Holder) {
27 return ErrIdentity
28 }
29 h, op := obs.Claim.Holder, obs.Claim.Op
30 if h.Acquisition == "" || op.Intent["ownership_effect"] == "" || op.Intent["record_effect"] == "" {
31 return ErrContract
32 }
33 appID := op.Intent["app_id"]
34 start, ok := startHandleFor(ctx, h.Account, h.App, appID, op.ID)
35 if !ok || start.owner.Console != a.ConsoleID || start.owner.Enrollment != a.Enrollment {
36 return ErrPending
37 }
38 adm, err := ReadAdmission(ctx, a.Store, h.Account, h.App, appID)
39 if err != nil {
40 return err
41 }
42 reg, ok := adm.Value.Registrations[op.ID]
43 if !ok || reg.Owner != start.owner || !RegistrationLive(reg, a.Now()) {
44 return ErrPending
45 }
46 runner := start.Runner(a.Now)
47 marker := domains.Claim{Schema: 1, Host: obs.Host, Account: h.Account, App: h.App, CreatedAt: h.CreatedAt.UTC().Format(time.RFC3339Nano), CreatedBy: op.By, Acquisition: h.Acquisition, RecordIncarnation: h.Incarnation, OwnershipState: "active"}
48 body, err := json.Marshal(marker)
49 if err != nil {
50 return err
51 }
52 key := domains.ClaimKey(obs.Host)
53 expectedTag := ""
54 previous, tag, readErr := a.Store.Get(ctx, key)
55 if readErr != nil && !errors.Is(readErr, os.ErrNotExist) {
56 return readErr
57 }
58 if readErr == nil {
59 var prior domains.Claim
60 if json.Unmarshal(previous, &prior) != nil || prior.Host != obs.Host || prior.Account != h.Account || prior.App != h.App {
61 return ErrIdentity
62 }
63 if prior.Acquisition == "" && prior.RecordIncarnation == "" {
64 if prior.Pending && op.Intent["ownership_verified"] != "true" {
65 return ErrIdentity
66 }
67 // Existing same-owner TXT reservations become the same ownership
68 // record; never delete/recreate the marker or discard capabilities.
69 prior.Acquisition, prior.RecordIncarnation = h.Acquisition, h.Incarnation
70 prior.OwnershipState, prior.Pending, prior.PendingUntil = "active", false, ""
71 if body, err = json.Marshal(prior); err != nil {
72 return err
73 }
74 expectedTag = tag
75 } else if prior.Acquisition == h.Acquisition && prior.RecordIncarnation == h.Incarnation && prior.OwnershipState == "active" {
76 body, expectedTag = previous, tag
77 } else {
78 return ErrIdentity
79 }
80 }
81 _, err = runner.Run(ctx, Effect{ID: op.Intent["ownership_effect"], Resource: key, Action: "ownership-put", Inputs: map[string]string{"body": string(body), "expected_etag": expectedTag}}, func(ctx context.Context) (string, error) {
82 etag, err := a.Store.Put(ctx, key, body, expectedTag)
83 if !errors.Is(err, release.ErrCASConflict) {
84 return etag, err
85 }
86 raw, etag, err := a.Store.Get(ctx, key)
87 if err != nil {
88 return "", err
89 }
90 var got domains.Claim
91 if err = json.Unmarshal(raw, &got); err != nil {
92 return "", err
93 }
94 if got.Host != obs.Host || got.Account != h.Account || got.App != h.App || got.Acquisition != h.Acquisition || got.RecordIncarnation != h.Incarnation || got.OwnershipState != "active" {
95 return "", ErrIdentity
96 }
97 return etag, nil
98 })
99 if err != nil {
100 return err
101 }
102 _, err = runner.Run(ctx, Effect{ID: op.Intent["record_effect"], Resource: h.Key, Action: "record-put", Inputs: map[string]string{"body": string(r.Body)}}, func(ctx context.Context) (string, error) {
103 etag, _, err := provision.WriteRecord(provision.WithRecordSequence(ctx), a.Store, h.Key, []byte(`{}`), r.Body, "")
104 if !errors.Is(err, release.ErrCASConflict) {
105 return etag, err
106 }
107 existing, err := ReadRecord(ctx, a.Store, h.Key)
108 if err != nil {
109 return "", err
110 }
111 if !matches(existing, h) {
112 return "", ErrIdentity
113 }
114 return existing.ETag, nil
115 })
116 return err
117}
118