@fabrika/provider-zerops 0.8.1 → 0.9.0-cloudflare.1
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/package.json +16 -14
- package/src/api/client.ts +18 -0
- package/src/api/readers.ts +29 -0
- package/src/api/types.ts +64 -0
- package/src/api.ts +2 -0
- package/src/cli.ts +11 -0
- package/src/collaborators.ts +0 -14
- package/src/control.ts +35 -57
- package/src/execution.ts +113 -102
- package/src/index.ts +2 -0
- package/src/installation/admin.ts +1 -1
- package/src/installation/deploy-options.ts +1 -2
- package/src/installation/deploy.ts +225 -80
- package/src/installation/generated/platform-console-schema.ts +3 -0
- package/src/installation/generated/platform-proxy-manifest.ts +21 -1
- package/src/installation/guide.md +48 -18
- package/src/installation/hosts.ts +1 -7
- package/src/installation/index.ts +13 -16
- package/src/installation/init-options.ts +2 -1
- package/src/installation/install-environment.ts +47 -108
- package/src/installation/install-options.ts +3 -1
- package/src/installation/install-secrets.ts +24 -68
- package/src/installation/install.ts +9 -4
- package/src/installation/sidecar.ts +5 -26
- package/src/installation/topology/setups.ts +15 -4
- package/src/installation/topology/topology.ts +31 -21
- package/src/installation/upgrade-options.ts +2 -2
- package/src/managed-env.ts +1 -174
- package/src/manifest.ts +11 -14
- package/src/namespace/claims.ts +56 -1
- package/src/namespace/errors.ts +1 -1
- package/src/namespace/manifest.ts +6 -19
- package/src/namespace/provisioning.ts +24 -4
- package/src/namespace/proxy-attempt.ts +43 -2
- package/src/namespace/proxy-reads.ts +12 -22
- package/src/namespace/types.ts +2 -0
- package/src/namespace-cli.ts +152 -0
- package/src/plan.ts +2 -7
- package/src/provider.ts +128 -126
- package/src/proxy-manifest-carrier.ts +18 -0
- package/src/types.ts +0 -2
- package/types/src/api/client.d.ts +5 -0
- package/types/src/api/errors.d.ts +24 -0
- package/types/src/api/polling.d.ts +41 -0
- package/types/src/api/readers.d.ts +1115 -0
- package/types/src/api/service-env-limit.d.ts +17 -0
- package/types/src/api/service-env-response.d.ts +23 -0
- package/types/src/api/types.d.ts +687 -0
- package/types/src/api.d.ts +6 -0
- package/types/src/authoring.d.ts +9 -0
- package/types/src/cli-args.d.ts +19 -0
- package/types/src/cli.d.ts +13 -0
- package/types/src/collaborators.d.ts +16 -0
- package/types/src/compile.d.ts +91 -0
- package/types/src/control.d.ts +40 -0
- package/types/src/database-budget.d.ts +38 -0
- package/types/src/descriptor.d.ts +28 -0
- package/types/src/execution.d.ts +125 -0
- package/types/src/exposure.d.ts +47 -0
- package/types/src/index.d.ts +31 -0
- package/types/src/installation/admin-options.d.ts +24 -0
- package/types/src/installation/admin.d.ts +35 -0
- package/types/src/installation/checker-release.d.ts +12 -0
- package/types/src/installation/deploy-options.d.ts +37 -0
- package/types/src/installation/deploy.d.ts +136 -0
- package/types/src/installation/generated/platform-console-schema.d.ts +2 -0
- package/types/src/installation/generated/platform-proxy-manifest.d.ts +2 -0
- package/types/src/installation/hosts.d.ts +34 -0
- package/types/src/installation/index.d.ts +7 -0
- package/types/src/installation/init-options.d.ts +38 -0
- package/types/src/installation/init.d.ts +60 -0
- package/types/src/installation/install-collaborators.d.ts +24 -0
- package/types/src/installation/install-environment.d.ts +63 -0
- package/types/src/installation/install-options.d.ts +49 -0
- package/types/src/installation/install-project.d.ts +13 -0
- package/types/src/installation/install-provisioning.d.ts +58 -0
- package/types/src/installation/install-secrets.d.ts +17 -0
- package/types/src/installation/install-token.d.ts +10 -0
- package/types/src/installation/install.d.ts +23 -0
- package/types/src/installation/installation-source.d.ts +8 -0
- package/types/src/installation/log.d.ts +44 -0
- package/types/src/installation/machine-key.d.ts +19 -0
- package/types/src/installation/placement-options.d.ts +11 -0
- package/types/src/installation/platform-archive.d.ts +7 -0
- package/types/src/installation/platform-source.d.ts +8 -0
- package/types/src/installation/platform-upload.d.ts +3 -0
- package/types/src/installation/sidecar.d.ts +49 -0
- package/types/src/installation/source-options.d.ts +1 -0
- package/types/src/installation/topology/artifacts.d.ts +8 -0
- package/types/src/installation/topology/index.d.ts +5 -0
- package/types/src/installation/topology/installation-schemas.d.ts +2242 -0
- package/types/src/installation/topology/invariants.d.ts +23 -0
- package/types/src/installation/topology/setups.d.ts +59 -0
- package/types/src/installation/topology/topology.d.ts +153 -0
- package/types/src/installation/topology/validate.d.ts +11 -0
- package/types/src/installation/upgrade-options.d.ts +26 -0
- package/types/src/installation/upgrade-output.d.ts +6 -0
- package/types/src/installation/upgrade.d.ts +88 -0
- package/types/src/managed-env.d.ts +24 -0
- package/types/src/manifest.d.ts +204 -0
- package/types/src/namespace/claims.d.ts +16 -0
- package/types/src/namespace/codec.d.ts +334 -0
- package/types/src/namespace/constants.d.ts +25 -0
- package/types/src/namespace/errors.d.ts +15 -0
- package/types/src/namespace/manifest.d.ts +4 -0
- package/types/src/namespace/operator.d.ts +12 -0
- package/types/src/namespace/provisioning.d.ts +4 -0
- package/types/src/namespace/proxy-attempt.d.ts +39 -0
- package/types/src/namespace/proxy-reads.d.ts +22 -0
- package/types/src/namespace/proxy-secrets.d.ts +8 -0
- package/types/src/namespace/proxy-source.d.ts +74 -0
- package/types/src/namespace/topology.d.ts +9 -0
- package/types/src/namespace/types.d.ts +101 -0
- package/types/src/namespace/update.d.ts +6 -0
- package/types/src/namespace/validation.d.ts +12 -0
- package/types/src/namespace-cli.d.ts +2 -0
- package/types/src/namespace.d.ts +9 -0
- package/types/src/plan.d.ts +11 -0
- package/types/src/provider.d.ts +63 -0
- package/types/src/proxy-manifest-carrier.d.ts +24 -0
- package/types/src/schema.generated.d.ts +376 -0
- package/types/src/service-names.d.ts +4 -0
- package/types/src/service-references.d.ts +13 -0
- package/types/src/source/constants.d.ts +6 -0
- package/types/src/source/port.d.ts +60 -0
- package/types/src/source/types.d.ts +4 -0
- package/types/src/source/validation.d.ts +19 -0
- package/types/src/source.d.ts +2 -0
- package/types/src/types.d.ts +119 -0
- package/types/src/version-attempt.d.ts +8 -0
- package/src/installation/mcp.ts +0 -102
- package/src/installation/proxy-manifest.ts +0 -214
- package/src/installation/secrets.ts +0 -78
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabrika/provider-zerops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-cloudflare.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,27 +12,28 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"src",
|
|
15
|
-
"!src/**/__tests__/**"
|
|
15
|
+
"!src/**/__tests__/**",
|
|
16
|
+
"types"
|
|
16
17
|
],
|
|
17
18
|
"exports": {
|
|
18
19
|
".": {
|
|
19
20
|
"bun": "./src/index.ts",
|
|
20
|
-
"types": "./src/index.ts",
|
|
21
|
+
"types": "./types/src/index.d.ts",
|
|
21
22
|
"default": "./src/index.ts"
|
|
22
23
|
},
|
|
23
24
|
"./cli": {
|
|
24
25
|
"bun": "./src/cli.ts",
|
|
25
|
-
"types": "./src/cli.ts",
|
|
26
|
+
"types": "./types/src/cli.d.ts",
|
|
26
27
|
"default": "./src/cli.ts"
|
|
27
28
|
},
|
|
28
29
|
"./installation": {
|
|
29
30
|
"bun": "./src/installation/index.ts",
|
|
30
|
-
"types": "./src/installation/index.ts",
|
|
31
|
+
"types": "./types/src/installation/index.d.ts",
|
|
31
32
|
"default": "./src/installation/index.ts"
|
|
32
33
|
},
|
|
33
34
|
"./installation/topology": {
|
|
34
35
|
"bun": "./src/installation/topology/index.ts",
|
|
35
|
-
"types": "./src/installation/topology/index.ts",
|
|
36
|
+
"types": "./types/src/installation/topology/index.d.ts",
|
|
36
37
|
"default": "./src/installation/topology/index.ts"
|
|
37
38
|
}
|
|
38
39
|
},
|
|
@@ -43,14 +44,15 @@
|
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"zod": "^3.25.76",
|
|
46
|
-
"@fabrika/
|
|
47
|
-
"@fabrika/auth
|
|
48
|
-
"@fabrika/
|
|
49
|
-
"@fabrika/
|
|
50
|
-
"@fabrika/
|
|
51
|
-
"@fabrika/
|
|
52
|
-
"@fabrika/
|
|
53
|
-
"@fabrika/
|
|
47
|
+
"@fabrika/app": "0.9.0-cloudflare.1",
|
|
48
|
+
"@fabrika/auth": "0.9.0-cloudflare.1",
|
|
49
|
+
"@fabrika/auth-core": "0.9.0-cloudflare.1",
|
|
50
|
+
"@fabrika/email": "0.9.0-cloudflare.1",
|
|
51
|
+
"@fabrika/iam-contract": "0.9.0-cloudflare.1",
|
|
52
|
+
"@fabrika/installation-init": "0.9.0-cloudflare.1",
|
|
53
|
+
"@fabrika/operations-contract": "0.9.0-cloudflare.1",
|
|
54
|
+
"@fabrika/provider-contract": "0.9.0-cloudflare.1",
|
|
55
|
+
"@fabrika/proxy-core": "0.9.0-cloudflare.1",
|
|
54
56
|
"yaml": "^2.9.1"
|
|
55
57
|
}
|
|
56
58
|
}
|
package/src/api/client.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { waitForProcess } from './polling'
|
|
|
5
5
|
import {
|
|
6
6
|
appVersionListSchema,
|
|
7
7
|
appVersionSchema,
|
|
8
|
+
backupConfigSchema,
|
|
8
9
|
importResultSchema,
|
|
9
10
|
integrationTokenSchema,
|
|
10
11
|
logAccessSchema,
|
|
@@ -346,6 +347,12 @@ export const createZeropsApi = (options: ZeropsApiOptions): ZeropsApi => {
|
|
|
346
347
|
await request('enable subdomain access', 'PUT', `/service-stack/${serviceId}/enable-subdomain-access`, { signal })
|
|
347
348
|
},
|
|
348
349
|
|
|
350
|
+
startService: async ({ serviceId, signal }) =>
|
|
351
|
+
readResponse('start service', processSchema, await request('start service', 'PUT', `/service-stack/${serviceId}/start`, { signal })),
|
|
352
|
+
|
|
353
|
+
stopService: async ({ serviceId, signal }) =>
|
|
354
|
+
readResponse('stop service', processSchema, await request('stop service', 'PUT', `/service-stack/${serviceId}/stop`, { signal })),
|
|
355
|
+
|
|
349
356
|
configureAutoscaling: async ({ serviceId, autoscalingProfileId, signal }) => {
|
|
350
357
|
const payload = await request('configure autoscaling', 'PUT', `/service-stack/${serviceId}/autoscaling`, {
|
|
351
358
|
body: { autoscalingProfileId },
|
|
@@ -364,6 +371,17 @@ export const createZeropsApi = (options: ZeropsApiOptions): ZeropsApi => {
|
|
|
364
371
|
return readResponse('configure custom autoscaling', processNilSchema, payload).process ?? null
|
|
365
372
|
},
|
|
366
373
|
|
|
374
|
+
getBackupConfig: async ({ serviceId, signal }) =>
|
|
375
|
+
readResponse('list backups', backupConfigSchema, await request('list backups', 'GET', `/service-stack/${serviceId}/backup`, { signal })),
|
|
376
|
+
|
|
377
|
+
configureBackup: async ({ serviceId, backupPeriod, retentionPolicy, signal }) => {
|
|
378
|
+
const payload = await request('configure backup', 'PUT', `/service-stack/${serviceId}/backup-config`, {
|
|
379
|
+
body: { backupPeriod, retentionPolicy },
|
|
380
|
+
signal,
|
|
381
|
+
})
|
|
382
|
+
return readResponse('configure backup', processNilSchema, payload).process ?? null
|
|
383
|
+
},
|
|
384
|
+
|
|
367
385
|
getService: async ({ serviceId, signal }) =>
|
|
368
386
|
readResponse('get service', serviceSchema, await request('get service', 'GET', `/service-stack/${serviceId}`, { signal })),
|
|
369
387
|
|
package/src/api/readers.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { withoutSensitiveContent } from './service-env-response'
|
|
|
3
3
|
import type {
|
|
4
4
|
ZeropsAppVersion,
|
|
5
5
|
ZeropsAppVersionStatus,
|
|
6
|
+
ZeropsBackupConfig,
|
|
7
|
+
ZeropsBackupRetentionPolicy,
|
|
6
8
|
ZeropsImportResult,
|
|
7
9
|
ZeropsIntegrationToken,
|
|
8
10
|
ZeropsLogAccess,
|
|
@@ -170,6 +172,33 @@ export const serviceSchema = z.object({
|
|
|
170
172
|
currentAutoscaling: optional(serviceAutoscalingSchema),
|
|
171
173
|
}).transform(({ activeAppVersion, ...service }): ZeropsService => ({ ...service, activeAppVersionId: activeAppVersion?.id }))
|
|
172
174
|
|
|
175
|
+
const backupCountSchema = z.number().int().nonnegative()
|
|
176
|
+
|
|
177
|
+
/** Strict like the custom autoscaling, for the same reason: it is sent back whole, so no field may read as absent. */
|
|
178
|
+
const backupRetentionPolicySchema = z.object({
|
|
179
|
+
maxTotalFiles: backupCountSchema,
|
|
180
|
+
maxTotalGiB: backupCountSchema,
|
|
181
|
+
// A Go API may serialise an empty list as null; read as the empty list it is.
|
|
182
|
+
protectedTags: z.array(z.string()).nullable().transform((tags) => tags ?? []),
|
|
183
|
+
minDaily: backupCountSchema,
|
|
184
|
+
maxDaily: backupCountSchema,
|
|
185
|
+
minWeekly: backupCountSchema,
|
|
186
|
+
maxWeekly: backupCountSchema,
|
|
187
|
+
minMonthly: backupCountSchema,
|
|
188
|
+
maxMonthly: backupCountSchema,
|
|
189
|
+
}) satisfies z.ZodType<ZeropsBackupRetentionPolicy, z.ZodTypeDef, unknown>
|
|
190
|
+
|
|
191
|
+
/** A service's own retention that does not parse fails the whole read: reading it as absent would send the default over it. */
|
|
192
|
+
export const backupConfigSchema = z.object({
|
|
193
|
+
backupPeriod: z.string(),
|
|
194
|
+
defaultRetentionPolicy: backupRetentionPolicySchema,
|
|
195
|
+
retentionPolicy: backupRetentionPolicySchema.nullish(),
|
|
196
|
+
}).transform(({ backupPeriod, defaultRetentionPolicy, retentionPolicy }): ZeropsBackupConfig => ({
|
|
197
|
+
backupPeriod,
|
|
198
|
+
defaultRetentionPolicy,
|
|
199
|
+
...(retentionPolicy === null || retentionPolicy === undefined ? {} : { retentionPolicy }),
|
|
200
|
+
}))
|
|
201
|
+
|
|
173
202
|
export const projectSchema = z.object({
|
|
174
203
|
id: idSchema,
|
|
175
204
|
name: z.string(),
|
package/src/api/types.ts
CHANGED
|
@@ -196,6 +196,34 @@ export interface ZeropsServiceAutoscaling {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* How many backups of a managed service the platform keeps. VERIFIED AS A SHAPE: `OutDtoBackupRetentionPolicy` and
|
|
201
|
+
* `InDtoBodyBackupRetentionPolicy` carry these nine fields, all required. The `min*`/`max*` counts are per backup TAG
|
|
202
|
+
* (`daily`, `weekly`, `monthly`); UNVERIFIED: which tags an hourly automatic backup gets.
|
|
203
|
+
*/
|
|
204
|
+
export interface ZeropsBackupRetentionPolicy {
|
|
205
|
+
maxTotalFiles: number
|
|
206
|
+
maxTotalGiB: number
|
|
207
|
+
/** Backups with one of these tags are never deleted automatically. */
|
|
208
|
+
protectedTags: string[]
|
|
209
|
+
minDaily: number
|
|
210
|
+
maxDaily: number
|
|
211
|
+
minWeekly: number
|
|
212
|
+
maxWeekly: number
|
|
213
|
+
minMonthly: number
|
|
214
|
+
maxMonthly: number
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** A managed service's backup schedule and retention. VERIFIED AS A SHAPE: `ResponseServiceStackBackupFileList`, without its `files`. */
|
|
218
|
+
export interface ZeropsBackupConfig {
|
|
219
|
+
/** UNVERIFIED: the format. Zerops' GUI documentation describes a 5-field cron (`min hour dom mon dow`). */
|
|
220
|
+
backupPeriod: string
|
|
221
|
+
/** What the platform applies to a service with no retention of its own. UNVERIFIED: that it is what applies then. */
|
|
222
|
+
defaultRetentionPolicy: ZeropsBackupRetentionPolicy
|
|
223
|
+
/** The service's own retention; absent when none was set. */
|
|
224
|
+
retentionPolicy?: ZeropsBackupRetentionPolicy
|
|
225
|
+
}
|
|
226
|
+
|
|
199
227
|
/** One service. VERIFIED: `ResponseServiceStack` (narrowed to the fields fabrika reads). */
|
|
200
228
|
export interface ZeropsService {
|
|
201
229
|
id: string
|
|
@@ -472,6 +500,18 @@ export interface ZeropsApi {
|
|
|
472
500
|
*/
|
|
473
501
|
enableSubdomainAccess(input: { serviceId: string; signal: AbortSignal }): Promise<void>
|
|
474
502
|
|
|
503
|
+
// ── service-stack: running state ──────────────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Start a stopped service. VERIFIED AS A SHAPE: `PUT /service-stack/{id}/start` (`StartServiceStack`), no body,
|
|
507
|
+
* `ResponseProcess`. Measured 2026-10-21: a stopped Bun service served again about 9 s after the call. Files written
|
|
508
|
+
* at runtime survive a stop and a start; a deploy replaces the container and loses them.
|
|
509
|
+
*/
|
|
510
|
+
startService(input: { serviceId: string; signal: AbortSignal }): Promise<ZeropsProcess>
|
|
511
|
+
|
|
512
|
+
/** Stop a service. VERIFIED AS A SHAPE: `PUT /service-stack/{id}/stop` (`StopServiceStack`), no body, `ResponseProcess`. */
|
|
513
|
+
stopService(input: { serviceId: string; signal: AbortSignal }): Promise<ZeropsProcess>
|
|
514
|
+
|
|
475
515
|
// ── service-stack: autoscaling ────────────────────────────────────────────────
|
|
476
516
|
|
|
477
517
|
/**
|
|
@@ -494,6 +534,30 @@ export interface ZeropsApi {
|
|
|
494
534
|
*/
|
|
495
535
|
configureCustomAutoscaling(input: { serviceId: string; autoscaling: ZeropsServiceAutoscaling; signal: AbortSignal }): Promise<ZeropsProcess | null>
|
|
496
536
|
|
|
537
|
+
// ── service-stack: backups ────────────────────────────────────────────────────
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* A managed service's backup schedule and retention. VERIFIED AS A SHAPE: `GET /service-stack/{id}/backup`
|
|
541
|
+
* (`ListBackups`), response `ResponseServiceStackBackupFileList`; the backup files it also lists are not read.
|
|
542
|
+
*/
|
|
543
|
+
getBackupConfig(input: { serviceId: string; signal: AbortSignal }): Promise<ZeropsBackupConfig>
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Set a managed service's backup schedule and retention. VERIFIED AS A SHAPE: `PUT /service-stack/{id}/backup-config`
|
|
547
|
+
* (`ConfigureBackup`), body `RequestServiceStackBackupConfig`, response `ResponseProcessNil`. The platform stores the
|
|
548
|
+
* period as the service's `BACKUP_PERIOD` variable and may start a user-data sync, whose process this answers.
|
|
549
|
+
*
|
|
550
|
+
* `retentionPolicy` is required here although the body makes it optional. UNVERIFIED: whether a request without one
|
|
551
|
+
* resets the service's retention; a PUT elsewhere on this API resets every field it leaves out, so the caller sends
|
|
552
|
+
* the retention it read, whole.
|
|
553
|
+
*/
|
|
554
|
+
configureBackup(input: {
|
|
555
|
+
serviceId: string
|
|
556
|
+
backupPeriod: string
|
|
557
|
+
retentionPolicy: ZeropsBackupRetentionPolicy
|
|
558
|
+
signal: AbortSignal
|
|
559
|
+
}): Promise<ZeropsProcess | null>
|
|
560
|
+
|
|
497
561
|
// ── service-stack / project: reading state ────────────────────────────────────
|
|
498
562
|
|
|
499
563
|
/** One service by id. VERIFIED: `GET /service-stack/{id}`. */
|
package/src/api.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import type { AppCli } from '@fabrika/provider-contract/app-cli'
|
|
2
3
|
import { constants } from 'node:fs'
|
|
3
4
|
import { open } from 'node:fs/promises'
|
|
4
5
|
import { resolve } from 'node:path'
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
manifestServiceHostnames,
|
|
13
14
|
ZEROPS_SOURCE_DESCRIPTOR_MAX_BYTES,
|
|
14
15
|
} from './manifest'
|
|
16
|
+
import { zeropsNamespaceCli } from './namespace-cli'
|
|
15
17
|
import type { ZeropsAppConfig } from './types'
|
|
16
18
|
|
|
17
19
|
const USAGE = `Zerops app tools
|
|
@@ -106,6 +108,15 @@ export const runZeropsCli = async (argv: readonly string[]): Promise<void> => {
|
|
|
106
108
|
console.info(`wrote ${output} (${manifestServiceHostnames(manifest).length} service(s))`)
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
/** What `fabrika app build`, `app register` and `fabrika namespace` run for an application on Zerops. */
|
|
112
|
+
export const appCli: AppCli = {
|
|
113
|
+
provider: 'zerops',
|
|
114
|
+
entryRuntime: 'bun',
|
|
115
|
+
build: (argv) => runZeropsCli(['build', ...argv]),
|
|
116
|
+
registrationManifest: buildZeropsManifestText,
|
|
117
|
+
namespaces: zeropsNamespaceCli,
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
if (import.meta.main) {
|
|
110
121
|
await runZeropsCli(process.argv.slice(2)).catch((error: unknown) => {
|
|
111
122
|
console.error(error instanceof Error ? error.message : String(error))
|
package/src/collaborators.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { reconcileSchema } from '@fabrika/auth'
|
|
2
|
-
import type { SchemaReconciler } from '@fabrika/provider-contract'
|
|
3
1
|
import { createZeropsApi, type ZeropsApi } from './api'
|
|
4
2
|
import { type ExposureAlerter, sendExposureAlert } from './exposure'
|
|
5
3
|
import type { ZeropsSourcePort } from './source/port'
|
|
@@ -28,7 +26,6 @@ export interface ZeropsCollaborators {
|
|
|
28
26
|
api: ZeropsApi
|
|
29
27
|
/** Injected by the Zerops control composition; dry runs do not require it. */
|
|
30
28
|
source?: ZeropsSourcePort
|
|
31
|
-
reconcileSchema: SchemaReconciler
|
|
32
29
|
/** Raises the Operations alert for a refused activation; best-effort. */
|
|
33
30
|
alertExposure: ExposureAlerter
|
|
34
31
|
sleep: Sleeper
|
|
@@ -36,19 +33,8 @@ export interface ZeropsCollaborators {
|
|
|
36
33
|
|
|
37
34
|
export type ZeropsCollaboratorFactory = (target: ZeropsRuntimeTarget) => ZeropsCollaborators
|
|
38
35
|
|
|
39
|
-
const defaultReconcileSchema: SchemaReconciler = (input) =>
|
|
40
|
-
reconcileSchema({
|
|
41
|
-
url: input.url,
|
|
42
|
-
app: input.app,
|
|
43
|
-
schema: input.schema,
|
|
44
|
-
...(input.returnOrigins === undefined ? {} : { returnOrigins: input.returnOrigins }),
|
|
45
|
-
adminKey: input.adminKey,
|
|
46
|
-
signal: input.signal,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
36
|
export const defaultZeropsCollaborators: ZeropsCollaboratorFactory = (target) => ({
|
|
50
37
|
api: createZeropsApi({ token: target.accessToken, baseUrl: target.apiBaseUrl }),
|
|
51
|
-
reconcileSchema: defaultReconcileSchema,
|
|
52
38
|
alertExposure: sendExposureAlert,
|
|
53
39
|
sleep: defaultSleep,
|
|
54
40
|
})
|
package/src/control.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { appEnvironmentId } from '@fabrika/auth-core'
|
|
2
1
|
import type {
|
|
3
2
|
ControlProvider,
|
|
4
3
|
JsonValue,
|
|
@@ -8,6 +7,7 @@ import type {
|
|
|
8
7
|
ProviderDeployInput,
|
|
9
8
|
ProviderDeploymentNamespace,
|
|
10
9
|
ProviderDeployOutcome,
|
|
10
|
+
ProviderDevEnvironmentInput,
|
|
11
11
|
ProviderEnvelope,
|
|
12
12
|
ProviderEnvironment,
|
|
13
13
|
ProviderReconcileOutcome,
|
|
@@ -16,7 +16,6 @@ import type {
|
|
|
16
16
|
ProviderSourceBinding,
|
|
17
17
|
ProviderSourceResolution,
|
|
18
18
|
ProviderSourceResolutionInput,
|
|
19
|
-
SchemaReconciler,
|
|
20
19
|
} from '@fabrika/provider-contract'
|
|
21
20
|
import { decodeEnvelope, ProviderSecretError } from '@fabrika/provider-contract'
|
|
22
21
|
import { z } from 'zod'
|
|
@@ -31,9 +30,8 @@ import {
|
|
|
31
30
|
} from './api'
|
|
32
31
|
import { defaultSleep, defaultZeropsCollaborators, type Sleeper } from './collaborators'
|
|
33
32
|
import { createdServices, type ExposureAlerter, isObjectStorageType } from './exposure'
|
|
34
|
-
import { assertAuthoredManagedInputs, assertAuthoredManagedName, assertNoFrameworkReference
|
|
33
|
+
import { assertAuthoredManagedInputs, assertAuthoredManagedName, assertNoFrameworkReference } from './managed-env'
|
|
35
34
|
import {
|
|
36
|
-
approvedSchemaOf,
|
|
37
35
|
assertFabrikaManifestCoordinates,
|
|
38
36
|
type FabrikaManifest,
|
|
39
37
|
manifestServiceHostnames,
|
|
@@ -90,11 +88,8 @@ const envelope = <T>(codec: ProviderCodec<T>, value: T): ProviderEnvelope => ({
|
|
|
90
88
|
export interface ZeropsControlProviderOptions {
|
|
91
89
|
readonly accessToken: string
|
|
92
90
|
readonly apiBaseUrl?: string
|
|
93
|
-
readonly propustkaUrl?: string
|
|
94
|
-
readonly adminKey?: string
|
|
95
91
|
readonly api?: ZeropsApi
|
|
96
92
|
readonly source: ZeropsSourcePort
|
|
97
|
-
readonly reconcileSchema?: SchemaReconciler
|
|
98
93
|
readonly alertExposure?: ExposureAlerter
|
|
99
94
|
readonly sleep?: Sleeper
|
|
100
95
|
readonly namespaces?: {
|
|
@@ -102,6 +97,8 @@ export interface ZeropsControlProviderOptions {
|
|
|
102
97
|
readonly proxySource: ZeropsNamespaceProxySource
|
|
103
98
|
readonly iamUrl: string
|
|
104
99
|
readonly iamKey: string
|
|
100
|
+
/** The installation's own project, which no namespace may adopt. */
|
|
101
|
+
readonly installationProjectId?: string
|
|
105
102
|
/** The manifest parsers of the older releases' proxies, which a namespace pinned to an older source may run. */
|
|
106
103
|
readonly olderProxies: OlderProxyReaders
|
|
107
104
|
}
|
|
@@ -226,7 +223,6 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
226
223
|
return {
|
|
227
224
|
api,
|
|
228
225
|
source: options.source,
|
|
229
|
-
reconcileSchema: options.reconcileSchema ?? defaults.reconcileSchema,
|
|
230
226
|
alertExposure: options.alertExposure ?? defaults.alertExposure,
|
|
231
227
|
sleep: options.sleep ?? defaultSleep,
|
|
232
228
|
}
|
|
@@ -260,34 +256,6 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
260
256
|
}
|
|
261
257
|
}
|
|
262
258
|
|
|
263
|
-
const reconcileActiveSchema = async (
|
|
264
|
-
input: { environment: ProviderEnvironment; returnOrigins?: readonly string[] },
|
|
265
|
-
signal: AbortSignal,
|
|
266
|
-
): Promise<void> => {
|
|
267
|
-
const artifact = artifactFor(input.environment)
|
|
268
|
-
const schema = approvedSchemaOf(input.environment)
|
|
269
|
-
if (schema === undefined || options.propustkaUrl === undefined) return
|
|
270
|
-
const placement = resolvedEnvironment(input.environment, { requireReady: false })
|
|
271
|
-
const runtimeTarget: ZeropsRuntimeTarget = {
|
|
272
|
-
projectId: placement.projectId,
|
|
273
|
-
serviceId: placement.target.serviceId,
|
|
274
|
-
accessToken: options.accessToken,
|
|
275
|
-
...(options.apiBaseUrl !== undefined ? { apiBaseUrl: options.apiBaseUrl } : {}),
|
|
276
|
-
propustkaUrl: options.propustkaUrl,
|
|
277
|
-
...(options.adminKey !== undefined ? { adminKey: options.adminKey } : {}),
|
|
278
|
-
}
|
|
279
|
-
const reconcileSchema = options.reconcileSchema ?? defaultZeropsCollaborators(runtimeTarget).reconcileSchema
|
|
280
|
-
signal.throwIfAborted()
|
|
281
|
-
await reconcileSchema({
|
|
282
|
-
url: options.propustkaUrl,
|
|
283
|
-
app: appEnvironmentId(artifact.app.id, artifact.app.env),
|
|
284
|
-
schema,
|
|
285
|
-
...(input.returnOrigins === undefined || input.returnOrigins.length === 0 ? {} : { returnOrigins: input.returnOrigins }),
|
|
286
|
-
adminKey: options.adminKey,
|
|
287
|
-
signal,
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
|
|
291
259
|
const assertVersionCoordinates = (environment: ProviderEnvironment, appVersionId: string, version: ZeropsAppVersion): void => {
|
|
292
260
|
const serviceId = decodeEnvironment(environment).target.serviceId
|
|
293
261
|
if (version.id !== appVersionId || (version.serviceStackId !== undefined && version.serviceStackId !== serviceId)) {
|
|
@@ -322,9 +290,31 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
322
290
|
return { commitSha: result.commitSha }
|
|
323
291
|
}
|
|
324
292
|
|
|
293
|
+
/** Stop or start the service of a `dev` environment outside a run, and wait for the platform's process. */
|
|
294
|
+
const runningState = async (input: ProviderDevEnvironmentInput, action: 'start' | 'stop'): Promise<void> => {
|
|
295
|
+
if (input.environment.kind !== 'dev') throw new Error('zerops: only a dev environment is stopped and started outside a run')
|
|
296
|
+
const placement = resolvedEnvironment(input.environment, { requireReady: false })
|
|
297
|
+
const serviceId = placement.target.serviceId
|
|
298
|
+
// Idempotent: Control's record of a stopped service can lag behind a start from elsewhere.
|
|
299
|
+
const status = (await api.getService({ serviceId, signal: input.signal })).status
|
|
300
|
+
if (status === (action === 'stop' ? 'STOPPED' : 'ACTIVE')) return
|
|
301
|
+
const process = action === 'stop'
|
|
302
|
+
? await api.stopService({ serviceId, signal: input.signal })
|
|
303
|
+
: await api.startService({ serviceId, signal: input.signal })
|
|
304
|
+
await waitForProcess({
|
|
305
|
+
api,
|
|
306
|
+
processId: process.id,
|
|
307
|
+
sleep: options.sleep ?? defaultSleep,
|
|
308
|
+
signal: input.signal,
|
|
309
|
+
label: `the ${action} of ${input.environment.appId}/${input.environment.env}`,
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
325
313
|
const controlProvider: ControlProvider = {
|
|
326
314
|
id: 'zerops',
|
|
327
315
|
normalizeRegistration,
|
|
316
|
+
// The deploy reads `zerops.yaml` from the repository, so a workspace may not change it (ADR-0049 D6).
|
|
317
|
+
authoring: { protectedFiles: ['zerops.yaml', 'zerops.yml'] },
|
|
328
318
|
source: { binding: 'connection' },
|
|
329
319
|
/**
|
|
330
320
|
* A Zerops artifact IS the compiled manifest, so the declaration is right here and core can refuse
|
|
@@ -347,8 +337,6 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
347
337
|
deploy: async (input: ProviderDeployInput): Promise<ProviderDeployOutcome> => {
|
|
348
338
|
input.signal.throwIfAborted()
|
|
349
339
|
assertAuthoredManagedInputs({ variableNames: Object.keys(input.vars), secretNames: Object.keys(input.secrets) })
|
|
350
|
-
const handed = parseManagedEnvironment(input.managedEnvironment)
|
|
351
|
-
const managedEnvironment = { ...handed.values, ...handed.familyVariables }
|
|
352
340
|
const binding = privateBinding(input.sourceBinding)
|
|
353
341
|
assertEnvironmentOfApp(input.app, input.environment)
|
|
354
342
|
const placement = resolvedEnvironment(input.environment, { requireReady: true })
|
|
@@ -374,16 +362,14 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
374
362
|
...(placement.target.createdBuckets === undefined ? {} : { createdBuckets: placement.target.createdBuckets }),
|
|
375
363
|
...(source === undefined ? {} : { source }),
|
|
376
364
|
...(options.apiBaseUrl !== undefined ? { apiBaseUrl: options.apiBaseUrl } : {}),
|
|
377
|
-
...(options.propustkaUrl !== undefined ? { propustkaUrl: options.propustkaUrl } : {}),
|
|
378
|
-
...(options.adminKey !== undefined ? { adminKey: options.adminKey } : {}),
|
|
379
365
|
}
|
|
380
366
|
const result = await deployRun({
|
|
381
367
|
appId: input.app.id,
|
|
382
368
|
env: input.environment.env,
|
|
383
|
-
...(input.
|
|
369
|
+
...(input.environment.kind === 'dev' && source !== undefined ? { dev: { head: source.commitSha } } : {}),
|
|
384
370
|
secrets: input.secrets,
|
|
385
371
|
vars: input.vars,
|
|
386
|
-
managedEnvironment,
|
|
372
|
+
managedEnvironment: input.managedEnvironment,
|
|
387
373
|
target: runtimeTarget,
|
|
388
374
|
artifact,
|
|
389
375
|
...(input.environment.access === undefined ? {} : { access: input.environment.access }),
|
|
@@ -395,6 +381,10 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
395
381
|
input.signal.throwIfAborted()
|
|
396
382
|
return { state: result.status, ...(result.artifactState === undefined ? {} : { artifactState: result.artifactState }) }
|
|
397
383
|
},
|
|
384
|
+
devEnvironments: {
|
|
385
|
+
stop: (input) => runningState(input, 'stop'),
|
|
386
|
+
start: (input) => runningState(input, 'start'),
|
|
387
|
+
},
|
|
398
388
|
cancel: async (input): Promise<ProviderCancelOutcome> => {
|
|
399
389
|
const signal = input.signal
|
|
400
390
|
signal.throwIfAborted()
|
|
@@ -412,17 +402,8 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
412
402
|
const version = await getVersion({ appVersionId: checkpoint.appVersionId, signal })
|
|
413
403
|
if (version === null) return { state: 'settled', outcome: 'failed' }
|
|
414
404
|
assertVersionCoordinates(input.environment, checkpoint.appVersionId, version)
|
|
415
|
-
// Cancellation never implies rollback: a version that activated first makes the run a success
|
|
416
|
-
|
|
417
|
-
if (version.status === ZEROPS_ACTIVE) {
|
|
418
|
-
try {
|
|
419
|
-
await reconcileActiveSchema(input, signal)
|
|
420
|
-
} catch {
|
|
421
|
-
signal.throwIfAborted()
|
|
422
|
-
return { state: 'pending' }
|
|
423
|
-
}
|
|
424
|
-
return { state: 'settled', outcome: 'succeeded' }
|
|
425
|
-
}
|
|
405
|
+
// Cancellation never implies rollback: a version that activated first makes the run a success.
|
|
406
|
+
if (version.status === ZEROPS_ACTIVE) return { state: 'settled', outcome: 'succeeded' }
|
|
426
407
|
if (version.status !== undefined && ZEROPS_TERMINAL.has(version.status)) return { state: 'settled', outcome: 'failed' }
|
|
427
408
|
await cancelBuildQuietly(checkpoint.appVersionId, signal)
|
|
428
409
|
// A build request that never landed leaves the version UPLOADING for good; the deadline settles it.
|
|
@@ -459,10 +440,7 @@ export const createZeropsControlProvider = (options: ZeropsControlProviderOption
|
|
|
459
440
|
}
|
|
460
441
|
if (version === null) return { state: 'failed' }
|
|
461
442
|
assertVersionCoordinates(input.environment, checkpoint.appVersionId, version)
|
|
462
|
-
if (version.status === ZEROPS_ACTIVE) {
|
|
463
|
-
await reconcileActiveSchema(input, signal)
|
|
464
|
-
return { state: 'succeeded' }
|
|
465
|
-
}
|
|
443
|
+
if (version.status === ZEROPS_ACTIVE) return { state: 'succeeded' }
|
|
466
444
|
if (version.status !== undefined && ZEROPS_TERMINAL.has(version.status)) return { state: 'failed' }
|
|
467
445
|
if (pastDeadline(input.startedAtMs)) {
|
|
468
446
|
await cancelBuildQuietly(checkpoint.appVersionId, signal)
|