carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "os"
8 "strings"
9
10 "github.com/carlosframework/platform/internal/provision"
11 "github.com/carlosframework/platform/internal/release"
12)
13
14// Legacy migrations retain their create-only bytes. A populated sequence
15// cannot be transplanted into another key as a new incarnation; that needs
16// the claim create protocol, not a raw copy of archived authority.
17func putMigrationObject(ctx context.Context, store release.Store, key string, body []byte) (string, error) {
18 if !hostClaimEnabled(os.Getenv("CARLOS_HOST_CLAIM")) {
19 return store.Put(ctx, key, body, "")
20 }
21 if provision.IsRecordKey(key) {
22 if err := migrationRecordBytes(key, body); err != nil {
23 return "", err
24 }
25 if _, _, err := provision.RecordSequence(body); err != nil {
26 // Main copied bodies that are not strict JSON objects verbatim;
27 // with no sequence in them there is nothing to advance.
28 return store.Put(ctx, key, body, "")
29 }
30 tag, _, err := provision.WriteRecord(ctx, store, key, []byte(`{}`), body, "")
31 return tag, err
32 }
33 return store.Put(ctx, key, body, "")
34}
35
36// Preflight precedes every copy and also runs for --dry-run. The write helper
37// repeats the check because a source can change after preflight.
38func validateMigrationRecords(ctx context.Context, store release.Store, plan []copyOp) error {
39 if !hostClaimEnabled(os.Getenv("CARLOS_HOST_CLAIM")) {
40 return nil
41 }
42 for _, c := range plan {
43 if !provision.IsRecordKey(c.to) {
44 continue
45 }
46 raw, _, err := store.Get(ctx, c.from)
47 if err != nil {
48 return err
49 }
50 if err = migrationRecordBytes(c.from, raw); err != nil {
51 return err
52 }
53 }
54 return nil
55}
56
57func migrationRecordBytes(key string, body []byte) error {
58 if !migrationHasSequence(body) {
59 return nil
60 }
61 return fmt.Errorf("accounts migrate: %s has a host-claim sequence; use the claim create protocol to create a new incarnation: %w", key, provision.ErrRecordSequence)
62}
63
64// Only a present record_seq refuses: main copied malformed bodies, non-objects
65// and duplicate keys verbatim, so those still pass. A body no JSON reader can
66// parse carries no sequence; one that parses leniently is refused if any key
67// could be read as the sequence.
68func migrationHasSequence(body []byte) bool {
69 if fields, _, err := provision.RecordSequence(body); err == nil {
70 _, present := fields["record_seq"]
71 return present
72 }
73 var loose map[string]json.RawMessage
74 if json.Unmarshal(body, &loose) != nil {
75 return false
76 }
77 for name := range loose {
78 if strings.EqualFold(name, "record_seq") {
79 return true
80 }
81 }
82 return false
83}
84