@fabrika/provider-zerops 0.8.2 → 0.9.0-cloudflare.2
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 +6 -0
- package/src/api/types.ts +12 -0
- package/src/cli.ts +11 -0
- package/src/collaborators.ts +0 -14
- package/src/control.ts +33 -57
- package/src/execution.ts +113 -102
- package/src/installation/admin.ts +1 -1
- package/src/installation/deploy-options.ts +1 -2
- package/src/installation/deploy.ts +41 -75
- 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 +9 -6
- package/src/installation/hosts.ts +1 -7
- package/src/installation/index.ts +0 -13
- package/src/installation/init-options.ts +2 -1
- package/src/installation/install-environment.ts +42 -106
- package/src/installation/install-options.ts +3 -1
- package/src/installation/install-secrets.ts +24 -68
- package/src/installation/install.ts +8 -4
- package/src/installation/sidecar.ts +5 -26
- package/src/installation/topology/setups.ts +5 -0
- 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 +15 -3
- package/src/namespace/proxy-attempt.ts +43 -2
- package/src/namespace/proxy-reads.ts +12 -22
- 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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// so a pipeline or an agent drives init end to end. Its confirmations stay, answered by `--yes`.
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
assertPinnedTag,
|
|
9
10
|
CHECKOUT_FLAG,
|
|
10
11
|
type CommandEnv,
|
|
11
12
|
describeArgument,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
UNATTENDED_FLAG,
|
|
18
19
|
} from '@fabrika/installation-init'
|
|
19
20
|
import { parsePlatformHosts } from './placement-options'
|
|
20
|
-
import {
|
|
21
|
+
import { defaultCheckoutName, defaultSidecarRepo } from './sidecar'
|
|
21
22
|
|
|
22
23
|
/** The name every bypass in this platform is gated on, and therefore the one name an installation cannot take. */
|
|
23
24
|
const LOCAL_ENVIRONMENT = 'local'
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
INSTALLATION_SECRET_VARIABLES,
|
|
3
|
+
type InstallationEmail,
|
|
4
|
+
type InstallationSecrets,
|
|
5
|
+
installationServiceEnvironment,
|
|
6
|
+
type PlatformHosts,
|
|
7
|
+
platformOrigin,
|
|
8
|
+
type PlatformStorage,
|
|
9
|
+
} from '@fabrika/installation-init'
|
|
2
10
|
import { encodeProxyManifestJson } from '@fabrika/proxy-core/manifest'
|
|
3
11
|
import type { ZeropsNamespaceProxySource } from '../index'
|
|
4
12
|
import type { PlatformDeployService, ResolvedService } from './deploy'
|
|
5
|
-
import { type PlatformHosts, platformOrigin } from './hosts'
|
|
6
13
|
import { assertRunning, type InstallCollaborators } from './install-collaborators'
|
|
7
14
|
import { FABRIKA_ZEROPS_PROXY_SOURCE_JSON, serializeNamespaceProxySource } from './installation-source'
|
|
8
|
-
import type { InstallationSecrets } from './secrets'
|
|
9
15
|
import type { PlatformTier } from './topology/topology'
|
|
10
16
|
|
|
11
17
|
/** The private address the proxy dials IAM at inside the project, and pass 1's placeholder issuer. */
|
|
@@ -16,14 +22,7 @@ const PRIVATE_IAM_URL = `http://${PRIVATE_IAM_UPSTREAM}`
|
|
|
16
22
|
const postgresUrl = (service: string): string => `\${${service}_connectionTlsString}/\${${service}_dbName}?sslmode=require`
|
|
17
23
|
|
|
18
24
|
/** An object-storage service's coordinates, as the platform's own `${service_variable}` references. */
|
|
19
|
-
|
|
20
|
-
readonly bucket: string
|
|
21
|
-
readonly accessKeyId: string
|
|
22
|
-
readonly secretAccessKey: string
|
|
23
|
-
readonly endpoint: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const storageOf = (service: string): StorageReferences => ({
|
|
25
|
+
const storageOf = (service: string): PlatformStorage => ({
|
|
27
26
|
bucket: `\${${service}_bucketName}`,
|
|
28
27
|
accessKeyId: `\${${service}_accessKeyId}`,
|
|
29
28
|
secretAccessKey: `\${${service}_secretAccessKey}`,
|
|
@@ -72,51 +71,40 @@ export interface InstallationEnvInput {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
75
|
-
* EVERY variable this command places, per service
|
|
74
|
+
* EVERY variable this command places, per service: fabrika's (`installationServiceEnvironment`) over this
|
|
75
|
+
* project's own references, and control's Zerops variables after them.
|
|
76
76
|
*
|
|
77
77
|
* Pure, and exported, because the matrix is the thing most worth pinning: a name that is right on
|
|
78
|
-
* Cloudflare and wrong on Bun is a silent no-op rather than a failure.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* `FABRIKA_IAM_` prefix (`packages/iam/src/node/runtime.ts`). The prefixed spellings below are the
|
|
83
|
-
* ones that file actually reads; the unprefixed ones are the ones it actually reads too.
|
|
84
|
-
* • No key any service declares in its own `zerops.yaml` appears here — `PORT`,
|
|
85
|
-
* `FABRIKA_IAM_RPC_URL`, `FABRIKA_OPERATIONS_URL`, `FABRIKA_CONTROL_ASSETS_DIR`,
|
|
86
|
-
* `FABRIKA_CONTROL_URL`, `FABRIKA_PROXY_MANIFEST`, `FABRIKA_PROXY_PORT`. The env API refuses those
|
|
87
|
-
* (`provider-zerops/src/api.ts`), because the conflict cannot be resolved to a record id.
|
|
88
|
-
*
|
|
89
|
-
* The proxy manifest is deliberately absent: pass 1 writes an empty one and `deployPlatform` owns the
|
|
90
|
-
* real one, in the same run as the code it describes (ADR-0027).
|
|
78
|
+
* Cloudflare and wrong on Bun is a silent no-op rather than a failure. No key any service declares in its
|
|
79
|
+
* own `zerops.yaml` appears here — `PORT`, `FABRIKA_IAM_RPC_URL`, `FABRIKA_OPERATIONS_URL`,
|
|
80
|
+
* `FABRIKA_CONTROL_ASSETS_DIR`, `FABRIKA_CONTROL_URL`, `FABRIKA_PROXY_MANIFEST`, `FABRIKA_PROXY_PORT`. The env
|
|
81
|
+
* API refuses those (`provider-zerops/src/api.ts`), because the conflict cannot be resolved to a record id.
|
|
91
82
|
*/
|
|
92
83
|
export const installationServiceEnv = (input: InstallationEnvInput): ReadonlyMap<PlatformDeployService, ReadonlyMap<string, string>> => {
|
|
93
84
|
const { secrets } = input
|
|
94
|
-
const iamOrigin = platformOrigin(input.scheme, input.hosts.iam)
|
|
95
|
-
const operationsOrigin = platformOrigin(input.scheme, input.hosts.operations)
|
|
96
|
-
const platformDatabase = postgresUrl('db')
|
|
97
|
-
const runLogs = storageOf('storage')
|
|
98
85
|
const operationsData = OPERATIONS_DATA[input.tier]
|
|
99
|
-
const
|
|
86
|
+
const fabrika = installationServiceEnvironment({
|
|
87
|
+
environment: input.environment,
|
|
88
|
+
scheme: input.scheme,
|
|
89
|
+
hosts: input.hosts,
|
|
90
|
+
iamUpstream: PRIVATE_IAM_UPSTREAM,
|
|
91
|
+
secrets,
|
|
92
|
+
email: input.email,
|
|
93
|
+
data: {
|
|
94
|
+
iamDatabase: postgresUrl('db'),
|
|
95
|
+
controlDatabase: postgresUrl('db'),
|
|
96
|
+
operationsDatabase: postgresUrl(operationsData.database),
|
|
97
|
+
runLogs: storageOf('storage'),
|
|
98
|
+
operationsBlobs: storageOf(operationsData.storage),
|
|
99
|
+
},
|
|
100
|
+
})
|
|
100
101
|
const control = new Map([
|
|
101
|
-
|
|
102
|
-
['ENVIRONMENT', input.environment],
|
|
103
|
-
// A bare HOST, not an origin: `controlPublicOrigin` accepts either.
|
|
104
|
-
['FABRIKA_CONTROL_DOMAIN', input.hosts.control],
|
|
105
|
-
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
106
|
-
['OPERATIONS_ARTIFACT_ORIGIN', operationsOrigin],
|
|
107
|
-
['FABRIKA_CONTROL_RUN_LOGS_BUCKET', runLogs.bucket],
|
|
108
|
-
['FABRIKA_CONTROL_RUN_LOGS_ACCESS_KEY_ID', runLogs.accessKeyId],
|
|
109
|
-
['FABRIKA_CONTROL_RUN_LOGS_SECRET_ACCESS_KEY', runLogs.secretAccessKey],
|
|
110
|
-
['FABRIKA_CONTROL_RUN_LOGS_ENDPOINT', runLogs.endpoint],
|
|
111
|
-
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
112
|
-
['OPERATIONS_SYNC_KEY', secrets.operationsSyncKey],
|
|
113
|
-
['FABRIKA_CONTROL_VAULT_KEY', secrets.vaultKey],
|
|
114
|
-
['FABRIKA_IAM_PROVISIONING_KEY', secrets.provisioningKey],
|
|
102
|
+
...fabrika.control,
|
|
115
103
|
['FABRIKA_ZEROPS_CLIENT_ID', input.clientId],
|
|
116
104
|
[FABRIKA_ZEROPS_PROXY_SOURCE_JSON, serializeNamespaceProxySource(input.proxySource)],
|
|
117
105
|
// The PUBLIC IAM origin an application project's proxy reaches: there is no private network
|
|
118
106
|
// between Zerops projects (ADR-0006).
|
|
119
|
-
['FABRIKA_ZEROPS_PROXY_IAM_URL',
|
|
107
|
+
['FABRIKA_ZEROPS_PROXY_IAM_URL', platformOrigin(input.scheme, input.hosts.iam)],
|
|
120
108
|
['FABRIKA_ZEROPS_PROXY_IAM_KEY', secrets.proxyKey],
|
|
121
109
|
['FABRIKA_ZEROPS_ACCESS_TOKEN', input.zeropsAccessToken],
|
|
122
110
|
['FABRIKA_ZEROPS_PLATFORM_PROJECT_ID', input.projectId],
|
|
@@ -126,79 +114,27 @@ export const installationServiceEnv = (input: InstallationEnvInput): ReadonlyMap
|
|
|
126
114
|
// the CLI reads `FABRIKA_ZEROPS_API_URL`; they are different processes reading different sources.
|
|
127
115
|
control.set('FABRIKA_ZEROPS_API_BASE_URL', input.apiBaseUrl)
|
|
128
116
|
}
|
|
129
|
-
const email = input.email.kind === 'none' ? [] : [...input.email.variables]
|
|
130
|
-
for (const [key, value] of email) control.set(key, value)
|
|
131
117
|
return new Map<PlatformDeployService, ReadonlyMap<string, string>>([
|
|
132
|
-
[
|
|
133
|
-
|
|
134
|
-
new Map([
|
|
135
|
-
['FABRIKA_IAM_DATABASE_URL', platformDatabase],
|
|
136
|
-
['ENVIRONMENT', input.environment],
|
|
137
|
-
['ISSUER', iamOrigin],
|
|
138
|
-
// Password only, by decision: OIDC needs an identity provider the operator has not been
|
|
139
|
-
// asked for. Both are STATED because IAM's own defaults are the other way round — OIDC on,
|
|
140
|
-
// password off — and it refuses to boot with neither.
|
|
141
|
-
['FABRIKA_IAM_OIDC_ENABLED', 'false'],
|
|
142
|
-
['FABRIKA_IAM_PASSWORD_ENABLED', 'true'],
|
|
143
|
-
['FABRIKA_IAM_SIGNING_KEYS', secrets.signingKeys],
|
|
144
|
-
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
145
|
-
['FABRIKA_IAM_PROXY_KEY', secrets.proxyKey],
|
|
146
|
-
['FABRIKA_IAM_PROVISIONING_KEY', secrets.provisioningKey],
|
|
147
|
-
...email,
|
|
148
|
-
]),
|
|
149
|
-
],
|
|
150
|
-
[
|
|
151
|
-
'operations',
|
|
152
|
-
new Map([
|
|
153
|
-
['FABRIKA_OPERATIONS_DATABASE_URL', postgresUrl(operationsData.database)],
|
|
154
|
-
['FABRIKA_OPERATIONS_BLOB_BUCKET', operationsBlobs.bucket],
|
|
155
|
-
['FABRIKA_OPERATIONS_BLOB_ACCESS_KEY_ID', operationsBlobs.accessKeyId],
|
|
156
|
-
['FABRIKA_OPERATIONS_BLOB_SECRET_ACCESS_KEY', operationsBlobs.secretAccessKey],
|
|
157
|
-
['FABRIKA_OPERATIONS_BLOB_ENDPOINT', operationsBlobs.endpoint],
|
|
158
|
-
['FABRIKA_OPERATIONS_PUBLIC_HOST', input.hosts.operations],
|
|
159
|
-
['ENVIRONMENT', input.environment],
|
|
160
|
-
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
161
|
-
['OPERATIONS_SYNC_KEY', secrets.operationsSyncKey],
|
|
162
|
-
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
163
|
-
]),
|
|
164
|
-
],
|
|
118
|
+
['iam', fabrika.iam],
|
|
119
|
+
['operations', fabrika.operations],
|
|
165
120
|
['control', control],
|
|
166
|
-
[
|
|
167
|
-
'proxy',
|
|
168
|
-
new Map([
|
|
169
|
-
['ENVIRONMENT', input.environment],
|
|
170
|
-
// Compared byte-for-byte against a token's `iss`, so this is the PUBLIC issuer and not the
|
|
171
|
-
// private `iam:3000` pass 1 wrote.
|
|
172
|
-
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
173
|
-
['FABRIKA_IAM_KEY', secrets.proxyKey],
|
|
174
|
-
['FABRIKA_PROXY_SESSION_OWNER_JSON', JSON.stringify({ issuer: iamOrigin, upstream: PRIVATE_IAM_UPSTREAM })],
|
|
175
|
-
]),
|
|
176
|
-
],
|
|
121
|
+
['proxy', fabrika.proxy],
|
|
177
122
|
])
|
|
178
123
|
}
|
|
179
124
|
|
|
180
125
|
/**
|
|
181
|
-
* The keys whose VALUE this command generates, per service
|
|
126
|
+
* The keys whose VALUE this command generates, per service: fabrika's, and the two Zerops names on control
|
|
127
|
+
* that carry the proxy key and the minted token.
|
|
182
128
|
*
|
|
183
129
|
* Their presence on a live service is what "this project already holds an installation" means, and it is
|
|
184
130
|
* checked by KEY only — a value is never read back for comparison. `__tests__/install.test.ts` pins this
|
|
185
131
|
* against the matrix above, so a new generated secret cannot be added without appearing here.
|
|
186
132
|
*/
|
|
187
133
|
export const GENERATED_SECRET_KEYS: ReadonlyMap<PlatformDeployService, readonly string[]> = new Map<PlatformDeployService, readonly string[]>([
|
|
188
|
-
['iam',
|
|
189
|
-
['operations',
|
|
190
|
-
[
|
|
191
|
-
|
|
192
|
-
[
|
|
193
|
-
'FABRIKA_IAM_RPC_KEY',
|
|
194
|
-
'OPERATIONS_SYNC_KEY',
|
|
195
|
-
'FABRIKA_CONTROL_VAULT_KEY',
|
|
196
|
-
'FABRIKA_IAM_PROVISIONING_KEY',
|
|
197
|
-
'FABRIKA_ZEROPS_PROXY_IAM_KEY',
|
|
198
|
-
'FABRIKA_ZEROPS_ACCESS_TOKEN',
|
|
199
|
-
],
|
|
200
|
-
],
|
|
201
|
-
['proxy', ['FABRIKA_IAM_KEY']],
|
|
134
|
+
['iam', INSTALLATION_SECRET_VARIABLES.iam],
|
|
135
|
+
['operations', INSTALLATION_SECRET_VARIABLES.operations],
|
|
136
|
+
['control', [...INSTALLATION_SECRET_VARIABLES.control, 'FABRIKA_ZEROPS_PROXY_IAM_KEY', 'FABRIKA_ZEROPS_ACCESS_TOKEN']],
|
|
137
|
+
['proxy', INSTALLATION_SECRET_VARIABLES.proxy],
|
|
202
138
|
])
|
|
203
139
|
|
|
204
140
|
/**
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { builtinEmailProviders } from '@fabrika/email/providers'
|
|
12
12
|
import {
|
|
13
|
+
checkedSecretsFilePath,
|
|
13
14
|
describeArgument,
|
|
14
15
|
EMAIL_FLAGS,
|
|
15
16
|
type InstallationEmail,
|
|
@@ -17,10 +18,11 @@ import {
|
|
|
17
18
|
readFlag,
|
|
18
19
|
readInstallationEmail,
|
|
19
20
|
requiredSetting,
|
|
21
|
+
SECRETS_FILE_FLAG,
|
|
22
|
+
SECRETS_FILE_VARIABLE,
|
|
20
23
|
setting,
|
|
21
24
|
UNATTENDED_FLAG,
|
|
22
25
|
} from '@fabrika/installation-init'
|
|
23
|
-
import { checkedSecretsFilePath, SECRETS_FILE_FLAG, SECRETS_FILE_VARIABLE } from './install-secrets'
|
|
24
26
|
import { parsePlatformScheme } from './placement-options'
|
|
25
27
|
import { readInstallationSourceDir } from './source-options'
|
|
26
28
|
import type { PlatformTier } from './topology/topology'
|
|
@@ -1,36 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// What a Zerops installation's secrets file records.
|
|
2
2
|
//
|
|
3
3
|
// The services hold the same values in Zerops, but stored SENSITIVE, and Zerops hands a sensitive value
|
|
4
|
-
// back only to a token in sudo mode (measured 2026-10-15).
|
|
5
|
-
//
|
|
4
|
+
// back only to a token in sudo mode (measured 2026-10-15). The file's path rule, quoting and 0600 write
|
|
5
|
+
// are `@fabrika/installation-init`'s `secrets-file.ts`.
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { open } from 'node:fs/promises'
|
|
9
|
-
import { isAbsolute, relative, resolve } from 'node:path'
|
|
10
|
-
import type { InstallationSecrets } from './secrets'
|
|
11
|
-
|
|
12
|
-
/** `--secrets-file`: where the local copy goes. A new file, outside the source checkout. */
|
|
13
|
-
export const SECRETS_FILE_FLAG = '--secrets-file'
|
|
14
|
-
export const SECRETS_FILE_VARIABLE = 'FABRIKA_PLATFORM_SECRETS_FILE'
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Settle the path before anything is contacted.
|
|
18
|
-
*
|
|
19
|
-
* Refused when it exists, because overwriting one would destroy another installation's only copy, and
|
|
20
|
-
* when it is inside the source checkout, where it would dirty the checkout `platform deploy` uploads
|
|
21
|
-
* and could be committed.
|
|
22
|
-
*/
|
|
23
|
-
export const checkedSecretsFilePath = (path: string, sourceDir: string): string => {
|
|
24
|
-
const absolute = resolve(path)
|
|
25
|
-
const fromSource = relative(resolve(sourceDir), absolute)
|
|
26
|
-
if (fromSource === '' || (!fromSource.startsWith('..') && !isAbsolute(fromSource))) {
|
|
27
|
-
throw new Error(`${SECRETS_FILE_FLAG}=${path} is inside the source checkout — name a path outside it, so it is never uploaded or committed`)
|
|
28
|
-
}
|
|
29
|
-
if (existsSync(absolute)) {
|
|
30
|
-
throw new Error(`${SECRETS_FILE_FLAG}=${path} already exists — it may be another installation's only copy; name a new file`)
|
|
31
|
-
}
|
|
32
|
-
return absolute
|
|
33
|
-
}
|
|
7
|
+
import { type InstallationSecrets, renderSecretsFile } from '@fabrika/installation-init'
|
|
34
8
|
|
|
35
9
|
/** What the file records beside the credentials, so it identifies its installation on its own. */
|
|
36
10
|
export interface InstallationSecretsRecord {
|
|
@@ -43,47 +17,29 @@ export interface InstallationSecretsRecord {
|
|
|
43
17
|
}
|
|
44
18
|
|
|
45
19
|
/**
|
|
46
|
-
* The file's text
|
|
20
|
+
* The file's text, named as the commands that read it name each value.
|
|
47
21
|
*
|
|
48
22
|
* `FABRIKA_ZEROPS_ACCESS_TOKEN` is the INTEGRATION token, which is what `platform init` and
|
|
49
23
|
* `platform deploy` take; the personal token the install ran with is not written.
|
|
50
24
|
*/
|
|
51
|
-
export const
|
|
25
|
+
export const renderInstallationSecretsFile = (record: InstallationSecretsRecord): string => {
|
|
52
26
|
const { secrets } = record
|
|
53
|
-
|
|
54
|
-
[
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
[
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return [
|
|
72
|
-
`# fabrika on Zerops — installation \`${record.environment}\`, project ${record.projectId}.`,
|
|
73
|
-
'# Written by `fabrika platform install`. Every value below is a credential: keep this file private.',
|
|
74
|
-
'# Zerops holds the same values as sensitive variables and does not hand them back.',
|
|
75
|
-
'# Load it in a subshell: ( set -a && . <this file> && set +a && fabrika … )',
|
|
76
|
-
...lines,
|
|
77
|
-
'',
|
|
78
|
-
].join('\n')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Create the file with mode 0600, failing rather than replacing one that appeared since the check. */
|
|
82
|
-
export const writeSecretsFile = async (path: string, text: string): Promise<void> => {
|
|
83
|
-
const file = await open(path, 'wx', 0o600)
|
|
84
|
-
try {
|
|
85
|
-
await file.writeFile(text)
|
|
86
|
-
} finally {
|
|
87
|
-
await file.close()
|
|
88
|
-
}
|
|
27
|
+
return renderSecretsFile(
|
|
28
|
+
[
|
|
29
|
+
`fabrika on Zerops — installation \`${record.environment}\`, project ${record.projectId}.`,
|
|
30
|
+
'Written by `fabrika platform install`. Every value below is a credential: keep this file private.',
|
|
31
|
+
'Zerops holds the same values as sensitive variables and does not hand them back.',
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
['FABRIKA_ZEROPS_PROJECT_ID', record.projectId],
|
|
35
|
+
['FABRIKA_ZEROPS_CLIENT_ID', record.clientId],
|
|
36
|
+
['FABRIKA_IAM_PROVISIONING_KEY', secrets.provisioningKey],
|
|
37
|
+
['FABRIKA_ZEROPS_ACCESS_TOKEN', record.zeropsAccessToken],
|
|
38
|
+
['FABRIKA_IAM_RPC_KEY', secrets.rpcKey],
|
|
39
|
+
['FABRIKA_IAM_PROXY_KEY', secrets.proxyKey],
|
|
40
|
+
['OPERATIONS_SYNC_KEY', secrets.operationsSyncKey],
|
|
41
|
+
['FABRIKA_CONTROL_VAULT_KEY', secrets.vaultKey],
|
|
42
|
+
['FABRIKA_IAM_SIGNING_KEYS', secrets.signingKeys],
|
|
43
|
+
],
|
|
44
|
+
)
|
|
89
45
|
}
|
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
import {
|
|
6
6
|
action as consoleAction,
|
|
7
7
|
confirmations,
|
|
8
|
+
generateInstallationSecrets,
|
|
8
9
|
info as consoleInfo,
|
|
10
|
+
type InstallationSecrets,
|
|
9
11
|
ok as consoleOk,
|
|
12
|
+
type PlatformHosts,
|
|
13
|
+
platformOrigin,
|
|
10
14
|
step as consoleStep,
|
|
11
15
|
warn as consoleWarn,
|
|
16
|
+
writeSecretsFile,
|
|
12
17
|
} from '@fabrika/installation-init'
|
|
13
18
|
import type { SchemaReconciler } from '@fabrika/provider-contract'
|
|
14
19
|
import { createZeropsApi, defaultSleep, readableContent, type ZeropsNamespaceProxySource } from '../index'
|
|
@@ -24,7 +29,7 @@ import {
|
|
|
24
29
|
type ResolvedService,
|
|
25
30
|
} from './deploy'
|
|
26
31
|
import { PLATFORM_PROXY_MANIFEST_TEMPLATE } from './generated/platform-proxy-manifest'
|
|
27
|
-
import { derivePlatformHosts,
|
|
32
|
+
import { derivePlatformHosts, ZEROPS_SUBDOMAIN_VARIABLE } from './hosts'
|
|
28
33
|
import { checkedEnvironmentName } from './init-options'
|
|
29
34
|
import { assertRunning, type InstallCollaborators, type InstallPrompts } from './install-collaborators'
|
|
30
35
|
import { GENERATED_SECRET_KEYS, installationServiceEnv, PASS_ONE_PROXY_MANIFEST, passOneProxyEnv, writeServiceEnv } from './install-environment'
|
|
@@ -39,11 +44,10 @@ import {
|
|
|
39
44
|
resolvePlatformServices,
|
|
40
45
|
serviceOf,
|
|
41
46
|
} from './install-provisioning'
|
|
42
|
-
import {
|
|
47
|
+
import { renderInstallationSecretsFile } from './install-secrets'
|
|
43
48
|
import { explainMintFailure } from './install-token'
|
|
44
49
|
import { prepareInstallationSource, prepareNamespaceProxySource, validateInstallationSetups } from './installation-source'
|
|
45
50
|
import type { PreparedPlatformSource } from './platform-source'
|
|
46
|
-
import { generateInstallationSecrets, type InstallationSecrets } from './secrets'
|
|
47
51
|
|
|
48
52
|
export type { InstallCollaborators, InstallPrompts } from './install-collaborators'
|
|
49
53
|
export { GENERATED_SECRET_KEYS, type InstallationEnvInput, installationServiceEnv } from './install-environment'
|
|
@@ -158,7 +162,7 @@ const installPreparedPlatform = async (
|
|
|
158
162
|
log.step('Store the credentials locally')
|
|
159
163
|
// BEFORE the first one is placed: Zerops keeps them sensitive and will not hand them back.
|
|
160
164
|
await collaborators.storeSecrets(
|
|
161
|
-
|
|
165
|
+
renderInstallationSecretsFile({ environment, projectId: project.id, clientId: input.clientId, secrets, zeropsAccessToken: token.token }),
|
|
162
166
|
)
|
|
163
167
|
log.ok(`wrote ${input.secretsFile} (mode 0600) — every credential this installation was created with; nothing else holds a readable copy`)
|
|
164
168
|
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
//
|
|
13
13
|
// `fabrika.ref` names a published tag of `contember/fabrika-platform`, never a branch: a namespace
|
|
14
14
|
// proxy and an installation both build from tags, so a moving ref would make "which version is this
|
|
15
|
-
// installation running" unanswerable. `assertPinnedTag` refuses a branch
|
|
16
|
-
// the generated workflow refuses one again at run time — two gates,
|
|
17
|
-
// after creation and the second gate is the only one that sees what
|
|
15
|
+
// installation running" unanswerable. `assertPinnedTag` (`@fabrika/installation-init`) refuses a branch
|
|
16
|
+
// when init writes the file, and the generated workflow refuses one again at run time — two gates,
|
|
17
|
+
// because the file is operator-owned after creation and the second gate is the only one that sees what
|
|
18
|
+
// the operator later wrote.
|
|
18
19
|
//
|
|
19
20
|
// ── The templates are not Cloudflare's ────────────────────────────────────────────────────────────
|
|
20
21
|
//
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
// pipeline; this one runs a single step because on Zerops the order is inside the command (ADR-0027).
|
|
24
25
|
// Cloudflare builds a runner image and needs a GitHub App; Zerops has no runner at all (ADR-0003).
|
|
25
26
|
|
|
27
|
+
import { assertPinnedTag } from '@fabrika/installation-init'
|
|
26
28
|
import { resolve } from 'node:path'
|
|
27
29
|
import { z } from 'zod'
|
|
28
30
|
|
|
@@ -47,29 +49,6 @@ export interface SidecarValues {
|
|
|
47
49
|
readonly fabrikaRef: string
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
/**
|
|
51
|
-
* A published release tag: `v` and a semantic version.
|
|
52
|
-
*
|
|
53
|
-
* The same shape `scripts/release.ts` accepts, restated rather than imported — that script is not a
|
|
54
|
-
* package and this check has to hold in a published tarball.
|
|
55
|
-
*/
|
|
56
|
-
const TAG_PATTERN = /^v(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
|
|
57
|
-
|
|
58
|
-
/** The pinned ref, or an error naming what was found. A branch is refused, deliberately and loudly. */
|
|
59
|
-
export const assertPinnedTag = (ref: string): string => {
|
|
60
|
-
const trimmed = ref.trim()
|
|
61
|
-
if (trimmed === '') {
|
|
62
|
-
throw new Error('a published fabrika tag is required, for example v0.1.0')
|
|
63
|
-
}
|
|
64
|
-
if (!TAG_PATTERN.test(trimmed)) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`\`${trimmed}\` is not a published tag. A sidecar repository pins a TAG such as v0.1.0 and never a branch: `
|
|
67
|
-
+ 'an installation that tracked a moving ref could not say which version it runs (ADR-0025)',
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
return trimmed
|
|
71
|
-
}
|
|
72
|
-
|
|
73
52
|
/** Substitute every placeholder in a template. */
|
|
74
53
|
const render = async (name: string, values: SidecarValues): Promise<string> => {
|
|
75
54
|
const raw = await Bun.file(resolve(TEMPLATES, name)).text()
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
// arrives fully resolved). Only the proxy needs this, and it declares it explicitly below.
|
|
93
93
|
|
|
94
94
|
import type { ZeropsHealthCheckSpec, ZeropsReadinessCheckSpec, ZeropsYamlSetupSpec, ZeropsYamlSpec } from '../../index'
|
|
95
|
+
import { FABRIKA_PROXY_MANIFEST_BRAND } from '../../proxy-manifest-carrier'
|
|
95
96
|
|
|
96
97
|
// ── Readiness and liveness: two mechanisms, two questions ──────────────────────────────────────────
|
|
97
98
|
//
|
|
@@ -401,6 +402,10 @@ const proxy: ZeropsYamlSetupSpec = {
|
|
|
401
402
|
// it is the archive's data file (see the header).
|
|
402
403
|
envVariables: {
|
|
403
404
|
FABRIKA_PROXY_SESSION_OWNER_JSON: '${RUNTIME_FABRIKA_PROXY_SESSION_OWNER_JSON}',
|
|
405
|
+
// A marker, read by nothing at build time: a namespace pinned to this source has a manifest parser that knows
|
|
406
|
+
// `brand`, so Control may send the installation's client logo (ADR-0066). An older pin's parser refuses the
|
|
407
|
+
// key and fails its build, so it is sent only where this is (`zeropsProxyAcceptsBrand`).
|
|
408
|
+
[FABRIKA_PROXY_MANIFEST_BRAND]: '1',
|
|
404
409
|
},
|
|
405
410
|
// CGO off makes the binary static, which is what lets it run on Alpine/musl.
|
|
406
411
|
prepareCommands: ['CGO_ENABLED=0 GOBIN=/tmp/gobin go install github.com/caddyserver/caddy/v2/cmd/caddy@v2.10.2'],
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// The pin is checked BEFORE anything leaves this machine: `assertPinnedTag` refuses a branch or a SHA
|
|
9
9
|
// (ADR-0025) without a network call, so `--to=main` fails on the spot rather than after a clone.
|
|
10
10
|
|
|
11
|
-
import { describeArgument, readFlag } from '@fabrika/installation-init'
|
|
12
|
-
import {
|
|
11
|
+
import { assertPinnedTag, describeArgument, readFlag } from '@fabrika/installation-init'
|
|
12
|
+
import { defaultCheckoutDir } from './sidecar'
|
|
13
13
|
|
|
14
14
|
/** Where the sidecar repository this command edits is found. */
|
|
15
15
|
export type UpgradeSidecar =
|