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 "context"
5 "errors"
6 "os"
7 "path/filepath"
8 "slices"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/activator"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostwrite"
16 "github.com/carlosframework/platform/internal/instancekey"
17 "github.com/carlosframework/platform/internal/lease/leasetest"
18 "github.com/carlosframework/platform/internal/registry"
19)
20
21// keyCapturingFiles is a C8 provider that records the instance's own env
22// file as it stands when Establish runs: the moment the provider must
23// publish it (ruling 6).
24type keyCapturingFiles struct {
25 path string
26 seen []byte
27}
28
29func (k *keyCapturingFiles) Establish(ctx context.Context, o hostclaim.Observed, in ClaimLaunchInputs, work hostwrite.Work) error {
30 raw, err := os.ReadFile(k.path)
31 k.seen = raw
32 return err
33}
34
35type keyTestMarkers struct{}
36
37func (keyTestMarkers) PutMarker(context.Context, string, string, []byte, string, func(context.Context) error) (string, error) {
38 return "v1", nil
39}
40
41type passWork struct{}
42
43func (passWork) With(_ context.Context, _ hostwrite.Identity, fn func() error) error { return fn() }
44
45// The claim's first wake mints the key in Select (the launch source), so
46// the env file the C8 provider publishes in Materialize already carries it,
47// and the launch env the app will run with carries the same value.
48func TestClaimFirstWakeKeyIsOnDiskBeforeTheFilesManifest(t *testing.T) {
49 f := newAdminTokenFixture(t, "box-1")
50 files := &keyCapturingFiles{path: f.envPath()}
51 owners := &claimServingOwners{Data: leasetest.NewVersionedBucket(), Bucket: "data", Region: "ie", Box: "box-1",
52 NewID: func() string { return "g1" }, Now: func() time.Time { return time.Date(2026, 10, 10, 9, 0, 0, 0, time.UTC) },
53 Sources: ClaimServingSources{Launch: claimServingLaunchSource(f.backend, f.tokens), Markers: keyTestMarkers{}, Files: files}}
54 o, err := hostclaim.Read(t.Context(), f.claims, adminTokenHost)
55 if err != nil {
56 t.Fatal(err)
57 }
58 in, err := owners.Select(t.Context(), f.rt, o, activator.StartPolicy{})
59 if err != nil {
60 t.Fatalf("select: %v", err)
61 }
62 if err := owners.Materialize(t.Context(), o, in, passWork{}); err != nil {
63 t.Fatalf("materialize: %v", err)
64 }
65 var key string
66 for _, line := range strings.Split(string(files.seen), "\n") {
67 if v, ok := strings.CutPrefix(line, activator.InstanceKeyVar+"="); ok {
68 key = v
69 }
70 }
71 if len(key) != 44 {
72 t.Fatalf("the env file at Establish carries no minted key: %d bytes", len(files.seen))
73 }
74 found := false
75 for _, kv := range in.Env {
76 found = found || kv == activator.InstanceKeyVar+"="+key
77 }
78 if !found {
79 t.Fatal("the launch env does not carry the key the provider published")
80 }
81}
82
83// Final review I2 on the claim path: a first wake whose configuration is
84// still on its way is deferred by the launch source and nothing is minted;
85// once the config sync has delivered the configured key, the launch
86// delivers that key and the own file still holds none.
87func TestClaimFirstWakeWaitsForTheConfiguredKey(t *testing.T) {
88 f := newAdminTokenFixture(t, "box-1")
89 f.backend.ConfiguredKey = func(context.Context, registry.Route) (string, error) { return instancekey.Digest("key-A"), nil }
90 launch := claimServingLaunchSource(f.backend, f.tokens)
91 if _, err := launch(t.Context(), f.rt); !errors.Is(err, activator.ErrInstanceKeyDeferred) || !strings.Contains(err.Error(), adminTokenHost) {
92 t.Fatalf("launch before delivery = %v, want ErrInstanceKeyDeferred naming the host", err)
93 }
94 raw, _ := os.ReadFile(f.envPath())
95 if strings.Contains(string(raw), activator.InstanceKeyVar) {
96 t.Fatal("a key was minted while the configured one was on its way")
97 }
98 if err := os.WriteFile(filepath.Join(f.data, adminTokenHost+".config.env"), []byte(activator.InstanceKeyVar+"=key-A\n"), 0o600); err != nil {
99 t.Fatal(err)
100 }
101 got, err := launch(t.Context(), f.rt)
102 if err != nil {
103 t.Fatalf("launch after delivery: %v", err)
104 }
105 if !slices.Contains(got.Env, activator.InstanceKeyVar+"=key-A") {
106 t.Fatal("the launch does not deliver the configured key")
107 }
108 if raw, _ := os.ReadFile(f.envPath()); strings.Contains(string(raw), activator.InstanceKeyVar) {
109 t.Fatal("a key was minted beside the configured one")
110 }
111}
112
113// An instance from before this build has a token and no key. Its next
114// claim launch mints the key under the same guarded write, leaves the
115// token alone, and a retry keeps both.
116func TestClaimLaunchMintsAKeyBesideAnExistingToken(t *testing.T) {
117 f := newAdminTokenFixture(t, "box-1")
118 tok := strings.Repeat("c", 64)
119 if err := os.WriteFile(f.envPath(), []byte("CARLOS_ADMIN_TOKEN="+tok+"\n"), 0o600); err != nil {
120 t.Fatal(err)
121 }
122 launch := claimServingLaunchSource(f.backend, f.tokens)
123 first, err := launch(t.Context(), f.rt)
124 if err != nil {
125 t.Fatal(err)
126 }
127 raw, _ := os.ReadFile(f.envPath())
128 if f.token() != tok || !strings.HasPrefix(string(raw), "CARLOS_ADMIN_TOKEN="+tok+"\n"+activator.InstanceKeyVar+"=") {
129 t.Fatalf("env file after the launch: %d bytes, token kept %v", len(raw), f.token() == tok)
130 }
131 again, err := launch(t.Context(), f.rt)
132 if err != nil {
133 t.Fatal(err)
134 }
135 raw2, _ := os.ReadFile(f.envPath())
136 if string(raw2) != string(raw) || again.Identity != first.Identity {
137 t.Fatal("a retried launch changed the env file or the launch identity")
138 }
139}
140
141// Ruling 21: the claim snapshot names the key its env delivers, by digest,
142// and the file that supplied it, for O6 to check a process's report
143// against. The identity is the env's alone, so it does not change.
144func TestClaimSnapshotNamesTheDeliveredKeyAndItsSource(t *testing.T) {
145 f := newAdminTokenFixture(t, "box-1")
146 first, err := claimServingLaunchSource(f.backend, f.tokens)(t.Context(), f.rt)
147 if err != nil {
148 t.Fatal(err)
149 }
150 snap, err := f.backend.SnapshotClaimLaunch(t.Context(), f.rt)
151 if err != nil {
152 t.Fatal(err)
153 }
154 own, _ := os.ReadFile(f.envPath())
155 var key string
156 for _, line := range strings.Split(string(own), "\n") {
157 if v, ok := strings.CutPrefix(line, activator.InstanceKeyVar+"="); ok {
158 key = v
159 }
160 }
161 if snap.InstanceKeySource != instancekey.SourcePlatform || snap.InstanceKeySHA256 != instancekey.Digest(key) || snap.Identity != first.Identity {
162 t.Fatalf("platform key: source %q, digest match %v, identity kept %v", snap.InstanceKeySource, snap.InstanceKeySHA256 == instancekey.Digest(key), snap.Identity == first.Identity)
163 }
164 tok := "CARLOS_ADMIN_TOKEN=" + f.token() + "\n"
165 if err := os.WriteFile(f.envPath(), []byte(tok+activator.InstanceKeyVar+"=\n"), 0o600); err != nil {
166 t.Fatal(err)
167 }
168 if err := os.WriteFile(filepath.Join(f.data, adminTokenHost+".config.env"), []byte(activator.InstanceKeyVar+"=from-config\n"), 0o600); err != nil {
169 t.Fatal(err)
170 }
171 snap, err = f.backend.SnapshotClaimLaunch(t.Context(), f.rt)
172 if err != nil || snap.InstanceKeySource != instancekey.SourceConfig || snap.InstanceKeySHA256 != instancekey.Digest("from-config") {
173 t.Fatalf("configured key: source %q, %v", snap.InstanceKeySource, err)
174 }
175 // The named key is the delivered key: Env carries the configured value,
176 // not the own file's empty override (mergeEnv applies Effective).
177 if !slices.Contains(snap.Env, activator.InstanceKeyVar+"=from-config") {
178 t.Fatal("the snapshot names the configured key but its env does not deliver it")
179 }
180}
181