@mizchi/k1c 0.2.0 → 0.4.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 +9 -6
- package/dist/cli/main.js +21 -1
- package/dist/ingress/router-template.d.ts +30 -0
- package/dist/ingress/router-template.js +50 -0
- package/dist/manifest/lower.js +223 -1
- package/dist/manifest/schemas.d.ts +3257 -145
- package/dist/manifest/schemas.js +62 -0
- package/dist/manifest/types.d.ts +75 -1
- package/dist/providers/access-application.d.ts +53 -0
- package/dist/providers/access-application.js +183 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/worker-route.d.ts +10 -0
- package/dist/providers/worker-route.js +115 -0
- package/dist/providers/worker.d.ts +8 -0
- package/dist/providers/worker.js +22 -0
- package/dist/reconciler/plan.js +44 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,9 @@ $ K1C_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... pnpm k1c apply -f manifest.yaml
|
|
|
49
49
|
| `DNSRecord` (CRD) | DNS records | working |
|
|
50
50
|
| `LogpushJob` (CRD) | Logpush (zone- or account-scoped) | working |
|
|
51
51
|
| `ai` / `browser` / `version_metadata` / `analytics_engine` Worker bindings | annotation- / volume-driven | working |
|
|
52
|
-
| `Ingress` /
|
|
52
|
+
| `Ingress` (`networking.k8s.io/v1`) | generated router Worker + Custom Domain per literal host (Workers Route per wildcard host) | working |
|
|
53
|
+
| `AccessApplication` (CRD) | Cloudflare Access self-hosted app with inline policies | working |
|
|
54
|
+
| `CustomHostname` | — | not implemented (see [`TODO.md`](TODO.md)) |
|
|
53
55
|
|
|
54
56
|
See [`docs/resources.md`](docs/resources.md) for the full mapping and limitations,
|
|
55
57
|
and [`TODO.md`](TODO.md) for what's queued.
|
|
@@ -137,13 +139,14 @@ Versioning is automated via [release-please](https://github.com/googleapis/relea
|
|
|
137
139
|
- Conventional Commit messages on `main` (`feat:`, `fix:`, `chore:`, etc.) feed
|
|
138
140
|
into a release PR that bumps `package.json`, updates `CHANGELOG.md`, and
|
|
139
141
|
cuts a Git tag plus a GitHub Release.
|
|
140
|
-
-
|
|
141
|
-
publishes `@mizchi/k1c` to npm via
|
|
142
|
-
|
|
142
|
+
- The same `release-please.yml` workflow then runs a `publish` job (gated on
|
|
143
|
+
`release_created == true`) that publishes `@mizchi/k1c` to npm via
|
|
144
|
+
[OIDC trusted publishing](https://docs.npmjs.com/trusted-publishers)
|
|
145
|
+
(no `NPM_TOKEN`) with `--provenance` SLSA attestation.
|
|
143
146
|
|
|
144
147
|
The npm package must be registered as a trusted publisher on `npmjs.com` once,
|
|
145
|
-
pointing at `mizchi/k1c` + the `
|
|
146
|
-
flow (PR → merge → tag → npm) is hands-off.
|
|
148
|
+
pointing at `mizchi/k1c` + the `release-please.yml` workflow. After that the
|
|
149
|
+
entire flow (PR → merge → tag → npm) is hands-off.
|
|
147
150
|
|
|
148
151
|
## License
|
|
149
152
|
|
package/dist/cli/main.js
CHANGED
|
@@ -152,7 +152,27 @@ function isApi404(err) {
|
|
|
152
152
|
return err.status === 404;
|
|
153
153
|
}
|
|
154
154
|
main().then((code) => process.exit(code), (err) => {
|
|
155
|
-
process.stderr.write(`fatal: ${
|
|
155
|
+
process.stderr.write(`fatal: ${formatError(err)}\n`);
|
|
156
156
|
process.exit(1);
|
|
157
157
|
});
|
|
158
|
+
function formatError(err) {
|
|
159
|
+
if (err instanceof Error)
|
|
160
|
+
return err.stack ?? err.message;
|
|
161
|
+
// Provider errors are plain objects shaped as { code, recoverable, message, ... };
|
|
162
|
+
// formatting them through `String(err)` yields "[object Object]" and loses the
|
|
163
|
+
// useful fields. Render the structured form instead.
|
|
164
|
+
if (err !== null && typeof err === 'object') {
|
|
165
|
+
const e = err;
|
|
166
|
+
if (typeof e.message === 'string') {
|
|
167
|
+
return typeof e.code === 'string' ? `[${e.code}] ${e.message}` : e.message;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
return JSON.stringify(err);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return Object.prototype.toString.call(err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return String(err);
|
|
177
|
+
}
|
|
158
178
|
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the JavaScript source for a k1c Ingress router Worker.
|
|
3
|
+
*
|
|
4
|
+
* The router holds the Ingress routing table inline as a frozen literal and
|
|
5
|
+
* dispatches each request to the matching backend Worker via a `service`
|
|
6
|
+
* binding. Host matching supports k8s-style wildcards (`*.example.com`).
|
|
7
|
+
* Path matching follows k8s Ingress semantics: `Prefix` matches segment-wise
|
|
8
|
+
* (`/foo` matches `/foo` and `/foo/bar` but not `/foobar`), `Exact` requires
|
|
9
|
+
* full equality, `ImplementationSpecific` is treated as `Prefix`.
|
|
10
|
+
*/
|
|
11
|
+
export interface RouterRoute {
|
|
12
|
+
/**
|
|
13
|
+
* Hostname rule. Either a literal host (`api.example.com`), a wildcard
|
|
14
|
+
* (`*.example.com`), or null for the catch-all rule (`spec.rules[].host` omitted).
|
|
15
|
+
*/
|
|
16
|
+
readonly host: string | null;
|
|
17
|
+
readonly paths: ReadonlyArray<RouterPath>;
|
|
18
|
+
}
|
|
19
|
+
export interface RouterPath {
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly pathType: 'Prefix' | 'Exact' | 'ImplementationSpecific';
|
|
22
|
+
/** Binding identifier on `env` (e.g. `b0`, `b1`, ...) referencing the backend Worker. */
|
|
23
|
+
readonly backendBinding: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RouterTemplateOptions {
|
|
26
|
+
readonly routes: ReadonlyArray<RouterRoute>;
|
|
27
|
+
readonly defaultBackend: string | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function generateRouter(opts: RouterTemplateOptions): string;
|
|
30
|
+
//# sourceMappingURL=router-template.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the JavaScript source for a k1c Ingress router Worker.
|
|
3
|
+
*
|
|
4
|
+
* The router holds the Ingress routing table inline as a frozen literal and
|
|
5
|
+
* dispatches each request to the matching backend Worker via a `service`
|
|
6
|
+
* binding. Host matching supports k8s-style wildcards (`*.example.com`).
|
|
7
|
+
* Path matching follows k8s Ingress semantics: `Prefix` matches segment-wise
|
|
8
|
+
* (`/foo` matches `/foo` and `/foo/bar` but not `/foobar`), `Exact` requires
|
|
9
|
+
* full equality, `ImplementationSpecific` is treated as `Prefix`.
|
|
10
|
+
*/
|
|
11
|
+
export function generateRouter(opts) {
|
|
12
|
+
const tableLiteral = JSON.stringify(opts.routes, null, 2);
|
|
13
|
+
const defaultLiteral = opts.defaultBackend === null ? 'null' : JSON.stringify(opts.defaultBackend);
|
|
14
|
+
return `// k1c Ingress router (generated)
|
|
15
|
+
const ROUTES = ${tableLiteral};
|
|
16
|
+
const DEFAULT = ${defaultLiteral};
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
async fetch(request, env) {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const host = (request.headers.get('host') ?? url.host).toLowerCase();
|
|
22
|
+
const path = url.pathname;
|
|
23
|
+
|
|
24
|
+
for (const rule of ROUTES) {
|
|
25
|
+
if (!matchHost(rule.host, host)) continue;
|
|
26
|
+
for (const p of rule.paths) {
|
|
27
|
+
if (matchPath(p, path)) return env[p.backendBinding].fetch(request);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (DEFAULT !== null) return env[DEFAULT].fetch(request);
|
|
31
|
+
return new Response('Not Found', { status: 404 });
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function matchHost(rule, host) {
|
|
36
|
+
if (rule === null) return true;
|
|
37
|
+
const r = rule.toLowerCase();
|
|
38
|
+
if (r.startsWith('*.')) return host.endsWith(r.slice(1));
|
|
39
|
+
return host === r;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function matchPath(p, path) {
|
|
43
|
+
if (p.pathType === 'Exact') return path === p.path;
|
|
44
|
+
if (p.path === '/') return true;
|
|
45
|
+
if (path === p.path) return true;
|
|
46
|
+
return path.startsWith(p.path + '/');
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=router-template.js.map
|
package/dist/manifest/lower.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { generateDispatcher } from "../canary/dispatcher-template.js";
|
|
4
|
+
import { generateRouter } from "../ingress/router-template.js";
|
|
4
5
|
export class LowerError extends Error {
|
|
5
6
|
constructor(message) {
|
|
6
7
|
super(message);
|
|
@@ -29,6 +30,8 @@ export async function lower(resources, options) {
|
|
|
29
30
|
const jobs = [];
|
|
30
31
|
const dnsRecords = [];
|
|
31
32
|
const logpushJobs = [];
|
|
33
|
+
const ingresses = [];
|
|
34
|
+
const accessApplications = [];
|
|
32
35
|
for (const r of resources) {
|
|
33
36
|
const label = labelOf(r);
|
|
34
37
|
switch (r.kind) {
|
|
@@ -85,6 +88,12 @@ export async function lower(resources, options) {
|
|
|
85
88
|
case 'LogpushJob':
|
|
86
89
|
logpushJobs.push(r);
|
|
87
90
|
break;
|
|
91
|
+
case 'Ingress':
|
|
92
|
+
ingresses.push(r);
|
|
93
|
+
break;
|
|
94
|
+
case 'AccessApplication':
|
|
95
|
+
accessApplications.push(r);
|
|
96
|
+
break;
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
const desired = [];
|
|
@@ -144,8 +153,63 @@ export async function lower(resources, options) {
|
|
|
144
153
|
if (out !== null)
|
|
145
154
|
desired.push(out);
|
|
146
155
|
}
|
|
156
|
+
for (const ing of ingresses) {
|
|
157
|
+
for (const out of await lowerIngress(ing, tables, warnings, options)) {
|
|
158
|
+
desired.push(out);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const app of accessApplications) {
|
|
162
|
+
desired.push(lowerAccessApplication(app));
|
|
163
|
+
}
|
|
147
164
|
return { desired, warnings };
|
|
148
165
|
}
|
|
166
|
+
function ruleToWire(rule) {
|
|
167
|
+
if ('email' in rule)
|
|
168
|
+
return { email: { email: rule.email.email } };
|
|
169
|
+
if ('emailDomain' in rule)
|
|
170
|
+
return { email_domain: { domain: rule.emailDomain.domain } };
|
|
171
|
+
if ('everyone' in rule)
|
|
172
|
+
return { everyone: {} };
|
|
173
|
+
if ('ip' in rule)
|
|
174
|
+
return { ip: { ip: rule.ip.ip } };
|
|
175
|
+
if ('country' in rule)
|
|
176
|
+
return { country: { country_code: rule.country.code } };
|
|
177
|
+
if ('serviceToken' in rule)
|
|
178
|
+
return { service_token: { token_id: rule.serviceToken.tokenId } };
|
|
179
|
+
return { any_valid_service_token: {} };
|
|
180
|
+
}
|
|
181
|
+
function policyToWire(p) {
|
|
182
|
+
return {
|
|
183
|
+
name: p.name,
|
|
184
|
+
decision: p.decision,
|
|
185
|
+
include: p.include.map(ruleToWire),
|
|
186
|
+
...(p.exclude !== undefined ? { exclude: p.exclude.map(ruleToWire) } : {}),
|
|
187
|
+
...(p.require !== undefined ? { require: p.require.map(ruleToWire) } : {}),
|
|
188
|
+
...(p.sessionDuration !== undefined ? { session_duration: p.sessionDuration } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function lowerAccessApplication(app) {
|
|
192
|
+
const ns = app.metadata.namespace ?? 'default';
|
|
193
|
+
const name = app.metadata.name;
|
|
194
|
+
const properties = {
|
|
195
|
+
appName: `k1c-${ns}-${name}`,
|
|
196
|
+
domain: app.spec.domain,
|
|
197
|
+
...(app.spec.sessionDuration !== undefined
|
|
198
|
+
? { sessionDuration: app.spec.sessionDuration }
|
|
199
|
+
: {}),
|
|
200
|
+
...(app.spec.autoRedirectToIdentity !== undefined
|
|
201
|
+
? { autoRedirectToIdentity: app.spec.autoRedirectToIdentity }
|
|
202
|
+
: {}),
|
|
203
|
+
...(app.spec.allowedIdps !== undefined ? { allowedIdps: [...app.spec.allowedIdps] } : {}),
|
|
204
|
+
policies: app.spec.policies.map(policyToWire),
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
resourceType: 'AccessApplication',
|
|
208
|
+
ref: refOf(app),
|
|
209
|
+
label: `${ns}/${name}`,
|
|
210
|
+
properties,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
149
213
|
function lowerVectorize(v) {
|
|
150
214
|
const ns = v.metadata.namespace ?? 'default';
|
|
151
215
|
const name = v.metadata.name;
|
|
@@ -316,6 +380,150 @@ function lowerService(s, deployments, rollouts, warnings) {
|
|
|
316
380
|
],
|
|
317
381
|
};
|
|
318
382
|
}
|
|
383
|
+
async function lowerIngress(ing, tables, warnings, options) {
|
|
384
|
+
const ns = ing.metadata.namespace ?? 'default';
|
|
385
|
+
const name = ing.metadata.name;
|
|
386
|
+
const ref = refOf(ing);
|
|
387
|
+
const annotations = ing.metadata.annotations ?? {};
|
|
388
|
+
const zoneId = annotations['cloudflare.com/zone-id'];
|
|
389
|
+
if (!zoneId) {
|
|
390
|
+
throw new LowerError(`Ingress ${ns}/${name}: missing required annotation \`cloudflare.com/zone-id\``);
|
|
391
|
+
}
|
|
392
|
+
const literalHosts = new Set();
|
|
393
|
+
const wildcardHosts = new Set();
|
|
394
|
+
for (const rule of ing.spec.rules) {
|
|
395
|
+
if (rule.host === undefined)
|
|
396
|
+
continue;
|
|
397
|
+
if (rule.host.startsWith('*.')) {
|
|
398
|
+
wildcardHosts.add(rule.host);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
literalHosts.add(rule.host);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (literalHosts.size === 0 && wildcardHosts.size === 0) {
|
|
405
|
+
throw new LowerError(`Ingress ${ns}/${name}: at least one rule must specify a host (literal or wildcard) so traffic can be bound to the router`);
|
|
406
|
+
}
|
|
407
|
+
// Assign one binding name per unique backend Service.
|
|
408
|
+
const backendBindings = new Map(); // serviceName -> bindingName
|
|
409
|
+
const serviceDeps = [];
|
|
410
|
+
function bindingFor(serviceName) {
|
|
411
|
+
const existing = backendBindings.get(serviceName);
|
|
412
|
+
if (existing !== undefined)
|
|
413
|
+
return existing;
|
|
414
|
+
const id = `b${backendBindings.size}`;
|
|
415
|
+
backendBindings.set(serviceName, id);
|
|
416
|
+
return id;
|
|
417
|
+
}
|
|
418
|
+
function resolveBackendScript(serviceName, where) {
|
|
419
|
+
const target = tables.serviceTargets.get(`${ns}/${serviceName}`);
|
|
420
|
+
if (target === undefined) {
|
|
421
|
+
throw new LowerError(`Ingress ${ns}/${name}: backend Service "${serviceName}" referenced by ${where} is not found (or has no matching workload) in namespace "${ns}"`);
|
|
422
|
+
}
|
|
423
|
+
pushUnique(serviceDeps, {
|
|
424
|
+
apiVersion: 'v1',
|
|
425
|
+
kind: 'Service',
|
|
426
|
+
namespace: ns,
|
|
427
|
+
name: serviceName,
|
|
428
|
+
});
|
|
429
|
+
return target;
|
|
430
|
+
}
|
|
431
|
+
// Pre-walk to collect bindings and validate.
|
|
432
|
+
const routes = [];
|
|
433
|
+
for (const rule of ing.spec.rules) {
|
|
434
|
+
const paths = [...rule.http.paths]
|
|
435
|
+
.map((p) => ({
|
|
436
|
+
path: p.path,
|
|
437
|
+
pathType: p.pathType,
|
|
438
|
+
backend: p.backend,
|
|
439
|
+
}))
|
|
440
|
+
// Longest path first so prefix matching picks the most specific.
|
|
441
|
+
.sort((a, b) => b.path.length - a.path.length);
|
|
442
|
+
const routerPaths = paths.map((p) => {
|
|
443
|
+
const sName = p.backend.service.name;
|
|
444
|
+
resolveBackendScript(sName, `rule host=${rule.host ?? '<any>'} path=${p.path}`);
|
|
445
|
+
return {
|
|
446
|
+
path: p.path,
|
|
447
|
+
pathType: p.pathType,
|
|
448
|
+
backendBinding: bindingFor(sName),
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
routes.push({ host: rule.host ?? null, paths: routerPaths });
|
|
452
|
+
}
|
|
453
|
+
let defaultBackendBinding = null;
|
|
454
|
+
if (ing.spec.defaultBackend !== undefined) {
|
|
455
|
+
const sName = ing.spec.defaultBackend.service.name;
|
|
456
|
+
resolveBackendScript(sName, 'defaultBackend');
|
|
457
|
+
defaultBackendBinding = bindingFor(sName);
|
|
458
|
+
}
|
|
459
|
+
// Build the router Worker.
|
|
460
|
+
const routerScriptName = `k1c--${ns}--${name}--ingress`;
|
|
461
|
+
const routerSource = generateRouter({ routes, defaultBackend: defaultBackendBinding });
|
|
462
|
+
const bindings = [];
|
|
463
|
+
for (const [serviceName, bindingName] of backendBindings) {
|
|
464
|
+
const target = tables.serviceTargets.get(`${ns}/${serviceName}`);
|
|
465
|
+
bindings.push({ type: 'service', name: bindingName, service: target });
|
|
466
|
+
}
|
|
467
|
+
const baseProperties = {
|
|
468
|
+
scriptName: routerScriptName,
|
|
469
|
+
entrypoint: '<k1c-generated:ingress-router>',
|
|
470
|
+
entrypointContent: routerSource,
|
|
471
|
+
compatibilityDate: annotations['cloudflare.com/compatibility-date'] ?? DEFAULT_COMPATIBILITY_DATE,
|
|
472
|
+
bindings,
|
|
473
|
+
};
|
|
474
|
+
const properties = {
|
|
475
|
+
...baseProperties,
|
|
476
|
+
entrypointHash: await hashEntrypoint(baseProperties, options),
|
|
477
|
+
};
|
|
478
|
+
const routerRef = { ...ref, name: `${name}--router` };
|
|
479
|
+
const results = [
|
|
480
|
+
{
|
|
481
|
+
resourceType: 'Worker',
|
|
482
|
+
ref: routerRef,
|
|
483
|
+
label: `${ns}/${name}--router`,
|
|
484
|
+
properties,
|
|
485
|
+
...(serviceDeps.length > 0 ? { dependsOn: serviceDeps } : {}),
|
|
486
|
+
},
|
|
487
|
+
];
|
|
488
|
+
// One Custom Domain per literal host, all pointing at the router Worker.
|
|
489
|
+
const environment = annotations['cloudflare.com/environment'] ?? 'production';
|
|
490
|
+
for (const host of literalHosts) {
|
|
491
|
+
const cdRef = { ...ref, name: `${name}--cd--${host}` };
|
|
492
|
+
results.push({
|
|
493
|
+
resourceType: 'CustomDomain',
|
|
494
|
+
ref: cdRef,
|
|
495
|
+
label: host,
|
|
496
|
+
properties: {
|
|
497
|
+
hostname: host,
|
|
498
|
+
service: routerScriptName,
|
|
499
|
+
zoneId,
|
|
500
|
+
environment,
|
|
501
|
+
},
|
|
502
|
+
dependsOn: [routerRef],
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
// Wildcard hosts cannot be bound via Custom Domain (which takes literal
|
|
506
|
+
// hostnames only); fall back to a zone-scoped Workers Route at `<host>/*`.
|
|
507
|
+
// The router Worker already matches the wildcard in-source, so this just
|
|
508
|
+
// delivers traffic to it.
|
|
509
|
+
for (const host of wildcardHosts) {
|
|
510
|
+
const pattern = `${host}/*`;
|
|
511
|
+
const routeRef = { ...ref, name: `${name}--route--${host}` };
|
|
512
|
+
const routeProps = {
|
|
513
|
+
zoneId,
|
|
514
|
+
pattern,
|
|
515
|
+
scriptName: routerScriptName,
|
|
516
|
+
};
|
|
517
|
+
results.push({
|
|
518
|
+
resourceType: 'WorkerRoute',
|
|
519
|
+
ref: routeRef,
|
|
520
|
+
label: pattern,
|
|
521
|
+
properties: routeProps,
|
|
522
|
+
dependsOn: [routerRef],
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return results;
|
|
526
|
+
}
|
|
319
527
|
function findWorkloadBySelector(selector, namespace, deployments, rollouts) {
|
|
320
528
|
for (const d of deployments) {
|
|
321
529
|
if ((d.metadata.namespace ?? 'default') !== namespace)
|
|
@@ -838,8 +1046,22 @@ async function buildContainerProperties(kind, ns, name, scriptName, container, t
|
|
|
838
1046
|
dataset: vol.analyticsEngineRef.dataset,
|
|
839
1047
|
});
|
|
840
1048
|
}
|
|
1049
|
+
else if (vol.mtlsCertificateRef) {
|
|
1050
|
+
bindings.push({
|
|
1051
|
+
type: 'mtls_certificate',
|
|
1052
|
+
name: mount.mountPath,
|
|
1053
|
+
certificateId: vol.mtlsCertificateRef.certificateId,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
else if (vol.pipelinesRef) {
|
|
1057
|
+
bindings.push({
|
|
1058
|
+
type: 'pipelines',
|
|
1059
|
+
name: mount.mountPath,
|
|
1060
|
+
pipeline: vol.pipelinesRef.pipelineId,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
841
1063
|
else {
|
|
842
|
-
throw new LowerError(`${ctxLabel}: volume "${vol.name}" has no recognised reference (r2BucketRef, kvNamespaceRef, serviceRef, hyperdriveRef, d1DatabaseRef, queueRef, vectorizeRef, or
|
|
1064
|
+
throw new LowerError(`${ctxLabel}: volume "${vol.name}" has no recognised reference (r2BucketRef, kvNamespaceRef, serviceRef, hyperdriveRef, d1DatabaseRef, queueRef, vectorizeRef, analyticsEngineRef, mtlsCertificateRef, or pipelinesRef)`);
|
|
843
1065
|
}
|
|
844
1066
|
}
|
|
845
1067
|
// Pod-level annotations for the no-config bindings: AI, Browser Rendering,
|