@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,43 @@
|
|
|
1
|
+
import { type ProviderSim } from "../provider.js";
|
|
2
|
+
/** One S3 CORS rule as stored. */
|
|
3
|
+
export interface AwsCorsRule {
|
|
4
|
+
ID?: string;
|
|
5
|
+
AllowedOrigin: string[];
|
|
6
|
+
AllowedMethod: string[];
|
|
7
|
+
AllowedHeader: string[];
|
|
8
|
+
ExposeHeader: string[];
|
|
9
|
+
MaxAgeSeconds?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Simulated AWS state: buckets with their CORS configuration, Amplify apps with their branches. */
|
|
12
|
+
export interface AwsState {
|
|
13
|
+
/** Access key id → secret access key. */
|
|
14
|
+
keys: Record<string, string>;
|
|
15
|
+
region: string;
|
|
16
|
+
buckets: Record<string, {
|
|
17
|
+
cors: AwsCorsRule[] | null;
|
|
18
|
+
}>;
|
|
19
|
+
amplify: Record<string, {
|
|
20
|
+
branches: Record<string, {
|
|
21
|
+
environmentVariables: Record<string, string>;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/** Initial AWS state. Buckets are listed by name; Amplify apps by id, with their branches' variables. */
|
|
26
|
+
export interface AwsSeed {
|
|
27
|
+
keys?: Record<string, string>;
|
|
28
|
+
region?: string;
|
|
29
|
+
buckets?: Record<string, {
|
|
30
|
+
cors?: AwsCorsRule[] | null;
|
|
31
|
+
}>;
|
|
32
|
+
amplify?: Record<string, {
|
|
33
|
+
branches: Record<string, {
|
|
34
|
+
environmentVariables?: Record<string, string>;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
/** The access key id the sim's default seed accepts (`AWS_SIM_ACCESS_KEY_ID` in `simEnv`). */
|
|
39
|
+
export declare const AWS_SIM_ACCESS_KEY_ID = "AKIASIMEXAMPLE0042";
|
|
40
|
+
/** Its secret (`AWS_SIM_SECRET_ACCESS_KEY` in `simEnv`). */
|
|
41
|
+
export declare const AWS_SIM_SECRET_ACCESS_KEY = "sim/aws-secret-access-key-0042";
|
|
42
|
+
/** Simulated AWS (S3 bucket CORS, Amplify branch variables); see the assumptions at the top of this file. */
|
|
43
|
+
export declare const awsSim: ProviderSim<AwsState, AwsSeed>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulated AWS APIs signed with SigV4: S3 bucket CORS configuration (XML) and Amplify branch environment
|
|
3
|
+
* variables (JSON). They exist to test the generic `http` adapter's `auth: { aws_sigv4 }` and `encoding: xml`
|
|
4
|
+
* against something that checks both: every request's signature is recomputed (../auth.ts) and refused when wrong.
|
|
5
|
+
*
|
|
6
|
+
* Path-style, under `/aws`: `/aws/s3/<bucket>/?cors` stands for `https://<bucket>.s3.<region>.amazonaws.com/?cors`,
|
|
7
|
+
* `/aws/amplify/apps/<app>/branches/<branch>` for `https://amplify.<region>.amazonaws.com/apps/<app>/branches/<branch>`.
|
|
8
|
+
*
|
|
9
|
+
* Assumptions (docs/api-verification.md, "AWS through the generic adapter"):
|
|
10
|
+
* A1. Requests are signed with SigV4 for the path's service (`s3`, `amplify`) and the seeded region; S3 requests
|
|
11
|
+
* carry `x-amz-content-sha256`. Verified: IAM "Create a signed AWS API request"; S3 "Common request headers".
|
|
12
|
+
* A2. `PUT /?cors` takes a `CORSConfiguration` of 1 to 100 `CORSRule`s (each with at least one `AllowedOrigin` and
|
|
13
|
+
* one `AllowedMethod`) and requires `Content-MD5` (or an x-amz-checksum); it replaces the whole configuration
|
|
14
|
+
* and answers 200 with no body. An empty configuration is `MalformedXML`. Verified: S3 API Reference
|
|
15
|
+
* PutBucketCors; botocore `requestChecksumRequired`.
|
|
16
|
+
* A3. `GET /?cors` answers the configuration as XML; a bucket without one answers 404 `NoSuchCORSConfiguration`.
|
|
17
|
+
* Unverified (the 404 code from memory); `DELETE /?cors` answers 204. Verified: DeleteBucketCors.
|
|
18
|
+
* A4. Amplify `GetBranch` (`GET`) and `UpdateBranch` (`POST`) on `/apps/{appId}/branches/{branchName}` answer
|
|
19
|
+
* `{ branch: { …, environmentVariables } }`; `environmentVariables` in an update replaces the whole map.
|
|
20
|
+
* Verified: Amplify API Reference UpdateBranch, GetBranch. Errors are JSON `{ message }` with
|
|
21
|
+
* `x-amzn-ErrorType`; unknown app or branch is 404 `NotFoundException`.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import { sigV4Problem } from "../auth.js";
|
|
25
|
+
import { Reply, route, router } from "../provider.js";
|
|
26
|
+
/** The access key id the sim's default seed accepts (`AWS_SIM_ACCESS_KEY_ID` in `simEnv`). */
|
|
27
|
+
export const AWS_SIM_ACCESS_KEY_ID = "AKIASIMEXAMPLE0042";
|
|
28
|
+
/** Its secret (`AWS_SIM_SECRET_ACCESS_KEY` in `simEnv`). */
|
|
29
|
+
export const AWS_SIM_SECRET_ACCESS_KEY = "sim/aws-secret-access-key-0042";
|
|
30
|
+
/** Simulated AWS (S3 bucket CORS, Amplify branch variables); see the assumptions at the top of this file. */
|
|
31
|
+
export const awsSim = {
|
|
32
|
+
env: { token: "AWS_SIM_SECRET_ACCESS_KEY", url: "AWS_SIM_URL", testToken: AWS_SIM_SECRET_ACCESS_KEY, more: { AWS_SIM_ACCESS_KEY_ID: AWS_SIM_ACCESS_KEY_ID } },
|
|
33
|
+
defaultSeed: { buckets: { assets: { cors: null } }, amplify: { d1demo: { branches: { "pr-42": {} } } } },
|
|
34
|
+
reset(_core, seed) {
|
|
35
|
+
const buckets = {};
|
|
36
|
+
for (const [name, b] of Object.entries(seed?.buckets ?? {}))
|
|
37
|
+
buckets[name] = { cors: b.cors ? structuredClone(b.cors) : null };
|
|
38
|
+
const amplify = {};
|
|
39
|
+
for (const [app, a] of Object.entries(seed?.amplify ?? {})) {
|
|
40
|
+
amplify[app] = { branches: Object.fromEntries(Object.entries(a.branches).map(([n, b]) => [n, { environmentVariables: { ...(b.environmentVariables ?? {}) } }])) };
|
|
41
|
+
}
|
|
42
|
+
return { keys: seed?.keys ?? { [AWS_SIM_ACCESS_KEY_ID]: AWS_SIM_SECRET_ACCESS_KEY }, region: seed?.region ?? "us-east-1", buckets, amplify };
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* `s3.<bucket>.cors`: "delete" removes the configuration; "remove:<ID>" one rule.
|
|
46
|
+
* `amplify.<app>.<branch>.<NAME>`: a new value, or "delete".
|
|
47
|
+
*/
|
|
48
|
+
drift(_core, state, { key, rest, value, only }) {
|
|
49
|
+
if (only !== undefined)
|
|
50
|
+
return false;
|
|
51
|
+
const [kind, a, b, c] = rest.split(".");
|
|
52
|
+
if (kind === "s3" && a && b === "cors")
|
|
53
|
+
return driftCors(state, a, value, key);
|
|
54
|
+
if (kind === "amplify" && a && b && c) {
|
|
55
|
+
const branch = state.amplify[a]?.branches[b];
|
|
56
|
+
if (!branch)
|
|
57
|
+
throw new Error(`drift ${key}: no branch ${a}/${b}`);
|
|
58
|
+
if (value === "delete")
|
|
59
|
+
delete branch.environmentVariables[c];
|
|
60
|
+
else
|
|
61
|
+
branch.environmentVariables[c] = value;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
},
|
|
66
|
+
authorized(headers, state, req) {
|
|
67
|
+
return authorized(headers, req, state);
|
|
68
|
+
},
|
|
69
|
+
routes(core, state, req) {
|
|
70
|
+
return routes(core, state, req);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
function driftCors(state, bucket, value, key) {
|
|
74
|
+
const b = state.buckets[bucket];
|
|
75
|
+
if (!b)
|
|
76
|
+
throw new Error(`drift ${key}: no bucket ${bucket}`);
|
|
77
|
+
if (value === "delete")
|
|
78
|
+
b.cors = null;
|
|
79
|
+
else if (value.startsWith("remove:"))
|
|
80
|
+
b.cors = (b.cors ?? []).filter((r) => r.ID !== value.slice(7));
|
|
81
|
+
else
|
|
82
|
+
throw new Error(`drift ${key}: "delete" or "remove:<ID>"`);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/** A1: the signature must be valid for the service the path belongs to. */
|
|
86
|
+
function authorized(headers, req, state) {
|
|
87
|
+
const service = /^\/aws\/(s3|amplify)\//.exec(req.url.pathname)?.[1];
|
|
88
|
+
if (!service)
|
|
89
|
+
return false;
|
|
90
|
+
if (service === "s3" && headers["x-amz-content-sha256"] === undefined)
|
|
91
|
+
return s3Error(400, "InvalidRequest", "Missing required header for this request: x-amz-content-sha256");
|
|
92
|
+
const problem = sigV4Problem(headers, req, { keys: state.keys, region: state.region, service });
|
|
93
|
+
if (problem === null)
|
|
94
|
+
return true;
|
|
95
|
+
return service === "s3" ? s3Error(403, problem === "InvalidAccessKeyId" ? problem : "SignatureDoesNotMatch", problem) : amplifyError(401, "UnauthorizedException", problem);
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// S3 CORS (XML)
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
const XML = { "content-type": "application/xml" };
|
|
101
|
+
function escape(s) {
|
|
102
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
103
|
+
}
|
|
104
|
+
function unescape(s) {
|
|
105
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
106
|
+
}
|
|
107
|
+
function s3Error(status, code, message) {
|
|
108
|
+
return new Reply(status, `<?xml version="1.0" encoding="UTF-8"?><Error><Code>${code}</Code><Message>${escape(message)}</Message></Error>`, XML);
|
|
109
|
+
}
|
|
110
|
+
function corsXml(rules) {
|
|
111
|
+
const el = (name, values) => values.map((v) => `<${name}>${escape(v)}</${name}>`).join("");
|
|
112
|
+
const body = rules
|
|
113
|
+
.map((r) => `<CORSRule>${r.ID !== undefined ? el("ID", [r.ID]) : ""}${el("AllowedHeader", r.AllowedHeader)}${el("AllowedMethod", r.AllowedMethod)}${el("AllowedOrigin", r.AllowedOrigin)}${el("ExposeHeader", r.ExposeHeader)}${r.MaxAgeSeconds !== undefined ? el("MaxAgeSeconds", [r.MaxAgeSeconds]) : ""}</CORSRule>`)
|
|
114
|
+
.join("");
|
|
115
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">${body}</CORSConfiguration>`;
|
|
116
|
+
}
|
|
117
|
+
/** The rules of a `CORSConfiguration` document; a reason when it is not one S3 accepts (A2). */
|
|
118
|
+
function parseCors(xml) {
|
|
119
|
+
if (/<!DOCTYPE|<!ENTITY/i.test(xml))
|
|
120
|
+
return "DTDs are not accepted";
|
|
121
|
+
if (!/<CORSConfiguration(\s[^>]*)?>[\s\S]*<\/CORSConfiguration>\s*$/.test(xml.replace(/^<\?xml[^>]*\?>\s*/, "")))
|
|
122
|
+
return "the root element must be CORSConfiguration";
|
|
123
|
+
const rules = [];
|
|
124
|
+
for (const m of xml.matchAll(/<CORSRule>([\s\S]*?)<\/CORSRule>/g)) {
|
|
125
|
+
const values = (name) => [...m[1].matchAll(new RegExp(`<${name}>([^<]*)</${name}>`, "g"))].map((x) => unescape(x[1]));
|
|
126
|
+
const rule = { AllowedOrigin: values("AllowedOrigin"), AllowedMethod: values("AllowedMethod"), AllowedHeader: values("AllowedHeader"), ExposeHeader: values("ExposeHeader") };
|
|
127
|
+
const id = values("ID")[0];
|
|
128
|
+
const age = values("MaxAgeSeconds")[0];
|
|
129
|
+
if (id !== undefined)
|
|
130
|
+
rule.ID = id;
|
|
131
|
+
if (age !== undefined)
|
|
132
|
+
rule.MaxAgeSeconds = age;
|
|
133
|
+
if (rule.AllowedOrigin.length === 0 || rule.AllowedMethod.length === 0)
|
|
134
|
+
return "every CORSRule needs an AllowedOrigin and an AllowedMethod";
|
|
135
|
+
if (rule.AllowedMethod.some((x) => !["GET", "PUT", "POST", "DELETE", "HEAD"].includes(x)))
|
|
136
|
+
return "AllowedMethod must be GET, PUT, POST, DELETE or HEAD";
|
|
137
|
+
rules.push(rule);
|
|
138
|
+
}
|
|
139
|
+
if (rules.length === 0 || rules.length > 100)
|
|
140
|
+
return "a CORSConfiguration holds 1 to 100 CORSRules";
|
|
141
|
+
return rules;
|
|
142
|
+
}
|
|
143
|
+
function putCors(bucket, headers, body) {
|
|
144
|
+
const xml = typeof body === "string" ? body : "";
|
|
145
|
+
const md5 = headers["content-md5"];
|
|
146
|
+
if (md5 === undefined)
|
|
147
|
+
return s3Error(400, "InvalidRequest", "Missing required header for this request: Content-Md5");
|
|
148
|
+
if (md5 !== createHash("md5").update(xml, "utf8").digest("base64"))
|
|
149
|
+
return s3Error(400, "BadDigest", "The Content-MD5 you specified did not match what we received.");
|
|
150
|
+
const rules = parseCors(xml);
|
|
151
|
+
if (typeof rules === "string")
|
|
152
|
+
return s3Error(400, "MalformedXML", rules);
|
|
153
|
+
bucket.cors = rules;
|
|
154
|
+
return new Reply(200, "", XML);
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Amplify branches (JSON)
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
function amplifyError(status, type, message) {
|
|
160
|
+
return new Reply(status, { message }, { "x-amzn-errortype": type });
|
|
161
|
+
}
|
|
162
|
+
function branchView(app, name, b) {
|
|
163
|
+
return new Reply(200, { branch: { branchArn: `arn:aws:amplify:us-east-1:000000000000:apps/${app}/branches/${name}`, branchName: name, stage: "PULL_REQUEST", environmentVariables: { ...b.environmentVariables } } });
|
|
164
|
+
}
|
|
165
|
+
function updateBranch(app, name, b, body) {
|
|
166
|
+
if (typeof body !== "object" || body === null)
|
|
167
|
+
return amplifyError(400, "BadRequestException", "body must be a JSON object");
|
|
168
|
+
const vars = body.environmentVariables;
|
|
169
|
+
if (vars !== undefined) {
|
|
170
|
+
if (typeof vars !== "object" || vars === null || !Object.values(vars).every((v) => typeof v === "string"))
|
|
171
|
+
return amplifyError(400, "BadRequestException", "environmentVariables must map names to strings");
|
|
172
|
+
b.environmentVariables = { ...vars };
|
|
173
|
+
}
|
|
174
|
+
return branchView(app, name, b);
|
|
175
|
+
}
|
|
176
|
+
const routes = router([
|
|
177
|
+
route("GET", "/s3/:bucket/", ({ state, params, url }) => {
|
|
178
|
+
const b = state.buckets[params.bucket];
|
|
179
|
+
if (!b)
|
|
180
|
+
return s3Error(404, "NoSuchBucket", "The specified bucket does not exist");
|
|
181
|
+
if (!url.searchParams.has("cors"))
|
|
182
|
+
return s3Error(501, "NotImplemented", "only ?cors is simulated");
|
|
183
|
+
return b.cors ? new Reply(200, corsXml(b.cors), XML) : s3Error(404, "NoSuchCORSConfiguration", "The CORS configuration does not exist");
|
|
184
|
+
}),
|
|
185
|
+
route("PUT", "/s3/:bucket/", ({ state, params, url, headers, body }) => {
|
|
186
|
+
const b = state.buckets[params.bucket];
|
|
187
|
+
if (!b)
|
|
188
|
+
return s3Error(404, "NoSuchBucket", "The specified bucket does not exist");
|
|
189
|
+
if (!url.searchParams.has("cors"))
|
|
190
|
+
return s3Error(501, "NotImplemented", "only ?cors is simulated");
|
|
191
|
+
return putCors(b, headers, body);
|
|
192
|
+
}),
|
|
193
|
+
route("DELETE", "/s3/:bucket/", ({ state, params, url }) => {
|
|
194
|
+
const b = state.buckets[params.bucket];
|
|
195
|
+
if (!b)
|
|
196
|
+
return s3Error(404, "NoSuchBucket", "The specified bucket does not exist");
|
|
197
|
+
if (!url.searchParams.has("cors"))
|
|
198
|
+
return s3Error(501, "NotImplemented", "only ?cors is simulated");
|
|
199
|
+
b.cors = null;
|
|
200
|
+
return new Reply(204, null);
|
|
201
|
+
}),
|
|
202
|
+
route("GET", "/amplify/apps/:app/branches/:branch", ({ state, params }) => {
|
|
203
|
+
const b = state.amplify[params.app]?.branches[params.branch];
|
|
204
|
+
return b ? branchView(params.app, params.branch, b) : amplifyError(404, "NotFoundException", "branch not found");
|
|
205
|
+
}),
|
|
206
|
+
route("POST", "/amplify/apps/:app/branches/:branch", ({ state, params, body }) => {
|
|
207
|
+
const b = state.amplify[params.app]?.branches[params.branch];
|
|
208
|
+
return b ? updateBranch(params.app, params.branch, b, body) : amplifyError(404, "NotFoundException", "branch not found");
|
|
209
|
+
}),
|
|
210
|
+
], () => s3Error(404, "NotFound", "not simulated"));
|
|
211
|
+
//# sourceMappingURL=aws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../src/routes/aws.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
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 { type CreatedBy, type ProviderSim } from "../provider.js";
|
|
58
|
+
/** One traffic target, as sent and stored (CR5). */
|
|
59
|
+
export interface CloudRunTrafficTarget {
|
|
60
|
+
type: string;
|
|
61
|
+
revision?: string;
|
|
62
|
+
percent?: number;
|
|
63
|
+
tag?: string;
|
|
64
|
+
}
|
|
65
|
+
/** A revision: the template it was made from, and whether it came up (CR4, CR10). */
|
|
66
|
+
export interface CloudRunRevision {
|
|
67
|
+
name: string;
|
|
68
|
+
/** `<project>/<region>/<service>`. */
|
|
69
|
+
service: string;
|
|
70
|
+
template: Record<string, unknown>;
|
|
71
|
+
state: "pending" | "ready" | "failed";
|
|
72
|
+
createdBy: CreatedBy;
|
|
73
|
+
}
|
|
74
|
+
/** A Cloud Run service. */
|
|
75
|
+
export interface CloudRunService {
|
|
76
|
+
project: string;
|
|
77
|
+
region: string;
|
|
78
|
+
name: string;
|
|
79
|
+
generation: number;
|
|
80
|
+
etag: string;
|
|
81
|
+
template: Record<string, unknown>;
|
|
82
|
+
traffic: CloudRunTrafficTarget[];
|
|
83
|
+
latestCreated: string;
|
|
84
|
+
latestReady: string;
|
|
85
|
+
/** The part of its URLs Cloud Run makes up (CR6). */
|
|
86
|
+
identifier: string;
|
|
87
|
+
createdBy: CreatedBy;
|
|
88
|
+
}
|
|
89
|
+
/** A long-running operation a patch started (CR9). */
|
|
90
|
+
export interface CloudRunOperation {
|
|
91
|
+
name: string;
|
|
92
|
+
service: string;
|
|
93
|
+
/** The revision it brings up, for a template change. */
|
|
94
|
+
revision: string | null;
|
|
95
|
+
polls: number;
|
|
96
|
+
done: boolean;
|
|
97
|
+
error?: {
|
|
98
|
+
code: number;
|
|
99
|
+
message: string;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** The simulated Cloud Run's state. */
|
|
103
|
+
export interface CloudRunState {
|
|
104
|
+
services: CloudRunService[];
|
|
105
|
+
revisions: CloudRunRevision[];
|
|
106
|
+
operations: CloudRunOperation[];
|
|
107
|
+
/** Polls an operation answers `done: false` before it ends (default 1). */
|
|
108
|
+
operationPolls: number;
|
|
109
|
+
/** Images whose revisions never become ready. */
|
|
110
|
+
failImages: string[];
|
|
111
|
+
}
|
|
112
|
+
/** One service a scenario or test seeds (`seed: { cloud_run: { services: [...] } }`). */
|
|
113
|
+
export interface CloudRunSeedService {
|
|
114
|
+
/** Default `demo-project`. */
|
|
115
|
+
project?: string;
|
|
116
|
+
/** Default `us-central1`. */
|
|
117
|
+
region?: string;
|
|
118
|
+
name: string;
|
|
119
|
+
image?: string;
|
|
120
|
+
env?: Record<string, string>;
|
|
121
|
+
/** `latest` (default): all traffic to LATEST; `pinned`: all traffic to the first revision by name. */
|
|
122
|
+
traffic?: "latest" | "pinned";
|
|
123
|
+
}
|
|
124
|
+
/** What a scenario or test seeds the simulated Cloud Run with. */
|
|
125
|
+
export interface CloudRunSeed {
|
|
126
|
+
services?: CloudRunSeedService[];
|
|
127
|
+
operationPolls?: number;
|
|
128
|
+
failImages?: string[];
|
|
129
|
+
}
|
|
130
|
+
/** The simulated Cloud Run Admin API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/cloud_run`. */
|
|
131
|
+
export declare const cloudRunSim: ProviderSim<CloudRunState, CloudRunSeed>;
|