@sponson/sim 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +195 -0
- package/dist/auth.js.map +1 -0
- package/dist/bin.js +9 -14
- package/dist/bin.js.map +1 -1
- package/dist/chaos.d.ts +4 -0
- package/dist/chaos.js +2 -0
- package/dist/chaos.js.map +1 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/demo.d.ts +39 -0
- package/dist/demo.js +168 -0
- package/dist/demo.js.map +1 -0
- package/dist/graphql.d.ts +22 -0
- package/dist/graphql.js +36 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +43 -4
- package/dist/provider.js +12 -6
- package/dist/provider.js.map +1 -1
- package/dist/routes/algolia.d.ts +37 -0
- package/dist/routes/algolia.js +83 -0
- package/dist/routes/algolia.js.map +1 -0
- package/dist/routes/aws.d.ts +43 -0
- package/dist/routes/aws.js +211 -0
- package/dist/routes/aws.js.map +1 -0
- package/dist/routes/cloud_run.d.ts +131 -0
- package/dist/routes/cloud_run.js +378 -0
- package/dist/routes/cloud_run.js.map +1 -0
- package/dist/routes/cloudflare.d.ts +80 -0
- package/dist/routes/cloudflare.js +176 -0
- package/dist/routes/cloudflare.js.map +1 -0
- package/dist/routes/digitalocean.d.ts +58 -0
- package/dist/routes/digitalocean.js +158 -0
- package/dist/routes/digitalocean.js.map +1 -0
- package/dist/routes/fly.d.ts +22 -0
- package/dist/routes/fly.js +113 -0
- package/dist/routes/fly.js.map +1 -0
- package/dist/routes/launchdarkly.d.ts +98 -0
- package/dist/routes/launchdarkly.js +213 -0
- package/dist/routes/launchdarkly.js.map +1 -0
- package/dist/routes/netlify.d.ts +126 -0
- package/dist/routes/netlify.js +347 -0
- package/dist/routes/netlify.js.map +1 -0
- package/dist/routes/planetscale.d.ts +95 -0
- package/dist/routes/planetscale.js +234 -0
- package/dist/routes/planetscale.js.map +1 -0
- package/dist/routes/railway.d.ts +24 -0
- package/dist/routes/railway.js +112 -0
- package/dist/routes/railway.js.map +1 -0
- package/dist/routes/rest.d.ts +138 -0
- package/dist/routes/rest.js +462 -0
- package/dist/routes/rest.js.map +1 -0
- package/dist/routes/supabase.d.ts +103 -0
- package/dist/routes/supabase.js +234 -0
- package/dist/routes/supabase.js.map +1 -0
- package/dist/routes/xata.d.ts +57 -0
- package/dist/routes/xata.js +115 -0
- package/dist/routes/xata.js.map +1 -0
- package/dist/server.js +71 -12
- package/dist/server.js.map +1 -1
- package/dist/state.d.ts +13 -0
- package/dist/state.js +14 -1
- package/dist/state.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulated Google Cloud Run Admin API v2: services whose every template change makes an immutable revision, their
|
|
3
|
+
* traffic targets and tags, and the long-running operations a patch answers with.
|
|
4
|
+
*
|
|
5
|
+
* Assumptions about the real API this fake encodes, checked against the official REST reference and discovery
|
|
6
|
+
* document (https://run.googleapis.com/$discovery/rest?version=v2, called DISC below) unless marked unverified. The
|
|
7
|
+
* critical ones are pinned in scenarios/contract.test.ts (`assumption CR…`).
|
|
8
|
+
* CR1. The API is `https://run.googleapis.com/v2`; every method takes an OAuth access token with the
|
|
9
|
+
* `cloud-platform` scope as `Authorization: Bearer`, which `gcloud auth print-access-token` prints for the
|
|
10
|
+
* active account. (verified: DISC; https://docs.cloud.google.com/sdk/gcloud/reference/auth/print-access-token)
|
|
11
|
+
* CR2. `GET /v2/projects/{project}/locations/{region}/services/{service}` answers the Service: `etag` ("may be used
|
|
12
|
+
* to detect modification conflict during updates"), `template`, `traffic`, `trafficStatuses`,
|
|
13
|
+
* `latestReadyRevision`, `latestCreatedRevision`, `reconciling`, and `generation` as a JSON string (int64).
|
|
14
|
+
* (verified: DISC, https://docs.cloud.google.com/run/docs/reference/rest/v2/projects.locations.services)
|
|
15
|
+
* CR3. `PATCH /v2/{service.name}?updateMask=<fields>` takes a Service and answers a long-running Operation; only the
|
|
16
|
+
* fields the mask names are written. (verified: https://docs.cloud.google.com/run/docs/reference/rest/v2/projects.locations.services/patch;
|
|
17
|
+
* the sim refuses a patch without a mask, which the adapter never sends)
|
|
18
|
+
* CR4. Each change of `template` makes a new, immutable revision; `template.revision` names it ("If this field is
|
|
19
|
+
* omitted, it will be automatically generated based on the Service name"). A name starts with the service's
|
|
20
|
+
* name and `-`, has only lowercase letters, digits and `-`, does not end with `-` and is at most 63 characters.
|
|
21
|
+
* (verified: DISC; https://docs.cloud.google.com/run/docs/configuring/services/containers) A name already
|
|
22
|
+
* taken is refused (unverified: the sim answers 409 ALREADY_EXISTS).
|
|
23
|
+
* CR5. A TrafficTarget is `{ type, revision, percent, tag }`, `type` `TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST` or
|
|
24
|
+
* `…_REVISION`, `percent` zero when left out; empty `traffic` means 100% to the latest ready revision. LATEST
|
|
25
|
+
* follows the latest ready revision, so a template change alone moves its share to the new revision; gcloud's
|
|
26
|
+
* `--no-traffic` "assigns any traffic assigned to the LATEST revision to the specific revision bound to LATEST
|
|
27
|
+
* before the deployment", after which "the LATEST revision will not receive traffic on future deployments".
|
|
28
|
+
* (verified: DISC; https://docs.cloud.google.com/sdk/gcloud/reference/run/deploy)
|
|
29
|
+
* CR6. A `tag` gives its revision a URL of its own with no traffic ("test the new revision at a specific URL,
|
|
30
|
+
* without serving traffic"), shown in `trafficStatuses[].uri` ("Displays the target URI"); its form is not to be
|
|
31
|
+
* parsed ("Don't parse the SERVICE_IDENTIFIER"), so it is read, never built. (verified:
|
|
32
|
+
* https://docs.cloud.google.com/run/docs/rollouts-rollbacks-traffic-migration,
|
|
33
|
+
* https://docs.cloud.google.com/run/docs/triggering/https-request)
|
|
34
|
+
* CR7. Whether `latestReadyRevision`, `latestCreatedRevision` and `TrafficTarget.revision` hold a revision's short
|
|
35
|
+
* name or its full resource name is not documented (unverified): clients take the last path segment; the sim
|
|
36
|
+
* answers full names in `latest*Revision` and the names it was sent in `traffic`.
|
|
37
|
+
* CR8. The `etag` sent in a patch body is a precondition: a patch whose etag is no longer the service's is refused
|
|
38
|
+
* and changes nothing. (unverified: the docs say only that it "may be used to detect modification conflict";
|
|
39
|
+
* the sim answers 409 ABORTED)
|
|
40
|
+
* CR9. `GET /v2/{name=projects/*\/locations/*\/operations/*}` answers `{ name, done, error, response }`; when `done`,
|
|
41
|
+
* "either `error` or `response` is available". (verified: DISC) The operation of a template change ends when
|
|
42
|
+
* the new revision is ready, or with an `error` when it cannot become ready (unverified: the sim ends it after
|
|
43
|
+
* `operationPolls` polls, failing it for an image in `failImages`).
|
|
44
|
+
* CR10. `GET …/services/{service}/revisions/{revision}` answers the Revision: `containers` (`name`, `image`, `env`
|
|
45
|
+
* of `{ name, value }` or `{ name, valueSource: { secretKeyRef } }`) and `conditions` of `{ type, state }`,
|
|
46
|
+
* where type "Ready" is "True when the Resource is ready" and state is CONDITION_SUCCEEDED, CONDITION_FAILED,
|
|
47
|
+
* CONDITION_PENDING or CONDITION_RECONCILING. (verified: DISC)
|
|
48
|
+
* CR11. Once an operation has succeeded, the Service answers the new `traffic` and its `trafficStatuses`
|
|
49
|
+
* (`reconciling` false: then they "will match"). (verified: DISC, `reconciling`)
|
|
50
|
+
* CR12. Cloud Run deletes revisions by itself: past 1000 per service, "non-addressable revisions are automatically
|
|
51
|
+
* deleted in historical order"; past 2000 tags per project and region, "tags that don't have a specified
|
|
52
|
+
* traffic percentage are automatically deleted". (verified: https://docs.cloud.google.com/run/quotas) Whether
|
|
53
|
+
* a tagged revision counts as addressable is not documented. The sim does not delete anything by itself.
|
|
54
|
+
* CR13. A tag is lowercase (the only documented rule, "your lowercase tag name"); the sim also refuses one that is
|
|
55
|
+
* not a DNS label (`^[a-z]([a-z0-9-]*[a-z0-9])?$`) or that two targets share. (unverified)
|
|
56
|
+
*/
|
|
57
|
+
import { Reply, route, router } from "../provider.js";
|
|
58
|
+
const LATEST = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST";
|
|
59
|
+
const BY_REVISION = "TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION";
|
|
60
|
+
const DEFAULT_IMAGE = "us-docker.pkg.dev/demo-project/web/app:main";
|
|
61
|
+
/** The simulated Cloud Run Admin API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/cloud_run`. */
|
|
62
|
+
export const cloudRunSim = {
|
|
63
|
+
// The adapter takes its token from `gcloud auth print-access-token` (CR1); this variable is only the placeholder.
|
|
64
|
+
env: { token: "CLOUD_RUN_SIM_ACCESS_TOKEN", url: "CLOUD_RUN_API_URL", testToken: "tok_cloud_run" },
|
|
65
|
+
defaultSeed: { services: [{ name: "web", env: { NODE_ENV: "production", DATABASE_URL: "postgres://prod.example.com/app" } }] },
|
|
66
|
+
reset(core, seed) {
|
|
67
|
+
const state = { services: [], revisions: [], operations: [], operationPolls: seed?.operationPolls ?? 1, failImages: [...(seed?.failImages ?? [])] };
|
|
68
|
+
for (const s of seed?.services ?? [])
|
|
69
|
+
seedService(core, state, s);
|
|
70
|
+
return state;
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* `service.<name>.tag.<tag>.env.<NAME>`: someone deploys the tagged revision again with that variable changed and
|
|
74
|
+
* the tag moved to the new revision (`gcloud run deploy --no-traffic --tag <tag> --update-env-vars`);
|
|
75
|
+
* `service.<name>.tag.<tag>` = "delete": the tag removed (`update-traffic --remove-tags`); `service.<name>.deploy`
|
|
76
|
+
* = an image: a deploy of the service's template with that image (traffic as it stands, CR5).
|
|
77
|
+
*/
|
|
78
|
+
drift(core, state, { key, rest, value, only }) {
|
|
79
|
+
if (only !== undefined || !rest.startsWith("service."))
|
|
80
|
+
return false;
|
|
81
|
+
const [name, kind, tag, sub, envName] = rest.slice("service.".length).split(".");
|
|
82
|
+
const svc = state.services.find((s) => s.name === name);
|
|
83
|
+
if (!svc)
|
|
84
|
+
throw new Error(`drift ${key}: no service ${String(name)}`);
|
|
85
|
+
if (kind === "deploy" && tag === undefined)
|
|
86
|
+
deployImage(core, state, svc, value);
|
|
87
|
+
else if (kind === "tag" && tag && sub === undefined && value === "delete")
|
|
88
|
+
removeTag(core, svc, tag);
|
|
89
|
+
else if (kind === "tag" && tag && sub === "env" && envName)
|
|
90
|
+
redeployTagged(core, state, svc, tag, envName, value);
|
|
91
|
+
else
|
|
92
|
+
throw new Error(`drift ${key}: use service.<name>.deploy = <image>, service.<name>.tag.<tag> = delete, or service.<name>.tag.<tag>.env.<NAME> = <value>`);
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
routes(core, state, req) {
|
|
96
|
+
return routes(core, state, req);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// State
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
function keyOf(s) {
|
|
103
|
+
return `${s.project}/${s.region}/${s.name}`;
|
|
104
|
+
}
|
|
105
|
+
function fullName(s) {
|
|
106
|
+
return `projects/${s.project}/locations/${s.region}/services/${s.name}`;
|
|
107
|
+
}
|
|
108
|
+
function revisionFullName(s, revision) {
|
|
109
|
+
return `${fullName(s)}/revisions/${revision}`;
|
|
110
|
+
}
|
|
111
|
+
function bump(core, svc) {
|
|
112
|
+
svc.generation += 1;
|
|
113
|
+
svc.etag = `"${core.nextId("etag-")}"`;
|
|
114
|
+
}
|
|
115
|
+
function seedService(core, state, s) {
|
|
116
|
+
const project = s.project ?? "demo-project";
|
|
117
|
+
const region = s.region ?? "us-central1";
|
|
118
|
+
const first = `${s.name}-00001-abc`;
|
|
119
|
+
const template = { containers: [{ name: "app", image: s.image ?? DEFAULT_IMAGE, ports: [{ containerPort: 8080 }], env: Object.entries(s.env ?? {}).map(([name, value]) => ({ name, value })) }] };
|
|
120
|
+
const svc = {
|
|
121
|
+
project,
|
|
122
|
+
region,
|
|
123
|
+
name: s.name,
|
|
124
|
+
generation: 1,
|
|
125
|
+
etag: `"${core.nextId("etag-")}"`,
|
|
126
|
+
template,
|
|
127
|
+
traffic: s.traffic === "pinned" ? [{ type: BY_REVISION, revision: first, percent: 100 }] : [{ type: LATEST, percent: 100 }],
|
|
128
|
+
latestCreated: first,
|
|
129
|
+
latestReady: first,
|
|
130
|
+
identifier: `${core.nextId("h")}x3bz5a7c2q-uc`,
|
|
131
|
+
createdBy: "sim",
|
|
132
|
+
};
|
|
133
|
+
state.services.push(svc);
|
|
134
|
+
state.revisions.push({ name: first, service: keyOf(svc), template: structuredClone(template), state: "ready", createdBy: "sim" });
|
|
135
|
+
}
|
|
136
|
+
/** A revision of `svc` made outside the API (a human's deploy), ready at once. */
|
|
137
|
+
function addReadyRevision(core, state, svc, template) {
|
|
138
|
+
const name = `${svc.name}-${String(svc.generation + 1).padStart(5, "0")}-${core.nextId("r")}`;
|
|
139
|
+
const t = structuredClone(template);
|
|
140
|
+
delete t.revision;
|
|
141
|
+
state.revisions.push({ name, service: keyOf(svc), template: structuredClone(t), state: "ready", createdBy: "sim" });
|
|
142
|
+
svc.template = t;
|
|
143
|
+
svc.latestCreated = name;
|
|
144
|
+
svc.latestReady = name;
|
|
145
|
+
bump(core, svc);
|
|
146
|
+
return name;
|
|
147
|
+
}
|
|
148
|
+
function deployImage(core, state, svc, image) {
|
|
149
|
+
const t = structuredClone(svc.template);
|
|
150
|
+
const c = t.containers[0];
|
|
151
|
+
c.image = image;
|
|
152
|
+
addReadyRevision(core, state, svc, t);
|
|
153
|
+
}
|
|
154
|
+
function removeTag(core, svc, tag) {
|
|
155
|
+
const t = svc.traffic.find((x) => x.tag === tag);
|
|
156
|
+
if (!t)
|
|
157
|
+
throw new Error(`drift: service ${svc.name} has no tag ${tag}`);
|
|
158
|
+
if (!t.percent)
|
|
159
|
+
svc.traffic = svc.traffic.filter((x) => x !== t);
|
|
160
|
+
else
|
|
161
|
+
delete t.tag;
|
|
162
|
+
bump(core, svc);
|
|
163
|
+
}
|
|
164
|
+
function redeployTagged(core, state, svc, tag, envName, value) {
|
|
165
|
+
const target = svc.traffic.find((x) => x.tag === tag);
|
|
166
|
+
const base = target ? state.revisions.find((r) => r.service === keyOf(svc) && r.name === lastSegment(target.revision ?? svc.latestReady)) : undefined;
|
|
167
|
+
if (!target || !base)
|
|
168
|
+
throw new Error(`drift: service ${svc.name} has no tagged revision ${tag}`);
|
|
169
|
+
// `--no-traffic`: LATEST's share stays with the revision it pointed at (CR5).
|
|
170
|
+
svc.traffic = svc.traffic.map((x) => (x.type === LATEST ? { ...x, type: BY_REVISION, revision: svc.latestReady } : x));
|
|
171
|
+
const t = structuredClone(base.template);
|
|
172
|
+
const c = t.containers[0];
|
|
173
|
+
const env = (c.env ?? []).filter((e) => e.name !== envName);
|
|
174
|
+
c.env = [...env, { name: envName, value }];
|
|
175
|
+
const name = addReadyRevision(core, state, svc, t);
|
|
176
|
+
const moved = svc.traffic.find((x) => x.tag === tag);
|
|
177
|
+
moved.type = BY_REVISION;
|
|
178
|
+
moved.revision = name;
|
|
179
|
+
}
|
|
180
|
+
function lastSegment(name) {
|
|
181
|
+
return name.slice(name.lastIndexOf("/") + 1);
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Operations
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
function imageOf(template) {
|
|
187
|
+
const c = template.containers?.[0];
|
|
188
|
+
return typeof c?.image === "string" ? c.image : undefined;
|
|
189
|
+
}
|
|
190
|
+
/** End an operation: its revision comes up (or fails to, CR9). */
|
|
191
|
+
function settle(state, op) {
|
|
192
|
+
op.done = true;
|
|
193
|
+
const svc = state.services.find((s) => keyOf(s) === op.service);
|
|
194
|
+
const rev = op.revision ? state.revisions.find((r) => r.service === op.service && r.name === op.revision) : undefined;
|
|
195
|
+
if (!svc || !rev)
|
|
196
|
+
return;
|
|
197
|
+
if (state.failImages.includes(imageOf(rev.template) ?? "")) {
|
|
198
|
+
rev.state = "failed";
|
|
199
|
+
op.error = { code: 9, message: `Revision '${rev.name}' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable.` };
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
rev.state = "ready";
|
|
203
|
+
svc.latestReady = rev.name;
|
|
204
|
+
}
|
|
205
|
+
/** One poll of an operation: it ends after `operationPolls` of them. */
|
|
206
|
+
function tick(state, op) {
|
|
207
|
+
if (op.done)
|
|
208
|
+
return;
|
|
209
|
+
if (op.polls > 0)
|
|
210
|
+
op.polls -= 1;
|
|
211
|
+
else
|
|
212
|
+
settle(state, op);
|
|
213
|
+
}
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Answers
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
function googleError(status, code, message) {
|
|
218
|
+
return new Reply(status, { error: { code: status, message, status: code } });
|
|
219
|
+
}
|
|
220
|
+
const notFound = () => googleError(404, "NOT_FOUND", "Requested entity was not found.");
|
|
221
|
+
function trafficStatuses(svc) {
|
|
222
|
+
return svc.traffic.map((t) => ({
|
|
223
|
+
type: t.type,
|
|
224
|
+
revision: t.type === LATEST ? svc.latestReady : lastSegment(t.revision ?? ""),
|
|
225
|
+
...(t.percent ? { percent: t.percent } : {}),
|
|
226
|
+
...(t.tag ? { tag: t.tag, uri: `https://${t.tag}---${svc.name}-${svc.identifier}.a.run.app` } : {}),
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
function publicService(state, svc) {
|
|
230
|
+
const reconciling = state.operations.some((o) => o.service === keyOf(svc) && !o.done);
|
|
231
|
+
return {
|
|
232
|
+
name: fullName(svc),
|
|
233
|
+
generation: String(svc.generation),
|
|
234
|
+
observedGeneration: String(reconciling ? svc.generation - 1 : svc.generation),
|
|
235
|
+
etag: svc.etag,
|
|
236
|
+
template: svc.template,
|
|
237
|
+
traffic: svc.traffic,
|
|
238
|
+
trafficStatuses: trafficStatuses(svc),
|
|
239
|
+
uri: `https://${svc.name}-${svc.identifier}.a.run.app`,
|
|
240
|
+
latestCreatedRevision: revisionFullName(svc, svc.latestCreated),
|
|
241
|
+
latestReadyRevision: revisionFullName(svc, svc.latestReady),
|
|
242
|
+
reconciling,
|
|
243
|
+
terminalCondition: { type: "Ready", state: reconciling ? "CONDITION_RECONCILING" : "CONDITION_SUCCEEDED" },
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const CONDITION = { pending: "CONDITION_RECONCILING", ready: "CONDITION_SUCCEEDED", failed: "CONDITION_FAILED" };
|
|
247
|
+
function publicRevision(svc, rev) {
|
|
248
|
+
return {
|
|
249
|
+
name: revisionFullName(svc, rev.name),
|
|
250
|
+
service: fullName(svc),
|
|
251
|
+
containers: rev.template.containers,
|
|
252
|
+
reconciling: rev.state === "pending",
|
|
253
|
+
conditions: [{ type: "Ready", state: CONDITION[rev.state] }],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function publicOperation(state, op) {
|
|
257
|
+
const svc = state.services.find((s) => keyOf(s) === op.service);
|
|
258
|
+
const outcome = !op.done ? {} : op.error ? { error: op.error } : { response: svc ? { "@type": "type.googleapis.com/google.cloud.run.v2.Service", ...publicService(state, svc) } : {} };
|
|
259
|
+
return { name: op.name, metadata: { "@type": "type.googleapis.com/google.cloud.run.v2.Service", name: svc ? fullName(svc) : "" }, done: op.done, ...outcome };
|
|
260
|
+
}
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// The patch (CR3, CR4, CR5, CR8)
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
const MASK_FIELDS = new Set(["template", "traffic"]);
|
|
265
|
+
const TAG = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
266
|
+
function maskOf(url) {
|
|
267
|
+
const raw = url.searchParams.get("updateMask");
|
|
268
|
+
if (!raw)
|
|
269
|
+
return googleError(400, "INVALID_ARGUMENT", "updateMask is required (the sim's own strictness)");
|
|
270
|
+
const fields = raw.split(",").map((f) => f.trim());
|
|
271
|
+
const unknown = fields.find((f) => !MASK_FIELDS.has(f));
|
|
272
|
+
if (unknown)
|
|
273
|
+
return googleError(400, "INVALID_ARGUMENT", `updateMask: field \`${unknown}\` is not one the sim can update`);
|
|
274
|
+
return new Set(fields);
|
|
275
|
+
}
|
|
276
|
+
/** The revision a template change makes: its name (CR4), or what is wrong with the template. */
|
|
277
|
+
function newRevisionName(core, state, svc, template) {
|
|
278
|
+
const containers = template?.containers;
|
|
279
|
+
if (!Array.isArray(containers) || containers.length === 0 || !containers.every((c) => typeof c?.image === "string")) {
|
|
280
|
+
return googleError(400, "INVALID_ARGUMENT", "template.containers: at least one container with an image is required");
|
|
281
|
+
}
|
|
282
|
+
const named = template.revision;
|
|
283
|
+
if (named === undefined || named === "")
|
|
284
|
+
return `${svc.name}-${String(svc.generation + 1).padStart(5, "0")}-${core.nextId("r")}`;
|
|
285
|
+
if (typeof named !== "string" || !named.startsWith(`${svc.name}-`) || named.length > 63 || !/^[a-z0-9-]*[a-z0-9]$/.test(named)) {
|
|
286
|
+
return googleError(400, "INVALID_ARGUMENT", `template.revision: ${String(named)} must start with ${svc.name}-, use lowercase letters, digits and -, not end with - and be at most 63 characters`);
|
|
287
|
+
}
|
|
288
|
+
if (state.revisions.some((r) => r.service === keyOf(svc) && r.name === named))
|
|
289
|
+
return googleError(409, "ALREADY_EXISTS", `Revision ${named} already exists.`);
|
|
290
|
+
return named;
|
|
291
|
+
}
|
|
292
|
+
/** What is wrong with one traffic target (CR5), or undefined. */
|
|
293
|
+
function targetProblem(t, known) {
|
|
294
|
+
if (t.type !== LATEST && t.type !== BY_REVISION)
|
|
295
|
+
return `traffic: unknown type ${String(t.type)}`;
|
|
296
|
+
if (t.type === BY_REVISION && (typeof t.revision !== "string" || !known.has(lastSegment(t.revision))))
|
|
297
|
+
return `traffic: revision ${String(t.revision)} does not exist`;
|
|
298
|
+
const percent = t.percent ?? 0;
|
|
299
|
+
if (typeof percent !== "number" || !Number.isInteger(percent) || percent < 0 || percent > 100)
|
|
300
|
+
return "traffic: percent must be 0 to 100";
|
|
301
|
+
if (t.tag !== undefined && (typeof t.tag !== "string" || !TAG.test(t.tag)))
|
|
302
|
+
return `traffic: tag ${String(t.tag)} is not a lowercase DNS label`;
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
/** What is wrong with a traffic list (CR5, CR13), or undefined. */
|
|
306
|
+
function trafficProblem(traffic, known) {
|
|
307
|
+
if (!Array.isArray(traffic))
|
|
308
|
+
return "traffic must be a list";
|
|
309
|
+
const targets = traffic;
|
|
310
|
+
const problem = targets.map((t) => targetProblem(t, known)).find((p) => p !== undefined);
|
|
311
|
+
if (problem)
|
|
312
|
+
return problem;
|
|
313
|
+
const tags = targets.flatMap((t) => (t.tag === undefined ? [] : [t.tag]));
|
|
314
|
+
if (new Set(tags).size !== tags.length)
|
|
315
|
+
return "traffic: a tag is used twice";
|
|
316
|
+
const sum = targets.reduce((n, t) => n + (t.percent ?? 0), 0);
|
|
317
|
+
return targets.length > 0 && sum !== 100 ? `traffic: percentages add up to ${sum}, not 100` : undefined;
|
|
318
|
+
}
|
|
319
|
+
function patchService(core, state, svc, url, body) {
|
|
320
|
+
const mask = maskOf(url);
|
|
321
|
+
if (mask instanceof Reply)
|
|
322
|
+
return mask;
|
|
323
|
+
const b = (body ?? {});
|
|
324
|
+
if (b.etag !== undefined && b.etag !== svc.etag)
|
|
325
|
+
return googleError(409, "ABORTED", "The etag does not match the service's: it was changed since it was read.");
|
|
326
|
+
const changesTemplate = mask.has("template") && JSON.stringify(b.template) !== JSON.stringify(svc.template);
|
|
327
|
+
const revision = changesTemplate ? newRevisionName(core, state, svc, b.template) : null;
|
|
328
|
+
if (revision instanceof Reply)
|
|
329
|
+
return revision;
|
|
330
|
+
const known = new Set(state.revisions.filter((r) => r.service === keyOf(svc)).map((r) => r.name).concat(revision ? [revision] : []));
|
|
331
|
+
const problem = mask.has("traffic") ? trafficProblem(b.traffic, known) : undefined;
|
|
332
|
+
if (problem)
|
|
333
|
+
return googleError(400, "INVALID_ARGUMENT", problem);
|
|
334
|
+
if (revision) {
|
|
335
|
+
const template = structuredClone(b.template);
|
|
336
|
+
state.revisions.push({ name: revision, service: keyOf(svc), template: structuredClone(template), state: "pending", createdBy: "api" });
|
|
337
|
+
svc.template = template;
|
|
338
|
+
svc.latestCreated = revision;
|
|
339
|
+
}
|
|
340
|
+
if (mask.has("traffic"))
|
|
341
|
+
svc.traffic = structuredClone(b.traffic);
|
|
342
|
+
bump(core, svc);
|
|
343
|
+
const op = { name: `projects/${svc.project}/locations/${svc.region}/operations/${core.nextId("op-")}`, service: keyOf(svc), revision, polls: state.operationPolls, done: false };
|
|
344
|
+
state.operations.push(op);
|
|
345
|
+
return new Reply(200, publicOperation(state, op));
|
|
346
|
+
}
|
|
347
|
+
function serviceAt(state, p) {
|
|
348
|
+
return state.services.find((s) => s.project === p.project && s.region === p.location && s.name === p.service);
|
|
349
|
+
}
|
|
350
|
+
const SERVICE = "/v2/projects/:project/locations/:location/services/:service";
|
|
351
|
+
const routes = router([
|
|
352
|
+
route("GET", SERVICE, ({ state, params }) => {
|
|
353
|
+
const svc = serviceAt(state, params);
|
|
354
|
+
if (!svc)
|
|
355
|
+
return notFound();
|
|
356
|
+
for (const op of state.operations)
|
|
357
|
+
if (op.service === keyOf(svc))
|
|
358
|
+
tick(state, op);
|
|
359
|
+
return new Reply(200, publicService(state, svc));
|
|
360
|
+
}),
|
|
361
|
+
route("PATCH", SERVICE, ({ core, state, params, url, body }) => {
|
|
362
|
+
const svc = serviceAt(state, params);
|
|
363
|
+
return svc ? patchService(core, state, svc, url, body) : notFound();
|
|
364
|
+
}),
|
|
365
|
+
route("GET", `${SERVICE}/revisions/:revision`, ({ state, params }) => {
|
|
366
|
+
const svc = serviceAt(state, params);
|
|
367
|
+
const rev = svc ? state.revisions.find((r) => r.service === keyOf(svc) && r.name === params.revision) : undefined;
|
|
368
|
+
return svc && rev ? new Reply(200, publicRevision(svc, rev)) : notFound();
|
|
369
|
+
}),
|
|
370
|
+
route("GET", "/v2/projects/:project/locations/:location/operations/:operation", ({ state, params }) => {
|
|
371
|
+
const op = state.operations.find((o) => o.name === `projects/${params.project}/locations/${params.location}/operations/${params.operation}`);
|
|
372
|
+
if (!op)
|
|
373
|
+
return notFound();
|
|
374
|
+
tick(state, op);
|
|
375
|
+
return new Reply(200, publicOperation(state, op));
|
|
376
|
+
}),
|
|
377
|
+
], notFound);
|
|
378
|
+
//# sourceMappingURL=cloud_run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud_run.js","sourceRoot":"","sources":["../../src/routes/cloud_run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulated Cloudflare: Pages projects and their per-environment variables (`deployment_configs.<env>.env_vars`).
|
|
3
|
+
*
|
|
4
|
+
* Assumptions about the real API that this fake encodes. Sources (fetched 2026-10-11): the official OpenAPI document
|
|
5
|
+
* https://github.com/cloudflare/api-schemas (`openapi.json`), the API reference pages
|
|
6
|
+
* https://developers.cloudflare.com/api/resources/pages/subresources/projects/methods/get/ and …/methods/edit/,
|
|
7
|
+
* and the Pages docs cited per item. The critical ones are pinned in scenarios/contract.test.ts
|
|
8
|
+
* (`assumption CF<n>: …`) so `pnpm test:live` checks them against a real account.
|
|
9
|
+
*
|
|
10
|
+
* CF1. Base URL `https://api.cloudflare.com/client/v4`, `Authorization: Bearer <API token>` — verified
|
|
11
|
+
* (https://developers.cloudflare.com/fundamentals/api/how-to/make-api-calls/). The token needs the
|
|
12
|
+
* account permission "Cloudflare Pages Edit" (fundamentals/api/reference/permissions/; schema token groups
|
|
13
|
+
* "Pages Read"/"Pages Write").
|
|
14
|
+
* CF2. `GET /accounts/{account_id}/pages/projects/{project_name}` answers `200 { success, errors, messages, result }`,
|
|
15
|
+
* `result` a project with `id`, `name`, `subdomain` and `deployment_configs.{preview,production}.env_vars`, a
|
|
16
|
+
* nullable map NAME → `{ type: "plain_text" | "secret_text", value }` — verified (schema `pages_project`,
|
|
17
|
+
* `pages_plain_text_env_var`, `pages_secret_text_env_var`).
|
|
18
|
+
* CF3. A `secret_text` value is never returned: the entry is listed with its type and `value: ""` — partly
|
|
19
|
+
* verified: the docs say secrets cannot be seen after they are set (pages/functions/bindings/), and the
|
|
20
|
+
* schema's example is `{"type":"secret_text","value":""}`; whether the key comes back with `""`, null or
|
|
21
|
+
* no `value` is not stated (the adapter accepts all three). Pinned.
|
|
22
|
+
* CF4. `PATCH` of the same path merges: `deployment_configs.<env>.env_vars` keys not in the body are left alone, a
|
|
23
|
+
* key set to `null` is deleted, and the answer is `200` with the updated project — `null` deletes and `200`
|
|
24
|
+
* verified ("To delete an environment variable, set its key to `null`"); merging is implied by that rule and
|
|
25
|
+
* by a body with no required fields, but not stated. Pinned: the adapter also re-reads and refuses to go on
|
|
26
|
+
* if a variable it did not send disappeared.
|
|
27
|
+
* CF5. No precondition (ETag / If-Match) exists for that PATCH — verified (only path parameters, no 412). Two
|
|
28
|
+
* writers of the same key race; last write wins. Sponson's own writers take turns through the parent lock
|
|
29
|
+
* (`lockOn`, ADR 0019).
|
|
30
|
+
* CF6. Failures answer `4XX { success: false, errors: [{ code, message }], result: null }` — verified (schema
|
|
31
|
+
* `pages_api-response-common-failure`). An unknown project answers `404` with code `8000007` — unverified:
|
|
32
|
+
* the schema declares only `4XX`; the status and code come from Wrangler's error reports.
|
|
33
|
+
* CF7. A write is visible to the next GET at once — unverified. Pinned.
|
|
34
|
+
* CF8. Preview variables are one set shared by every preview deployment; there are no per-branch values —
|
|
35
|
+
* verified ("This will set the configuration for all preview deployments, not just the deployments from a
|
|
36
|
+
* specific branch", pages/functions/wrangler-configuration/).
|
|
37
|
+
* CF9. Variable names: the schema gives no pattern (free-form map keys); the sim accepts any non-empty name.
|
|
38
|
+
* CF10. Changing a variable does not change existing deployments; it applies to the next one — indirectly
|
|
39
|
+
* documented ("Redeploy your project for the binding to take effect", pages/functions/bindings/). The sim
|
|
40
|
+
* has no deployments, so nothing here depends on it.
|
|
41
|
+
*/
|
|
42
|
+
import { type CreatedBy, type ProviderSim } from "../provider.js";
|
|
43
|
+
/** The two variable sets of a Pages project. */
|
|
44
|
+
export type CloudflarePagesEnv = "preview" | "production";
|
|
45
|
+
/** One variable as the simulated Cloudflare stores it (the secret's value included: the sim must compare it). */
|
|
46
|
+
export interface CloudflareEnvVar {
|
|
47
|
+
type: "plain_text" | "secret_text";
|
|
48
|
+
value: string;
|
|
49
|
+
}
|
|
50
|
+
/** A Pages project, with the bookkeeping the sim adds (`createdBy`). */
|
|
51
|
+
export interface CloudflarePagesProject {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
subdomain: string;
|
|
55
|
+
createdBy: CreatedBy;
|
|
56
|
+
deployment_configs: Record<CloudflarePagesEnv, {
|
|
57
|
+
env_vars: Record<string, CloudflareEnvVar>;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
/** The simulated Cloudflare's state: Pages projects per account id. */
|
|
61
|
+
export interface CloudflareState {
|
|
62
|
+
accounts: Record<string, {
|
|
63
|
+
projects: Record<string, CloudflarePagesProject>;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
/** Variables of one environment in a seed: NAME → a plain value, or `{ type, value }`. */
|
|
67
|
+
export type CloudflareSeedVars = Record<string, string | {
|
|
68
|
+
type?: CloudflareEnvVar["type"];
|
|
69
|
+
value: string;
|
|
70
|
+
}>;
|
|
71
|
+
/** What a scenario or test seeds the simulated Cloudflare with (`seed: { cloudflare: … }`). */
|
|
72
|
+
export interface CloudflareSeed {
|
|
73
|
+
/** Account id → project name → the variables that already exist (set "by a human", so unmanaged). */
|
|
74
|
+
accounts: Record<string, Record<string, {
|
|
75
|
+
preview?: CloudflareSeedVars;
|
|
76
|
+
production?: CloudflareSeedVars;
|
|
77
|
+
}>>;
|
|
78
|
+
}
|
|
79
|
+
/** The simulated Cloudflare API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/cloudflare`. */
|
|
80
|
+
export declare const cloudflareSim: ProviderSim<CloudflareState, CloudflareSeed>;
|