@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { followLeaseLoss, isLeaseLoss, ProviderNamespaceError } from '@fabrika/provider-contract'
|
|
2
|
+
import type { InstallationBrand } from '@fabrika/proxy-core/brand'
|
|
2
3
|
import { encodeProxyManifestJson, FABRIKA_PROXY_MANIFEST_JSON, type ProxyManifest } from '@fabrika/proxy-core/manifest'
|
|
3
4
|
import {
|
|
4
5
|
readableContent,
|
|
@@ -11,7 +12,12 @@ import {
|
|
|
11
12
|
} from '../api'
|
|
12
13
|
import type { Sleeper } from '../collaborators'
|
|
13
14
|
import { prepareZeropsDescriptor } from '../descriptor'
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
ZEROPS_PROXY_MANIFEST_PATH,
|
|
17
|
+
zeropsProxyAcceptsBrand,
|
|
18
|
+
type ZeropsProxyManifestCarrier,
|
|
19
|
+
zeropsProxyManifestCarrier,
|
|
20
|
+
} from '../proxy-manifest-carrier'
|
|
15
21
|
import { isZeropsUploadDestination } from '../source'
|
|
16
22
|
import type { ZeropsSourcePort } from '../source/port'
|
|
17
23
|
import { deleteVersion } from '../version-attempt'
|
|
@@ -35,6 +41,8 @@ export interface ZeropsProxyRepublishInput {
|
|
|
35
41
|
readonly namespaceId: string
|
|
36
42
|
readonly target: ZeropsNamespaceTarget
|
|
37
43
|
readonly manifest: ProxyManifest
|
|
44
|
+
/** The installation's client logo (ADR-0066); added to the manifest only when the pinned proxy can parse it. */
|
|
45
|
+
readonly brand?: InstallationBrand | null
|
|
38
46
|
readonly source?: ZeropsSourcePort
|
|
39
47
|
readonly api: ZeropsProxyApi
|
|
40
48
|
readonly iamUrl: string
|
|
@@ -44,6 +52,11 @@ export interface ZeropsProxyRepublishInput {
|
|
|
44
52
|
/** Aborts with `lease_lost` when the namespace lease goes, and with `cancel_requested` when a deploy is cancelled. */
|
|
45
53
|
readonly signal: AbortSignal
|
|
46
54
|
readonly wait: 'started' | 'active'
|
|
55
|
+
/**
|
|
56
|
+
* Reuse the proxy's ACTIVE version when it was built from this pin with this manifest identity. A deploy and a
|
|
57
|
+
* registry change pass true; provisioning and reconcile rebuild, so an operator's repair always reaches the platform.
|
|
58
|
+
*/
|
|
59
|
+
readonly reuseActive: boolean
|
|
47
60
|
/** Persist the target; the caller fences the write on the namespace lease. */
|
|
48
61
|
readonly checkpoint: (target: ZeropsNamespaceTarget) => Promise<void>
|
|
49
62
|
readonly boundary: () => Promise<void>
|
|
@@ -88,6 +101,28 @@ const writeProxyIam = async (input: ZeropsProxyRepublishInput, serviceId: string
|
|
|
88
101
|
const proxySecretsDigest = async (secrets: ReadonlyMap<string, string>): Promise<string> =>
|
|
89
102
|
digest([...secrets].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([key, value]) => `${key}=${value}`).join('\n'))
|
|
90
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Whether the proxy already runs `manifestSha256` on this pin: the recorded deployment names both, and its version is
|
|
106
|
+
* still the service's ACTIVE one. Any doubt, a failed read included, answers false, and the proxy is rebuilt.
|
|
107
|
+
*/
|
|
108
|
+
const servesManifest = async (input: ZeropsProxyRepublishInput, target: ZeropsNamespaceTarget, manifestSha256: string): Promise<boolean> => {
|
|
109
|
+
const deployed = target.proxyDeployment
|
|
110
|
+
const pin = target.proxySource
|
|
111
|
+
if (
|
|
112
|
+
target.ready !== true || deployed === undefined || deployed.manifestSha256 !== manifestSha256
|
|
113
|
+
|| deployed.commitSha !== pin.commitSha || deployed.descriptorSha256 !== pin.descriptor.sha256
|
|
114
|
+
|| deployed.repository.owner !== pin.repository.owner || deployed.repository.name !== pin.repository.name
|
|
115
|
+
) return false
|
|
116
|
+
await input.boundary()
|
|
117
|
+
try {
|
|
118
|
+
const version = await input.api.getAppVersion({ appVersionId: deployed.appVersionId, signal: input.signal })
|
|
119
|
+
return version.id === deployed.appVersionId && version.status === ZEROPS_ACTIVE && version.serviceStackId === target.proxyServiceId
|
|
120
|
+
} catch {
|
|
121
|
+
input.signal.throwIfAborted()
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
91
126
|
/**
|
|
92
127
|
* Rebuild the namespace proxy with `manifest` (ADR-0044). The attempt is recorded as `proxyBuild` and never
|
|
93
128
|
* resumed: an inherited attempt not yet `triggered` is deleted, a triggered one is polled to a terminal state.
|
|
@@ -100,7 +135,11 @@ export async function republishProxy(input: ZeropsProxyRepublishInput): Promise<
|
|
|
100
135
|
await verifyZeropsNamespaceProxySource(pin)
|
|
101
136
|
// The pinned descriptor decides: a namespace kept on an older proxy revision still lifts the variable (ADR-0045).
|
|
102
137
|
const carrier = zeropsProxyManifestCarrier(pin.descriptor.contents)
|
|
103
|
-
|
|
138
|
+
// So does it for the logo: an older parser refuses the key, and the namespace keeps the mark until it is re-pinned.
|
|
139
|
+
const brand = input.brand ?? null
|
|
140
|
+
const manifest = encodeProxyManifestJson(
|
|
141
|
+
brand !== null && zeropsProxyAcceptsBrand(pin.descriptor.contents) ? { ...input.manifest, brand } : input.manifest,
|
|
142
|
+
)
|
|
104
143
|
// The attempt's identity covers the IAM configuration and the proxy secrets too, so rotating any forces a rebuild.
|
|
105
144
|
const proxySecrets = input.proxySecrets ?? new Map<string, string>()
|
|
106
145
|
const secrets = proxySecrets.size === 0 ? '' : `\n${await proxySecretsDigest(proxySecrets)}`
|
|
@@ -173,6 +212,8 @@ export async function republishProxy(input: ZeropsProxyRepublishInput): Promise<
|
|
|
173
212
|
if (inherited.manifestSha256 === manifestSha256) return target
|
|
174
213
|
}
|
|
175
214
|
|
|
215
|
+
if (input.reuseActive && target.proxyBuild === undefined && await servesManifest(input, target, manifestSha256)) return target
|
|
216
|
+
|
|
176
217
|
if (input.source === undefined) throw new Error('Zerops namespace source client is required')
|
|
177
218
|
const runId = crypto.randomUUID()
|
|
178
219
|
const resolved = await input.source.resolve({
|
|
@@ -1,36 +1,26 @@
|
|
|
1
|
-
// What a namespace proxy pinned to an older source cannot read. Its manifest parser is strict: one row of a form
|
|
2
|
-
// not know, or a CSP directive it does not know, refuses the whole manifest and blocks the namespace's next
|
|
3
|
-
// So a deploy that would publish either is refused before
|
|
4
|
-
// this control plane's own.
|
|
1
|
+
// What a namespace proxy pinned to an older source cannot read or serve. Its manifest parser is strict: one row of a form
|
|
2
|
+
// it does not know, or a CSP directive it does not know, refuses the whole manifest and blocks the namespace's next
|
|
3
|
+
// republish; and it has no terminal sign-out. So a deploy that would publish either, or a terminal, is refused before
|
|
4
|
+
// publication while the namespace's proxy source is not this control plane's own.
|
|
5
5
|
|
|
6
6
|
import type { ProviderApprovedAccess } from '@fabrika/provider-contract'
|
|
7
|
-
import type
|
|
8
|
-
import { contractProxyTable, type ProxyEndpointRow } from '@fabrika/proxy-core/manifest'
|
|
7
|
+
import { contractProxyTable, type OlderProxyReaders } from '@fabrika/proxy-core/manifest'
|
|
9
8
|
import type { ZeropsNamespaceProxySource } from './types'
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
* What the namespace proxy of one older platform release parses of an application's manifest entry: a row, and the
|
|
13
|
-
* entry's CSP directives, each by that release's own parser.
|
|
14
|
-
*/
|
|
15
|
-
export interface OlderProxyReader {
|
|
16
|
-
readonly row: (row: ProxyEndpointRow) => boolean
|
|
17
|
-
readonly csp: (csp: CspOrigins) => boolean
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The namespace proxies of the older releases this platform serves (ADR-0062). Control holds their parsers, as it holds
|
|
22
|
-
* their contract readers; a pin's release is not recorded, so what any of them reads is what the pin may read.
|
|
23
|
-
*/
|
|
24
|
-
export type OlderProxyReaders = readonly [OlderProxyReader, ...OlderProxyReader[]]
|
|
10
|
+
export type { OlderProxyReader, OlderProxyReaders } from '@fabrika/proxy-core/manifest'
|
|
25
11
|
|
|
26
12
|
/**
|
|
27
13
|
* What of the approved contract's manifest entry no older release's proxy parses, as the namespace publishes it: each
|
|
28
14
|
* such row by its id, and `csp` when no older proxy parses the entry's CSP (`workerSrc`, which only this release knows).
|
|
15
|
+
* A contract that declares a terminal adds `terminal` when no older proxy serves the sign-out its sessions end at
|
|
16
|
+
* (ADR-0067 §5).
|
|
29
17
|
*/
|
|
30
18
|
export function entriesNeedingCurrentProxy(access: ProviderApprovedAccess, olderProxies: OlderProxyReaders): string[] {
|
|
31
19
|
const table = contractProxyTable(access.contract, access.contractDigest)
|
|
32
20
|
const rows = table.endpoints.filter((row) => !olderProxies.some((proxy) => proxy.row(row))).map((row) => row.id)
|
|
33
|
-
|
|
21
|
+
const csp = olderProxies.some((proxy) => proxy.csp(table.csp)) ? [] : ['csp']
|
|
22
|
+
const terminal = access.contract.terminal === undefined || olderProxies.some((proxy) => proxy.signsOut) ? [] : ['terminal']
|
|
23
|
+
return [...rows, ...csp, ...terminal]
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
/** This control plane's own proxy source, and the parsers of the older releases a pinned proxy may run. */
|
|
@@ -58,7 +48,7 @@ export function assertNamespaceProxyReads(
|
|
|
58
48
|
return
|
|
59
49
|
}
|
|
60
50
|
throw new Error(
|
|
61
|
-
`the proxy of deployment namespace \`${namespace}\` is pinned to an older source than this platform's, and no older release's proxy reads ${
|
|
51
|
+
`the proxy of deployment namespace \`${namespace}\` is pinned to an older source than this platform's, and no older release's proxy reads or serves ${
|
|
62
52
|
rows.map((row) => `\`${row}\``).join(', ')
|
|
63
53
|
}; re-pin it with \`fabrika control namespaces update --namespace=${namespace} --proxy-source=current\`, then deploy again`,
|
|
64
54
|
)
|
package/src/namespace/types.ts
CHANGED
|
@@ -81,6 +81,8 @@ export interface ZeropsNamespaceOptions {
|
|
|
81
81
|
readonly iamUrl: string
|
|
82
82
|
/** Same credential IAM receives as `FABRIKA_IAM_PROXY_KEY`. */
|
|
83
83
|
readonly iamKey: string
|
|
84
|
+
/** The installation's own project, which no namespace may create into or adopt. Absent until `platform deploy` writes it. */
|
|
85
|
+
readonly installationProjectId?: string
|
|
84
86
|
readonly api: ZeropsApi
|
|
85
87
|
readonly sleep?: Sleeper
|
|
86
88
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// `fabrika namespace` on Zerops: the provider options, the plan, and the namespace each command hands Control. The
|
|
2
|
+
// `fabrika` command holds Control's origin and credential, and prints the result.
|
|
3
|
+
|
|
4
|
+
import type { JsonValue, ProviderDeploymentNamespace, ProviderNamespacePlan } from '@fabrika/provider-contract'
|
|
5
|
+
import type { NamespaceCli, NamespaceCommandContext, NamespaceCommandResult } from '@fabrika/provider-contract/app-cli'
|
|
6
|
+
import { parseZeropsCliArgs, type ZeropsCliArgs } from './cli-args'
|
|
7
|
+
import {
|
|
8
|
+
createZeropsNamespaceOperator,
|
|
9
|
+
decodeZeropsNamespaceProxySource,
|
|
10
|
+
verifyZeropsNamespaceProxySource,
|
|
11
|
+
zeropsNamespaceTargetCodec,
|
|
12
|
+
} from './namespace'
|
|
13
|
+
import type { ZeropsNamespacePostgres } from './namespace'
|
|
14
|
+
import { postgresSchema } from './namespace/codec'
|
|
15
|
+
|
|
16
|
+
const NAMESPACE_USAGE = `fabrika namespace — Zerops deployment namespaces
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
fabrika namespace plan --provider=zerops --id=<id> --env=<env> --preset=<cheap|mid|full> [namespace options]
|
|
20
|
+
fabrika namespace create --provider=zerops --id=<id> --env=<env> --preset=<cheap|mid|full> [namespace options] [--control-url=<url>]
|
|
21
|
+
fabrika namespace adopt --provider=zerops --id=<id> --env=<env> --preset=<cheap|mid|full> --project-id=<id> [namespace options] [--control-url=<url>]
|
|
22
|
+
fabrika namespace reconcile --provider=zerops --id=<id> [--control-url=<url>]
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--id=<id> Namespace id.
|
|
26
|
+
--env=<env> Environment.
|
|
27
|
+
--preset=<cheap|mid|full> Provider-owned namespace preset.
|
|
28
|
+
--exclusive-app=<id> App reserved by a full namespace.
|
|
29
|
+
--project-id=<id> Existing Zerops project id for adoption.
|
|
30
|
+
--project-name=<name> Zerops project name (create: default fabrika-<env>-<id>;
|
|
31
|
+
adopt: the existing project's name, default the namespace id).
|
|
32
|
+
--core-package=<tier> LIGHT or SERIOUS.
|
|
33
|
+
--public-access=<mode> custom-domain or zerops-subdomain.
|
|
34
|
+
--postgres-type=<type> Cheap-tier PostgreSQL type.
|
|
35
|
+
--postgres-profile=<profile> Cheap-tier PostgreSQL profile.
|
|
36
|
+
--control-url=<url> Control origin; or FABRIKA_CONTROL_URL.
|
|
37
|
+
--json Print the result as JSON instead of rows.
|
|
38
|
+
-h, --help Show this help.
|
|
39
|
+
|
|
40
|
+
\`plan\` prints one \`<path>\\t<value>\` row per field of the plan, keyed by its path in --json; a multi-line
|
|
41
|
+
value such as the proxy's descriptor file is shown by size only. The other commands print one
|
|
42
|
+
\`<id>\\t<env>\\t<state>\\t<exclusive app>\` row.
|
|
43
|
+
|
|
44
|
+
Mutating namespace commands read the Bearer credential from FABRIKA_CONTROL_TOKEN.
|
|
45
|
+
Plan/create/adopt require FABRIKA_ZEROPS_PROXY_SOURCE_JSON:
|
|
46
|
+
{"repository":{"owner":"...","name":"..."},"commitSha":"...","descriptor":{"path":"zerops.yaml","contents":"...","sha256":"..."}}
|
|
47
|
+
Reconcile uses the persisted namespace source.
|
|
48
|
+
`
|
|
49
|
+
|
|
50
|
+
const required = (value: string | undefined, label: string): string => {
|
|
51
|
+
if (value === undefined || value.trim() === '') {
|
|
52
|
+
throw new Error(`${label} is required`)
|
|
53
|
+
}
|
|
54
|
+
return value.trim()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const postgres = (args: ZeropsCliArgs): ZeropsNamespacePostgres | undefined => {
|
|
58
|
+
const type = args.postgresType
|
|
59
|
+
const profile = args.postgresProfile
|
|
60
|
+
if (type === undefined && profile === undefined) return undefined
|
|
61
|
+
if (type === undefined) {
|
|
62
|
+
throw new Error('--postgres-type is required with --postgres-profile')
|
|
63
|
+
}
|
|
64
|
+
const result = postgresSchema.safeParse({ type, profile })
|
|
65
|
+
if (result.success) return result.data
|
|
66
|
+
if (result.error.issues[0]?.path[0] === 'type') throw new Error('--postgres-type must be postgresql:ha@18 or postgresql:single@18')
|
|
67
|
+
throw new Error(`--postgres-profile ${profile} is not supported by ${type}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const planOptions = (args: ZeropsCliArgs): { [key: string]: JsonValue } | undefined => {
|
|
71
|
+
const values: { [key: string]: JsonValue } = {}
|
|
72
|
+
if (args.projectName !== undefined) values['projectName'] = required(args.projectName, '--project-name')
|
|
73
|
+
if (args.corePackage !== undefined) values['corePackage'] = required(args.corePackage, '--core-package')
|
|
74
|
+
if (args.publicAccess !== undefined) values['publicAccess'] = required(args.publicAccess, '--public-access')
|
|
75
|
+
const database = postgres(args)
|
|
76
|
+
if (database !== undefined) {
|
|
77
|
+
values['postgres'] = database.profile === undefined ? { type: database.type } : { type: database.type, profile: database.profile }
|
|
78
|
+
}
|
|
79
|
+
return Object.keys(values).length === 0 ? undefined : values
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const namespacePlan = async (
|
|
83
|
+
args: ZeropsCliArgs,
|
|
84
|
+
source: Readonly<Record<string, string | undefined>>,
|
|
85
|
+
): Promise<ProviderNamespacePlan> => {
|
|
86
|
+
// `FABRIKA_ZEROPS_*` because Zerops reserves the bare `ZEROPS_` prefix and refuses to store a custom
|
|
87
|
+
// variable using it (`userDataZeropsPrefixForbidden`).
|
|
88
|
+
const serialized = required(source['FABRIKA_ZEROPS_PROXY_SOURCE_JSON'], 'FABRIKA_ZEROPS_PROXY_SOURCE_JSON')
|
|
89
|
+
let value: unknown
|
|
90
|
+
try {
|
|
91
|
+
value = JSON.parse(serialized)
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error('FABRIKA_ZEROPS_PROXY_SOURCE_JSON must contain valid JSON')
|
|
94
|
+
}
|
|
95
|
+
const proxySource = decodeZeropsNamespaceProxySource(value)
|
|
96
|
+
await verifyZeropsNamespaceProxySource(proxySource)
|
|
97
|
+
const operator = createZeropsNamespaceOperator({ proxySource })
|
|
98
|
+
const options = planOptions(args)
|
|
99
|
+
return operator.plan({
|
|
100
|
+
id: required(args.namespaceId, '--id'),
|
|
101
|
+
env: required(args.env, '--env'),
|
|
102
|
+
preset: required(args.preset, '--preset'),
|
|
103
|
+
...(args.exclusiveAppId === undefined ? {} : { exclusiveAppId: required(args.exclusiveAppId, '--exclusive-app') }),
|
|
104
|
+
...(options === undefined ? {} : { options }),
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** One namespace command. Every input is validated before `context.control()` is read. */
|
|
109
|
+
const runZeropsNamespaceCommand = async (
|
|
110
|
+
subcommand: string,
|
|
111
|
+
argv: readonly string[],
|
|
112
|
+
context: NamespaceCommandContext,
|
|
113
|
+
): Promise<NamespaceCommandResult> => {
|
|
114
|
+
const args = parseZeropsCliArgs(['namespace', subcommand, ...argv])
|
|
115
|
+
if (subcommand === 'plan') {
|
|
116
|
+
return { plan: await namespacePlan(args, context.source) }
|
|
117
|
+
}
|
|
118
|
+
if (subcommand === 'reconcile') {
|
|
119
|
+
const id = required(args.namespaceId, '--id')
|
|
120
|
+
return { namespace: await context.control().reconcile(id) }
|
|
121
|
+
}
|
|
122
|
+
if (subcommand !== 'create' && subcommand !== 'adopt') {
|
|
123
|
+
throw new Error(`Unknown namespace command: ${subcommand}`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const planned = await namespacePlan(args, context.source)
|
|
127
|
+
const control = context.control()
|
|
128
|
+
if (subcommand === 'create') {
|
|
129
|
+
return { namespace: await control.create(planned.namespace) }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const projectId = required(args.projectId, '--project-id')
|
|
133
|
+
const decoded = zeropsNamespaceTargetCodec.decode(planned.namespace.target.payload)
|
|
134
|
+
const namespace: ProviderDeploymentNamespace = {
|
|
135
|
+
...planned.namespace,
|
|
136
|
+
target: {
|
|
137
|
+
provider: 'zerops',
|
|
138
|
+
version: zeropsNamespaceTargetCodec.version,
|
|
139
|
+
payload: zeropsNamespaceTargetCodec.encode({
|
|
140
|
+
...decoded,
|
|
141
|
+
// An adopted project keeps its own name, so the plan's default for a created one does not apply.
|
|
142
|
+
projectName: args.projectName ?? planned.namespace.id,
|
|
143
|
+
managed: false,
|
|
144
|
+
projectId,
|
|
145
|
+
ready: false,
|
|
146
|
+
}),
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
return { namespace: await control.adopt(namespace) }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const zeropsNamespaceCli: NamespaceCli = { usage: NAMESPACE_USAGE, run: runZeropsNamespaceCommand }
|
package/src/plan.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type FabrikaManifest, manifestServiceHostnames } from './manifest'
|
|
2
|
-
import type { ZeropsRuntimeTarget } from './types'
|
|
3
2
|
|
|
4
3
|
export type ZeropsStepKind =
|
|
5
4
|
| 'apply-import'
|
|
@@ -11,7 +10,6 @@ export type ZeropsStepKind =
|
|
|
11
10
|
| 'upload-source'
|
|
12
11
|
| 'trigger-build'
|
|
13
12
|
| 'await-version'
|
|
14
|
-
| 'reconcile-schema'
|
|
15
13
|
|
|
16
14
|
export interface ZeropsStep {
|
|
17
15
|
readonly id: string
|
|
@@ -30,7 +28,7 @@ export const resolveDeployHostname = (manifest: FabrikaManifest): string => {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
/** Build the provider-owned deploy order without touching the platform. */
|
|
33
|
-
export const buildZeropsPlan = (manifest: FabrikaManifest
|
|
31
|
+
export const buildZeropsPlan = (manifest: FabrikaManifest): readonly ZeropsStep[] => {
|
|
34
32
|
const serviceHostnames = manifestServiceHostnames(manifest)
|
|
35
33
|
const steps: ZeropsStep[] = []
|
|
36
34
|
|
|
@@ -48,7 +46,7 @@ export const buildZeropsPlan = (manifest: FabrikaManifest, target: ZeropsRuntime
|
|
|
48
46
|
steps.push({
|
|
49
47
|
id: 'raise-start-memory',
|
|
50
48
|
kind: 'raise-start-memory',
|
|
51
|
-
description: "Raise the deploy service's minimum RAM to what its init commands need",
|
|
49
|
+
description: "Raise the deploy service's minimum RAM to what its init commands need, and pin a dev environment to one container",
|
|
52
50
|
})
|
|
53
51
|
steps.push({
|
|
54
52
|
id: 'size-database',
|
|
@@ -59,9 +57,6 @@ export const buildZeropsPlan = (manifest: FabrikaManifest, target: ZeropsRuntime
|
|
|
59
57
|
steps.push({ id: 'upload-source', kind: 'upload-source', description: 'Upload the resolved repository snapshot' })
|
|
60
58
|
steps.push({ id: 'trigger-build', kind: 'trigger-build', description: 'Trigger the Zerops build+deploy pipeline' })
|
|
61
59
|
steps.push({ id: 'await-version', kind: 'await-version', description: 'Await the Zerops pipeline and relay its build log' })
|
|
62
|
-
if (manifest.app.schema !== undefined && target.propustkaUrl !== undefined) {
|
|
63
|
-
steps.push({ id: 'reconcile-schema', kind: 'reconcile-schema', description: 'Reconcile authz schema into IAM' })
|
|
64
|
-
}
|
|
65
60
|
|
|
66
61
|
return steps
|
|
67
62
|
}
|