carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package provision
2
3// The replica provisioning surface (#162, edge checkout). The instance
4// record family's own verb, exactly as the discussion's Ask 2 frames it: a
5// replica is the sandbox verb with a different role and a chosen placement —
6// same app, same account, role=replica, placed on the box that carries the
7// placement code.
8//
9// The record the console's create door writes (apiinstances.go) and the
10// record a home's own code would write share ONE constructor, Replica, so
11// "what a replica record looks like" has exactly one answer: everything
12// copied from the home record's policy half (app, account, pool, channel),
13// Region = the replica's placement, Role = "replica", and ValidateShape run
14// over the result — the same rails the door applies, because neither side
15// trusts the other about what is in the bucket.
16//
17// The routing half of #162 lives in the reconciler and the edges file; the
18// lease/drain protocol that runs BETWEEN home and replica is titogo's own.
19
20import (
21 "context"
22 "errors"
23 "time"
24
25 "github.com/carlosframework/platform/internal/release"
26)
27
28// ErrReplicaOfReplica and ErrReplicaIncompleteHome are Replica's refusals:
29// a replica of a replica is not a shape the platform carries, and a home
30// link to an incomplete record would mint a replica whose account or app is
31// nothing the home ever was.
32var (
33 ErrReplicaOfReplica = errors.New("provision: record is itself a replica — a replica of a replica is not a shape the platform carries")
34 ErrReplicaIncompleteHome = errors.New("provision: home record is not a complete instance record (host/app/account)")
35)
36
37// Replica turns a home-shaped record into the replica record
38// CreateRecordReplica writes, validating the result as it goes. host is the
39// replica's own host (one label + the app's instance domain — the same shape
40// a home claims), placement is the replica's placement code (ValidRegion's
41// vocabulary, the same one the create door validates as armed), home is the
42// home record as read from the bucket. The returned record has passed
43// ValidateShape — the proof ReplicaRecord's type carries.
44func Replica(home Record, host, placement string) (ReplicaRecord, error) {
45 if home.Role == "replica" {
46 return ReplicaRecord{}, ErrReplicaOfReplica
47 }
48 if home.Host == "" || home.App == "" || home.Account == "" {
49 return ReplicaRecord{}, ErrReplicaIncompleteHome
50 }
51 rec := Record{
52 Schema: 1,
53 Host: host,
54 Slug: host,
55 App: home.App,
56 Account: home.Account,
57 Pool: home.Pool,
58 Region: placement,
59 Channel: home.Channel,
60 CreatedAt: time.Now().UTC(),
61 Role: "replica",
62 }
63 if err := ValidateShape(rec); err != nil {
64 return ReplicaRecord{}, err
65 }
66 return ReplicaRecord{rec}, nil
67}
68
69// ReplicaRecord is a Record that has passed the replica rails: ValidateShape
70// (which refuses a replica role on anything but a socket-backed instance)
71// has seen this exact value. Constructors return it; nothing else may build
72// one, so a writer that wants to stamp the role onto a record must go
73// through the same validation the door applies.
74type ReplicaRecord struct {
75 Record
76}
77
78// CreateRecordReplica writes the replica record — create-only, the
79// CreateRecord contract (a lost race is release.ErrCASConflict, never a
80// silent overwrite). The record MUST have come from Replica: the type is
81// the proof ValidateShape's replica rails have already run, and this writer
82// re-checks nothing.
83func CreateRecordReplica(ctx context.Context, s release.Store, rec ReplicaRecord) error {
84 return CreateRecord(ctx, s, rec.Record)
85}
86
87// ListRecordReplicas returns an app's records that ARE replicas — the
88// instance-API list the home instance walks to find whom it leases to,
89// addressable by Host + Region. Records are listed through ListRecords (the
90// same capped, path-validated fold the instance list uses) and filtered to
91// role=replica, so a home instance can never mistake a sibling home for its
92// own replica even before it checks the role on the wire view.
93func ListRecordReplicas(ctx context.Context, s release.Store, acct, app string, max int) ([]Record, error) {
94 recs, _, err := ListRecords(ctx, s, acct, app, max)
95 if err != nil {
96 return nil, err
97 }
98 var out []Record
99 for _, rec := range recs {
100 if rec.Role == "replica" {
101 out = append(out, rec)
102 }
103 }
104 return out, nil
105}
106