carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "errors"
5 "net/http"
6
7 "github.com/carlosframework/platform/internal/hostclaim"
8 "github.com/carlosframework/platform/internal/hostops"
9 "github.com/carlosframework/platform/internal/provision"
10)
11
12// claimRetireAccepted reports a delete whose exclusive operation this
13// console owns and has made durable, but which is still waiting (draining,
14// box acknowledgements, parking). Recovery or a retry of the same delete
15// carries it on. It unwraps to hostops.ErrPending, so a caller that only
16// checks for failure stays conservative.
17type claimRetireAccepted struct {
18 operation, phase, host string
19}
20
21func (a *claimRetireAccepted) Error() string {
22 return "console: delete of " + a.host + " accepted, operation " + a.operation + " is " + a.phase
23}
24
25func (a *claimRetireAccepted) Unwrap() error { return hostops.ErrPending }
26
27// claimRetireWaiting turns a pending answer from a step of an owned delete
28// into claimRetireAccepted. Every other error, and nil, passes through.
29func claimRetireWaiting(h *hostops.OperationHandle, phase, host string, err error) error {
30 if err == nil {
31 return nil
32 }
33 var accepted *claimRetireAccepted
34 if errors.As(err, &accepted) {
35 return err
36 }
37 if h != nil && errors.Is(err, hostops.ErrPending) {
38 return &claimRetireAccepted{operation: h.ID(), phase: phase, host: host}
39 }
40 return err
41}
42
43func claimRetireRecordKey(ref provision.RetireRef) (string, error) {
44 switch ref.Family {
45 case "instance":
46 return provision.RecordKey(ref.Account, ref.App, ref.Host), nil
47 case "sidecar":
48 if ref.Name == "" || ref.Host != provision.SidecarHost(ref.Name, ref.App, ref.Account) {
49 return "", provision.ErrClaimRetirer
50 }
51 return provision.SidecarRecordKey(ref.Account, ref.App, ref.Name), nil
52 default:
53 return "", provision.ErrClaimRetirer
54 }
55}
56
57// claimRetireHolderIs: the claim's holder is exactly the record this delete
58// names (and, when the caller pinned one, that incarnation).
59func claimRetireHolderIs(h *hostclaim.Holder, ref provision.RetireRef, key string) bool {
60 return h != nil && h.Key == key && h.Family == ref.Family && h.Account == ref.Account && h.App == ref.App &&
61 (ref.Incarnation == "" || h.Incarnation == ref.Incarnation)
62}
63
64// claimRetireOutcome answers a delete door's retire error. It reports
65// whether it wrote the response:
66//
67// - an owned, durable delete still in progress is 202 with the operation
68// ID and phase (the caller audits the request first);
69// - a delete refused before any effect because another exclusive
70// operation holds the app (or the host is not ready for this console)
71// is 409;
72// - anything else is left to the caller.
73func claimRetireOutcome(w http.ResponseWriter, err error) bool {
74 var accepted *claimRetireAccepted
75 if errors.As(err, &accepted) {
76 w.Header().Set("Retry-After", "2")
77 writeJSON(w, http.StatusAccepted, map[string]string{"result": "accepted", "operation_id": accepted.operation, "phase": accepted.phase, "host": accepted.host})
78 return true
79 }
80 if errors.Is(err, hostops.ErrPending) {
81 w.Header().Set("Retry-After", "2")
82 jsonError(w, http.StatusConflict, claimRetireBusyMsg)
83 return true
84 }
85 return false
86}
87
88// claimRetireBusyMsg is the 409 a delete meets when another operation
89// holds the app or the host.
90const claimRetireBusyMsg = "another operation is in progress on this app; try again shortly"
91
92// claimRetireAcceptedOf returns the accepted delete, if err is one.
93func claimRetireAcceptedOf(err error) (*claimRetireAccepted, bool) {
94 var accepted *claimRetireAccepted
95 if errors.As(err, &accepted) {
96 return accepted, true
97 }
98 return nil, false
99}
100
101// Explicit host-claim refusals of intentionally unsupported doors. Each
102// answers 409 with its own message before any registration or effect.
103//
104// Rename and account transfer are refused for EVERY app on a host-claim
105// console, with or without provisioning records. Both rewrite an app's
106// identity: its SQL rows (apps.app / account_id and every row keyed by the
107// name), its bucket prefixes and its admission object, which is keyed by
108// account and name. Doing that safely needs one operation that closes the
109// old incarnation's admission and creates the new name's under the same
110// gate, atomically, with no request admitted against either name in
111// between. That protocol is not built, so neither door may run, whatever
112// the app holds.
113//
114// Instance moves (request, cut, abort) rewrite the record's move journal,
115// which only the unbuilt host-claim Moves protocol may do under its own
116// operation.
117const (
118 claimRenameUnsupportedMsg = "renaming an app is not supported on this console yet: it needs a host-claim operation that gates both the old and the new name"
119 claimTransferUnsupportedMsg = "moving an app to another account is not supported on this console yet: it needs a host-claim operation that gates the app under both accounts"
120 claimMoveUnsupportedMsg = "instance moves are not available on this console yet: they need the host-claim Moves protocol"
121)
122