@wtfalch/foundry 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/declare.js +4 -1
- package/dist/bootstrap/versions.d.ts +2 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +3 -0
- package/dist/migrations/0065_native_build_runs.sql +32 -0
- package/dist/migrations/0066_org_kosmos.sql +10 -0
- package/dist/migrations/0067_requested_changes_kosmos.sql +6 -0
- package/dist/migrations/0068_platform_registry.sql +17 -0
- package/dist/modules/sign-in.js +1 -1
- package/dist/native-build.d.ts +50 -0
- package/dist/native-build.js +72 -0
- package/dist/org.d.ts +22 -3
- package/dist/org.js +25 -2
- package/dist/plan/agora.d.ts +1 -1
- package/dist/plan/agora.js +4 -0
- package/dist/plan/archon-blue-green.d.ts +5 -0
- package/dist/plan/archon-blue-green.js +2 -0
- package/dist/plan/blue-green-apply.d.ts +5 -0
- package/dist/plan/blue-green-apply.js +197 -22
- package/dist/plan/blue-green.d.ts +80 -9
- package/dist/plan/blue-green.js +115 -14
- package/dist/plan/boule.d.ts +1 -1
- package/dist/plan/boule.js +2 -0
- package/dist/plan/broker-entries.d.ts +9 -0
- package/dist/plan/broker-entries.js +29 -0
- package/dist/plan/deploy-health.js +9 -2
- package/dist/plan/desktop-callback.js +4 -1
- package/dist/plan/diff.d.ts +5 -0
- package/dist/plan/diff.js +184 -63
- package/dist/plan/edge-access.d.ts +2 -3
- package/dist/plan/edge-access.js +8 -0
- package/dist/plan/executor.d.ts +111 -19
- package/dist/plan/executor.js +578 -72
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/index.js +1 -0
- package/dist/plan/jobs-scheduler-http.d.ts +2 -0
- package/dist/plan/jobs-scheduler-http.js +11 -0
- package/dist/plan/kosmos.d.ts +56 -0
- package/dist/plan/kosmos.js +84 -0
- package/dist/plan/locked-hostnames.js +5 -2
- package/dist/plan/org-names.d.ts +3 -3
- package/dist/plan/org-names.js +3 -1
- package/dist/plan/org-postgres-migrate.js +1 -0
- package/dist/plan/own-limits.d.ts +12 -0
- package/dist/plan/own-limits.js +2 -0
- package/dist/plan/plan-apply.d.ts +51 -1
- package/dist/plan/plan-apply.js +298 -25
- package/dist/plan/platform-diff.d.ts +6 -0
- package/dist/plan/platform-diff.js +74 -0
- package/dist/plan/platform-org.d.ts +2 -0
- package/dist/plan/platform-providers.d.ts +46 -1
- package/dist/plan/platform-providers.js +162 -1
- package/dist/plan/platform-registry-keys.d.ts +58 -0
- package/dist/plan/platform-registry-keys.js +69 -0
- package/dist/plan/platform-registry.d.ts +67 -0
- package/dist/plan/platform-registry.js +298 -0
- package/dist/plan/post-logout-uris.d.ts +1 -1
- package/dist/plan/post-logout-uris.js +5 -2
- package/dist/plan/providers-readers.js +20 -1
- package/dist/plan/release-host.d.ts +31 -2
- package/dist/plan/release-host.js +114 -6
- package/dist/plan/stage-copy-host.d.ts +104 -0
- package/dist/plan/stage-copy-host.js +259 -0
- package/dist/plan/stage-copy.js +44 -0
- package/dist/plan/stage-env.d.ts +9 -0
- package/dist/plan/stage-env.js +38 -0
- package/dist/plan/stage-guards.d.ts +57 -1
- package/dist/plan/stage-guards.js +106 -0
- package/dist/plan/stage-isolation.js +56 -0
- package/dist/plan/stage-services.d.ts +93 -0
- package/dist/plan/stage-services.js +153 -0
- package/dist/plan/stage-signin.d.ts +152 -0
- package/dist/plan/stage-signin.js +133 -0
- package/dist/plan/stage-sink.d.ts +2 -0
- package/dist/plan/stage-sink.js +6 -1
- package/dist/platform.d.ts +7 -1
- package/dist/platform.js +1 -0
- package/dist/release/decide.d.ts +12 -0
- package/dist/release/decide.js +37 -2
- package/dist/release/migration-compatibility.d.ts +66 -0
- package/dist/release/migration-compatibility.js +340 -0
- package/dist/release/run.d.ts +71 -1
- package/dist/release/run.js +92 -8
- package/dist/release/state.d.ts +14 -1
- package/dist/release/state.js +43 -0
- package/dist/release/types.d.ts +20 -0
- package/dist/release/types.js +2 -0
- package/dist/requested-changes.d.ts +11 -2
- package/dist/requested-changes.js +13 -1
- package/dist/slug.d.ts +1 -1
- package/dist/slug.js +11 -2
- package/dist/store/postgres.js +21 -0
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.js +10 -0
- package/package.json +3 -2
- package/versions.digests.json +1 -0
- package/versions.json +1 -0
package/dist/plan/blue-green.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { VERSIONS, pinnedRef } from '../bootstrap/versions.js';
|
|
3
|
-
import {
|
|
3
|
+
import { kosmosOn } from '../org.js';
|
|
4
|
+
import { ZITADEL_ORG_SCOPE_KEY, agoraHostnameFor } from './agora.js';
|
|
4
5
|
import { bouleHostnameFor } from './boule.js';
|
|
5
6
|
import { serverConnectorScopeKey } from './cloudflared-connector.js';
|
|
6
7
|
import { entryHostnames, tunnelIngressHostnames, wildcardHostname } from './hostnames.js';
|
|
8
|
+
import { kosmosHostnameFor } from './kosmos.js';
|
|
7
9
|
import { appNameFor, appStepKey } from './platform-diff.js';
|
|
8
10
|
import { remoteDatabaseAppliedFor } from './remote-database.js';
|
|
9
11
|
import { buildStageEnv } from './stage-env.js';
|
|
12
|
+
import { stageHostname } from './stage-guards.js';
|
|
10
13
|
import { assertStageIsolated, envClass } from './stage-isolation.js';
|
|
11
14
|
import { STAGE_SINK_VERSION } from './stage-sink.js';
|
|
12
15
|
export const COLORS = ['blue', 'green'];
|
|
@@ -38,6 +41,8 @@ export const bgKey = {
|
|
|
38
41
|
digest: (id, color) => `bg.${id}.digest.${color}`,
|
|
39
42
|
weights: (id) => `bg.${id}.weights`,
|
|
40
43
|
stage: (id) => `bg.${id}.stage`,
|
|
44
|
+
/** The last `deploy_stage`: `<ISO time>|ok|<commit>` or `<ISO time>|failed|<step>`. */
|
|
45
|
+
stageDeploy: (id) => `bg.${id}.stagedeploy`,
|
|
41
46
|
window: (id) => `bg.${id}.window`,
|
|
42
47
|
held: (id, digest) => `bg.${id}.held.${digest}`,
|
|
43
48
|
/** The weights the load balancer on `server` was last written with. */
|
|
@@ -64,8 +69,29 @@ export const bgKey = {
|
|
|
64
69
|
stageIsolation: (id) => `bg.${id}.stageisolation`,
|
|
65
70
|
/** The id of the Access gate over the stage host: staff only, no bypass, no service-token rule. */
|
|
66
71
|
stageGate: (id) => `bg.${id}.stagegate`,
|
|
72
|
+
/** The hash of the sorted email list last written to the stage gate (owners plus the people holding `releases:preview`). */
|
|
73
|
+
stageGateMembers: (id) => `bg.${id}.stagegatemembers`,
|
|
67
74
|
/** `ok:<hash>` once every apply-time probe was rejected; `notrun:<names>` or `failed:<names>` otherwise. */
|
|
68
75
|
stageProbes: (id) => `bg.${id}.stageprobes`,
|
|
76
|
+
/** Stage's own sign-in client: its ZITADEL app id, and its client id (public, not a secret). */
|
|
77
|
+
stageClient: (id) => `bg.${id}.stageclient`,
|
|
78
|
+
stageClientId: (id) => `bg.${id}.stageclientid`,
|
|
79
|
+
/** The held ref of stage's app key, recorded and persisted as soon as it is minted (no `:`: undelivered). */
|
|
80
|
+
stageSignInKey: (id) => `bg.${id}.stagesigninkey`,
|
|
81
|
+
/** `{name, organizationId}` of stage's broker entry, so org destroy can remove it. No key. */
|
|
82
|
+
stageBroker: (id) => `bg.${id}.stagebroker`,
|
|
83
|
+
/** `ok:<hash of the client, app and broker entry ids>` or `none`; the copy waits for it. */
|
|
84
|
+
stageSignIn: (id) => `bg.${id}.stagesignin`,
|
|
85
|
+
/** `none` (production's app uses no files, ai or jobs) or `ok:<hash>` once every service it uses is held by stage or listed absent. */
|
|
86
|
+
stageServices: (id) => `bg.${id}.stageservices`,
|
|
87
|
+
/** `ok:<hash of the ids>` or `absent:<service>:<reason>` for one service (`files`, `ai` or `jobs`). */
|
|
88
|
+
stageService: (id, service) => `bg.${id}.stage${service}`,
|
|
89
|
+
/** The id of stage's own organisation (files, ai) or project (jobs). An id, not a secret. */
|
|
90
|
+
stageServiceOrg: (id, service) => `bg.${id}.stage${service}org`,
|
|
91
|
+
/** The name stage's project or organisation was made under, when the service is deleted by name (jobs). */
|
|
92
|
+
stageServiceName: (id, service) => `bg.${id}.stage${service}name`,
|
|
93
|
+
/** `{ids, refs}` of stage's keys in that service: key ids and held refs, never a value. */
|
|
94
|
+
stageServiceKeys: (id, service) => `bg.${id}.stage${service}keys`,
|
|
69
95
|
/** `applied` once the stage app holds the sink's mail env. The copy refuses without it. */
|
|
70
96
|
stageSink: (id) => `bg.${id}.stagesink`,
|
|
71
97
|
/** `<ISO time>|<dump execution id>|ok`, written by a finished copy of production into stage. */
|
|
@@ -165,7 +191,7 @@ export function reportingEnv(input) {
|
|
|
165
191
|
{ key: REPORTING_ENV.healthToken, value: input.healthToken },
|
|
166
192
|
];
|
|
167
193
|
}
|
|
168
|
-
export const BUILTIN_APPS = ['boule', 'agora'];
|
|
194
|
+
export const BUILTIN_APPS = ['boule', 'agora', 'kosmos'];
|
|
169
195
|
/** What foundry builds each built-in app from; `executor.ts` creates the app from the same values. */
|
|
170
196
|
export const BUILTIN_APP = {
|
|
171
197
|
boule: {
|
|
@@ -185,7 +211,31 @@ export const BUILTIN_APP = {
|
|
|
185
211
|
database: false,
|
|
186
212
|
reporting: false,
|
|
187
213
|
},
|
|
214
|
+
kosmos: {
|
|
215
|
+
repo: 'wtfalch/kosmos',
|
|
216
|
+
dockerfile: '/Dockerfile',
|
|
217
|
+
port: 3000,
|
|
218
|
+
healthPath: '/api/health',
|
|
219
|
+
database: true,
|
|
220
|
+
reporting: true,
|
|
221
|
+
},
|
|
188
222
|
};
|
|
223
|
+
/** `builtinSubject`'s hostname for each built-in app, kept beside {@link BUILTIN_APP} rather than a third switch. */
|
|
224
|
+
function builtinHostnameFor(org, app) {
|
|
225
|
+
if (app === 'boule')
|
|
226
|
+
return bouleHostnameFor(org);
|
|
227
|
+
if (app === 'agora')
|
|
228
|
+
return agoraHostnameFor(org);
|
|
229
|
+
return kosmosHostnameFor(org);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Whether `org` declares blue/green for a built-in app: Boule and Agora opt into it through their own
|
|
233
|
+
* `blueGreen` field (they already run as a single copy otherwise); kosmos has no single-copy mode -- turning
|
|
234
|
+
* it on (`org.kosmos.enabled`) always runs it blue/green, docs/adr/0001 rule 1.
|
|
235
|
+
*/
|
|
236
|
+
function builtinDeclaresBlueGreen(org, app) {
|
|
237
|
+
return app === 'kosmos' ? kosmosOn(org) : org[app]?.blueGreen !== undefined;
|
|
238
|
+
}
|
|
189
239
|
/**
|
|
190
240
|
* A hosting entry that declares `blueGreen`, as a subject; `null` when it does not or cannot run.
|
|
191
241
|
* `recorded` names the blue app: an app that ran before blue/green keeps its own name (see {@link liveAppNameFor}).
|
|
@@ -213,13 +263,14 @@ export function entrySubject(org, entry, recorded) {
|
|
|
213
263
|
};
|
|
214
264
|
}
|
|
215
265
|
/**
|
|
216
|
-
* The company's Boule or
|
|
217
|
-
* its name `<slug>-<app>` and is blue, like
|
|
266
|
+
* The company's Boule, Agora or kosmos as a subject; `null` when it does not declare blue/green (see
|
|
267
|
+
* {@link builtinDeclaresBlueGreen}). The running app keeps its name `<slug>-<app>` and is blue, like
|
|
268
|
+
* platform-archon. All three run on the primary server only.
|
|
218
269
|
*/
|
|
219
270
|
export function builtinSubject(org, app, recorded = {}) {
|
|
220
|
-
|
|
221
|
-
if (!config)
|
|
271
|
+
if (!builtinDeclaresBlueGreen(org, app))
|
|
222
272
|
return null;
|
|
273
|
+
const config = org[app]?.blueGreen ?? {};
|
|
223
274
|
const spec = BUILTIN_APP[app];
|
|
224
275
|
const base = `${org.slug}-${app}`;
|
|
225
276
|
return {
|
|
@@ -233,7 +284,7 @@ export function builtinSubject(org, app, recorded = {}) {
|
|
|
233
284
|
buildPack: 'dockerfile',
|
|
234
285
|
healthPath: spec.healthPath,
|
|
235
286
|
database: spec.database,
|
|
236
|
-
hostnames: [
|
|
287
|
+
hostnames: [builtinHostnameFor(org, app)],
|
|
237
288
|
config,
|
|
238
289
|
blueAppKey: `coolify:${app}:${org.id}`,
|
|
239
290
|
dockerfileLocation: spec.dockerfile,
|
|
@@ -241,7 +292,7 @@ export function builtinSubject(org, app, recorded = {}) {
|
|
|
241
292
|
builtin: app,
|
|
242
293
|
};
|
|
243
294
|
}
|
|
244
|
-
/** Every blue/green subject of an org: the hosting entries that declare it, then Boule and
|
|
295
|
+
/** Every blue/green subject of an org: the hosting entries that declare it, then Boule, Agora and kosmos. */
|
|
245
296
|
export function blueGreenSubjects(org, recorded) {
|
|
246
297
|
const subjects = [];
|
|
247
298
|
for (const entry of org.hosting.entries) {
|
|
@@ -258,7 +309,7 @@ export function blueGreenSubjects(org, recorded) {
|
|
|
258
309
|
}
|
|
259
310
|
/** Whether the org declares blue/green for any of its apps. */
|
|
260
311
|
export const declaresBlueGreen = (org) => org.hosting.entries.some((entry) => entry.blueGreen) ||
|
|
261
|
-
BUILTIN_APPS.some((app) => org
|
|
312
|
+
BUILTIN_APPS.some((app) => builtinDeclaresBlueGreen(org, app));
|
|
262
313
|
/**
|
|
263
314
|
* The Coolify name of the entry's app on the primary. A blue/green entry's app is its blue copy: the recorded
|
|
264
315
|
* name; else the entry's own name when its app is already recorded (it ran before blue/green and is adopted as
|
|
@@ -410,12 +461,42 @@ export function structureHash(subject, input) {
|
|
|
410
461
|
primary: input.primary,
|
|
411
462
|
lb: VERSIONS.traefik,
|
|
412
463
|
...(input.primary ? { stageSink: STAGE_SINK_VERSION } : {}),
|
|
464
|
+
// Only a primary has a stage, and only a present guard counts: no guard hashes as before.
|
|
465
|
+
...(input.primary && input.guards?.length ? { stageGuards: [...input.guards].sort() } : {}),
|
|
413
466
|
}))
|
|
414
467
|
.digest('hex')
|
|
415
468
|
.slice(0, 16);
|
|
416
469
|
}
|
|
417
|
-
/**
|
|
418
|
-
|
|
470
|
+
/**
|
|
471
|
+
* The kind of stage app a subject has: Boule and Agora by `builtin` (a hosting entry that happens to be keyed
|
|
472
|
+
* `boule` is still a customer app), platform-archon by its id, else a customer app (kosmos included: it has no lists of its own).
|
|
473
|
+
*/
|
|
474
|
+
export const stageKindOf = (subject) => subject.builtin === 'boule' || subject.builtin === 'agora'
|
|
475
|
+
? subject.builtin
|
|
476
|
+
: subject.id === ARCHON_SUBJECT_ID
|
|
477
|
+
? 'archon'
|
|
478
|
+
: 'customer';
|
|
479
|
+
/**
|
|
480
|
+
* The stage guards that can be made now, by name. The names go into the applied structure hash, so a guard
|
|
481
|
+
* that becomes possible after the first apply changes the hash and plans the structure step once more.
|
|
482
|
+
*/
|
|
483
|
+
export function stageGuardInputs(facts) {
|
|
484
|
+
const out = [];
|
|
485
|
+
if (facts.probesPort)
|
|
486
|
+
out.push('probes');
|
|
487
|
+
if (facts.accessPort &&
|
|
488
|
+
facts.providerId &&
|
|
489
|
+
facts.domain &&
|
|
490
|
+
facts.claimValue &&
|
|
491
|
+
facts.emails > 0 &&
|
|
492
|
+
!facts.hostRuled) {
|
|
493
|
+
out.push('gate');
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
/** The guard names as the plan's `desired.guards` carries them, and back. */
|
|
498
|
+
export const formatGuards = (guards) => [...guards].sort().join(',');
|
|
499
|
+
export const parseGuards = (value) => typeof value === 'string' && value ? value.split(',') : [];
|
|
419
500
|
/** The names stage can collide on. */
|
|
420
501
|
export function stageResources(subject) {
|
|
421
502
|
const database = `${subject.base}-stage-db`;
|
|
@@ -596,10 +677,22 @@ export function planBlueGreen(input) {
|
|
|
596
677
|
key: `bg:${id}:stage`,
|
|
597
678
|
});
|
|
598
679
|
}
|
|
680
|
+
// The guards that can be made now go into the applied hash: one that becomes possible after the first
|
|
681
|
+
// apply (a provider, a probe port, an owner) changes the hash and plans the structure step once more.
|
|
682
|
+
const stageHost = org.domain ? stageHostname(org.domain, id) : null;
|
|
683
|
+
const guards = stageGuardInputs({
|
|
684
|
+
probesPort: input.stageGuardPorts?.probes === true,
|
|
685
|
+
accessPort: input.stageGuardPorts?.access === true,
|
|
686
|
+
providerId: input.accessProviderId,
|
|
687
|
+
domain: org.domain,
|
|
688
|
+
claimValue: recordedIds[ZITADEL_ORG_SCOPE_KEY],
|
|
689
|
+
emails: org.owners.length,
|
|
690
|
+
hostRuled: (org.hosting.access ?? []).some((rule) => rule.hostname === stageHost),
|
|
691
|
+
});
|
|
599
692
|
for (const server of targets) {
|
|
600
693
|
const isPrimary = server === primary;
|
|
601
694
|
const key = `${org.id}:${id}:${server}`;
|
|
602
|
-
const hash = structureHash(subject, { primary: isPrimary });
|
|
695
|
+
const hash = structureHash(subject, { primary: isPrimary, guards });
|
|
603
696
|
if (!isPrimary && subject.database && !remoteDatabaseAppliedFor(recordedIds, id, server)) {
|
|
604
697
|
unmanaged.push({
|
|
605
698
|
description: `${org.slug}'s ${id} app has no blue/green copies on Coolify server ${server} yet: its database route to that server is not applied`,
|
|
@@ -624,6 +717,7 @@ export function planBlueGreen(input) {
|
|
|
624
717
|
primary,
|
|
625
718
|
weights: formatWeights(weights),
|
|
626
719
|
...(input.accessProviderId ? { accessProviderId: input.accessProviderId } : {}),
|
|
720
|
+
...(isPrimary && guards.length > 0 ? { guards: formatGuards(guards) } : {}),
|
|
627
721
|
},
|
|
628
722
|
});
|
|
629
723
|
}
|
|
@@ -722,7 +816,9 @@ export function planArchonBlueGreen(input) {
|
|
|
722
816
|
if (!primary || !up.includes(primary)) {
|
|
723
817
|
return blocked(`the primary Coolify server ${primary ?? '(none)'} is not usable and reachable`);
|
|
724
818
|
}
|
|
725
|
-
const
|
|
819
|
+
const guards = input.guards ?? [];
|
|
820
|
+
const done = recorded[bgKey.applied(subject.id, primary)] ===
|
|
821
|
+
structureHash(subject, { primary: true, guards }) &&
|
|
726
822
|
recorded[bgKey.lb(subject.id, primary)] === formatWeights(weights) &&
|
|
727
823
|
recorded[ARCHON_BG_ORIGIN_SCOPE_KEY] === lbOrigin(subject);
|
|
728
824
|
if (done)
|
|
@@ -734,7 +830,12 @@ export function planArchonBlueGreen(input) {
|
|
|
734
830
|
phase: 'after',
|
|
735
831
|
key: subject.id,
|
|
736
832
|
description: `run platform-archon as blue and green behind a load balancer on Coolify server ${primary}, then send its tunnel to the balancer`,
|
|
737
|
-
desired: {
|
|
833
|
+
desired: {
|
|
834
|
+
subject: subject.id,
|
|
835
|
+
primary,
|
|
836
|
+
weights: formatWeights(weights),
|
|
837
|
+
...(guards.length > 0 ? { guards: formatGuards(guards) } : {}),
|
|
838
|
+
},
|
|
738
839
|
},
|
|
739
840
|
};
|
|
740
841
|
}
|
package/dist/plan/boule.d.ts
CHANGED
|
@@ -154,7 +154,7 @@ export declare const registrarWanted: (org: Pick<DeclaredOrg, "customers" | "bou
|
|
|
154
154
|
/** The broker's base as Boule calls it: `@wtfalch/auth`'s own `brokerUrl` default, `<issuer>/api`. */
|
|
155
155
|
export declare const registrarUrl: (issuer: string) => string;
|
|
156
156
|
/** The env names `desiredBouleEnv` can write, so the live reader knows which to read back. */
|
|
157
|
-
export declare const BOULE_MANAGED_ENV_KEYS: readonly ["APP_URL", "AUTH_ISSUER", "AUTH_ORGANIZATION_ID", "AUTH_CLIENT_ID", "BOULE_ORG_NAME", "AUTHZ_BOOTSTRAP_OPERATOR", "AUTHZ_FIRST_OWNER", "ARCHON_URL", "MAIL_FROM", "LETTERMINT_API_TOKEN", "AUTHZ_PEER_PASSWORD_OPERATOR", "ARCHON_BRIDGE_SECRET", "OWNER_SEED_SECRET", "ARCHON_ORG_ID", "ARCHON_CREDENTIAL", "CUSTOMERS_ORGANISATION_ID", "CUSTOMERS_DIRECTORY_CREDENTIAL", "TEST_CUSTOMERS_ORGANISATION_ID", "CUSTOMER_GRANTS", "SIGNIN_UPSTREAM", "AUTH_REGISTRAR_URL", "AUTH_REGISTRAR_KEY"];
|
|
157
|
+
export declare const BOULE_MANAGED_ENV_KEYS: readonly ["APP_URL", "AUTH_ISSUER", "AUTH_ORGANIZATION_ID", "AUTH_CLIENT_ID", "BOULE_ORG_NAME", "AUTHZ_BOOTSTRAP_OPERATOR", "AUTHZ_FIRST_OWNER", "ARCHON_URL", "MAIL_FROM", "LETTERMINT_API_TOKEN", "AUTHZ_PEER_PASSWORD_OPERATOR", "ARCHON_BRIDGE_SECRET", "OWNER_SEED_SECRET", "NATIVE_BUILD_KEY", "ARCHON_ORG_ID", "ARCHON_CREDENTIAL", "CUSTOMERS_ORGANISATION_ID", "CUSTOMERS_DIRECTORY_CREDENTIAL", "TEST_CUSTOMERS_ORGANISATION_ID", "CUSTOMER_GRANTS", "SIGNIN_UPSTREAM", "AUTH_REGISTRAR_URL", "AUTH_REGISTRAR_KEY"];
|
|
158
158
|
/**
|
|
159
159
|
* The env vars an org's Boule app should carry, and every input still missing
|
|
160
160
|
* to build them. Callers that write must refuse on a non-empty `missing`: a
|
package/dist/plan/boule.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NATIVE_BUILD_KEY_ENV } from '../native-build.js';
|
|
1
2
|
import { DEFAULT_NO_REPLY_LOCAL_PART, appsOn, ownerMailboxes, } from '../org.js';
|
|
2
3
|
import { ZITADEL_ORG_SCOPE_KEY } from './agora.js';
|
|
3
4
|
import { SIGN_IN_UPSTREAM_ENV, inAppHostApplied, signInUpstream, staleSignInUpstream, } from './sign-in.js';
|
|
@@ -176,6 +177,7 @@ export const BOULE_MANAGED_ENV_KEYS = [
|
|
|
176
177
|
BOULE_PEER_PASSWORD_ENV,
|
|
177
178
|
BOULE_BRIDGE_SECRET_ENV,
|
|
178
179
|
BOULE_OWNER_SEED_SECRET_ENV,
|
|
180
|
+
NATIVE_BUILD_KEY_ENV,
|
|
179
181
|
BOULE_ARCHON_ORG_ID_ENV,
|
|
180
182
|
BOULE_ARCHON_CREDENTIAL_ENV,
|
|
181
183
|
BOULE_CUSTOMERS_ORG_ENV,
|
|
@@ -178,3 +178,12 @@ export declare function writeBrokerEntry(broker: BrokerPort, input: {
|
|
|
178
178
|
invalidForeign: string[];
|
|
179
179
|
replaced: string | null;
|
|
180
180
|
}>;
|
|
181
|
+
/**
|
|
182
|
+
* Takes one entry (and so its key) out of the broker's `AUTH_APPS`, matched by name and organisation, and
|
|
183
|
+
* deploys the broker when the list changed. Every other entry is copied back as it was. An entry that is
|
|
184
|
+
* already gone writes nothing. Errors name the entry, never a key.
|
|
185
|
+
*/
|
|
186
|
+
export declare function removeBrokerEntry(broker: BrokerPort, input: {
|
|
187
|
+
name: string;
|
|
188
|
+
organizationId: string;
|
|
189
|
+
}): Promise<boolean>;
|
|
@@ -373,3 +373,32 @@ export async function writeBrokerEntry(broker, input) {
|
|
|
373
373
|
replaced: replaced.length > 0 ? JSON.stringify(replaced) : null,
|
|
374
374
|
};
|
|
375
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Takes one entry (and so its key) out of the broker's `AUTH_APPS`, matched by name and organisation, and
|
|
378
|
+
* deploys the broker when the list changed. Every other entry is copied back as it was. An entry that is
|
|
379
|
+
* already gone writes nothing. Errors name the entry, never a key.
|
|
380
|
+
*/
|
|
381
|
+
export async function removeBrokerEntry(broker, input) {
|
|
382
|
+
const label = `broker entry '${input.name}'`;
|
|
383
|
+
const env = (await broker.listEnvs()).find((e) => e.key === 'AUTH_APPS' && !e.is_preview);
|
|
384
|
+
if (!env?.value)
|
|
385
|
+
return false;
|
|
386
|
+
let parsed;
|
|
387
|
+
try {
|
|
388
|
+
parsed = JSON.parse(env.value);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
throw new Error(`${label}: the broker's AUTH_APPS is not JSON, so nothing was removed`);
|
|
392
|
+
}
|
|
393
|
+
if (!Array.isArray(parsed)) {
|
|
394
|
+
throw new Error(`${label}: the broker's AUTH_APPS is not a JSON array, so nothing was removed`);
|
|
395
|
+
}
|
|
396
|
+
const next = parsed.filter((entry) => !(isObject(entry) &&
|
|
397
|
+
entry.name === input.name &&
|
|
398
|
+
entry.organizationId === input.organizationId));
|
|
399
|
+
if (next.length === parsed.length)
|
|
400
|
+
return false;
|
|
401
|
+
await broker.setAuthApps(JSON.stringify(next));
|
|
402
|
+
await broker.deploy();
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
@@ -142,10 +142,17 @@ async function matchesHealth(fetchImpl, health) {
|
|
|
142
142
|
const response = await fetchImpl(health.url, {
|
|
143
143
|
signal: AbortSignal.timeout(HEALTH_REQUEST_TIMEOUT_MS),
|
|
144
144
|
});
|
|
145
|
-
if (!response.ok)
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
// #760: an unread body left undici's parser paused when the socket
|
|
147
|
+
// ended, throwing an uncaught assertion in the host. Every answer this
|
|
148
|
+
// function does not go on to consume gets cancelled here.
|
|
149
|
+
await response.body?.cancel();
|
|
146
150
|
return false;
|
|
147
|
-
|
|
151
|
+
}
|
|
152
|
+
if (health.kind === 'ready') {
|
|
153
|
+
await response.body?.cancel();
|
|
148
154
|
return true;
|
|
155
|
+
}
|
|
149
156
|
const body = await response.json().catch(() => undefined);
|
|
150
157
|
const value = body && typeof body === 'object'
|
|
151
158
|
? body[health.field ?? 'version']
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { agoraHostnameFor } from './agora.js';
|
|
2
2
|
import { bouleHostnameFor } from './boule.js';
|
|
3
3
|
import { appHostname, cmsStudioHostnameFor, entryHostnames } from './hostnames.js';
|
|
4
|
+
import { kosmosHostnameFor } from './kosmos.js';
|
|
4
5
|
import { workspaceAuthEntryOf, workspaceClientHosts } from './workspace-client.js';
|
|
5
6
|
/**
|
|
6
7
|
* #339: the desktop app signs in through the company's web app, so every
|
|
@@ -36,7 +37,9 @@ export function signInRedirectUris(org, appKey) {
|
|
|
36
37
|
? cmsStudioHostnameFor(org)
|
|
37
38
|
: appKey === 'boule'
|
|
38
39
|
? bouleHostnameFor(org)
|
|
39
|
-
:
|
|
40
|
+
: appKey === 'kosmos'
|
|
41
|
+
? kosmosHostnameFor(org)
|
|
42
|
+
: appHostname(org),
|
|
40
43
|
];
|
|
41
44
|
return [
|
|
42
45
|
...hosts.map((host) => `https://${host}/auth/callback`),
|
package/dist/plan/diff.d.ts
CHANGED
|
@@ -150,6 +150,11 @@ export interface PlanOrgOptions {
|
|
|
150
150
|
blueGreenServers?: readonly BlueGreenServer[];
|
|
151
151
|
/** The company's stage mail sink is recorded and its live app is stopped or gone. */
|
|
152
152
|
stageSinkDown?: boolean;
|
|
153
|
+
/** Which stage-guard ports the host has ({@link PlanBlueGreenInput.stageGuardPorts}). */
|
|
154
|
+
stageGuardPorts?: {
|
|
155
|
+
probes: boolean;
|
|
156
|
+
access: boolean;
|
|
157
|
+
};
|
|
153
158
|
}
|
|
154
159
|
export interface PlanDetail {
|
|
155
160
|
orgId: OrgId;
|