@treeseed/sdk 0.10.17 → 0.10.19
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/operations/services/railway-api.d.ts +4 -7
- package/dist/operations/services/railway-api.js +73 -30
- package/dist/operations/services/railway-deploy.d.ts +26 -0
- package/dist/operations/services/railway-deploy.js +103 -1
- package/dist/reconcile/builtin-adapters.js +41 -16
- package/package.json +1 -1
|
@@ -199,7 +199,7 @@ export declare function getRailwayServiceInstance({ serviceId, environmentId, en
|
|
|
199
199
|
sleepApplication: null;
|
|
200
200
|
runtimeConfigSupported: false;
|
|
201
201
|
}>;
|
|
202
|
-
export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, environmentId, buildCommand, startCommand, cronSchedule, rootDirectory, healthcheckPath, healthcheckTimeoutSeconds, healthcheckIntervalSeconds, restartPolicy, runtimeMode, env, fetchImpl, }: {
|
|
202
|
+
export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, environmentId, buildCommand, startCommand, cronSchedule, rootDirectory, healthcheckPath, healthcheckTimeoutSeconds, healthcheckIntervalSeconds, restartPolicy, runtimeMode, env, fetchImpl, settleAttempts, settleDelayMs, }: {
|
|
203
203
|
serviceId: string;
|
|
204
204
|
environmentId: string;
|
|
205
205
|
buildCommand?: string | null;
|
|
@@ -213,6 +213,8 @@ export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, e
|
|
|
213
213
|
runtimeMode?: string | null;
|
|
214
214
|
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
215
215
|
fetchImpl?: typeof fetch;
|
|
216
|
+
settleAttempts?: number;
|
|
217
|
+
settleDelayMs?: number;
|
|
216
218
|
}): Promise<{
|
|
217
219
|
instance: {
|
|
218
220
|
id: string | null;
|
|
@@ -288,12 +290,7 @@ export declare function ensureRailwayServiceVolume({ projectId, environmentId, s
|
|
|
288
290
|
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
289
291
|
fetchImpl?: typeof fetch;
|
|
290
292
|
}): Promise<{
|
|
291
|
-
volume:
|
|
292
|
-
instances: RailwayVolumeInstanceSummary[];
|
|
293
|
-
id: string;
|
|
294
|
-
name: string;
|
|
295
|
-
projectId: string | null;
|
|
296
|
-
} | null;
|
|
293
|
+
volume: RailwayVolumeSummary | null;
|
|
297
294
|
instance: RailwayVolumeInstanceSummary | null;
|
|
298
295
|
created: boolean;
|
|
299
296
|
updated: boolean;
|
|
@@ -838,9 +838,17 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
838
838
|
restartPolicy,
|
|
839
839
|
runtimeMode,
|
|
840
840
|
env = process.env,
|
|
841
|
-
fetchImpl = fetch
|
|
841
|
+
fetchImpl = fetch,
|
|
842
|
+
settleAttempts = 24,
|
|
843
|
+
settleDelayMs = 5e3
|
|
842
844
|
}) {
|
|
843
|
-
|
|
845
|
+
let current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
|
|
846
|
+
if (!current.id) {
|
|
847
|
+
for (let attempt = 0; attempt < 8 && !current.id; attempt += 1) {
|
|
848
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
849
|
+
current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
844
852
|
if (!current.id) {
|
|
845
853
|
return { instance: current, updated: false };
|
|
846
854
|
}
|
|
@@ -905,30 +913,40 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
|
|
|
905
913
|
}
|
|
906
914
|
throw error;
|
|
907
915
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
916
|
+
let instance = current;
|
|
917
|
+
for (let attempt = 0; attempt <= settleAttempts; attempt += 1) {
|
|
918
|
+
instance = await getRailwayServiceInstance({
|
|
919
|
+
serviceId,
|
|
920
|
+
environmentId,
|
|
921
|
+
env,
|
|
922
|
+
fetchImpl
|
|
923
|
+
});
|
|
924
|
+
if (!serviceInstanceDrifted(instance, desired) || attempt >= settleAttempts) {
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
await new Promise((resolve) => setTimeout(resolve, settleDelayMs));
|
|
928
|
+
}
|
|
914
929
|
return {
|
|
915
930
|
instance: {
|
|
916
931
|
id: instance.id || current.id,
|
|
917
|
-
buildCommand: instance.buildCommand
|
|
918
|
-
startCommand: instance.startCommand
|
|
919
|
-
cronSchedule: instance.cronSchedule
|
|
920
|
-
rootDirectory: instance.rootDirectory
|
|
921
|
-
healthcheckPath: instance.healthcheckPath
|
|
922
|
-
healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds
|
|
923
|
-
healthcheckIntervalSeconds: instance.healthcheckIntervalSeconds
|
|
924
|
-
restartPolicy: instance.restartPolicy
|
|
925
|
-
runtimeMode: instance.runtimeMode
|
|
926
|
-
sleepApplication: instance.sleepApplication
|
|
932
|
+
buildCommand: instance.buildCommand,
|
|
933
|
+
startCommand: instance.startCommand,
|
|
934
|
+
cronSchedule: instance.cronSchedule,
|
|
935
|
+
rootDirectory: instance.rootDirectory,
|
|
936
|
+
healthcheckPath: instance.healthcheckPath,
|
|
937
|
+
healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds,
|
|
938
|
+
healthcheckIntervalSeconds: instance.healthcheckIntervalSeconds,
|
|
939
|
+
restartPolicy: instance.restartPolicy,
|
|
940
|
+
runtimeMode: instance.runtimeMode,
|
|
941
|
+
sleepApplication: instance.sleepApplication,
|
|
927
942
|
runtimeConfigSupported: instance.runtimeConfigSupported
|
|
928
943
|
},
|
|
929
944
|
updated: true
|
|
930
945
|
};
|
|
931
946
|
}
|
|
947
|
+
function serviceInstanceDrifted(current, desired) {
|
|
948
|
+
return desired.buildCommand !== null && desired.buildCommand !== current.buildCommand || desired.startCommand !== null && desired.startCommand !== current.startCommand || desired.cronSchedule !== null && desired.cronSchedule !== current.cronSchedule || desired.rootDirectory !== null && desired.rootDirectory !== current.rootDirectory || desired.healthcheckPath !== null && desired.healthcheckPath !== current.healthcheckPath || desired.healthcheckTimeoutSeconds !== null && desired.healthcheckTimeoutSeconds !== current.healthcheckTimeoutSeconds || desired.runtimeMode !== null && desired.runtimeMode !== current.runtimeMode;
|
|
949
|
+
}
|
|
932
950
|
async function listRailwayVariables({
|
|
933
951
|
projectId,
|
|
934
952
|
environmentId,
|
|
@@ -1193,23 +1211,37 @@ async function ensureRailwayServiceVolume({
|
|
|
1193
1211
|
...candidate,
|
|
1194
1212
|
instances: candidate.instances.filter(isActiveRailwayVolumeInstance)
|
|
1195
1213
|
})).filter((candidate) => candidate.instances.length > 0);
|
|
1196
|
-
let volume = activeVolumes.find(
|
|
1197
|
-
(candidate) => candidate.instances.some((instance2) => instance2.serviceId === serviceId && instance2.environmentId === environmentId)
|
|
1198
|
-
) ?? activeVolumes.find(
|
|
1214
|
+
let volume = findRailwayVolumeForService(volumes, serviceId, environmentId) ?? activeVolumes.find(
|
|
1199
1215
|
(candidate) => candidate.name === name && candidate.instances.some((instance2) => instance2.environmentId === environmentId)
|
|
1200
1216
|
) ?? null;
|
|
1201
1217
|
let created = false;
|
|
1202
1218
|
let updated = false;
|
|
1203
1219
|
const createReplacementVolume = async () => {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1220
|
+
let replacement;
|
|
1221
|
+
try {
|
|
1222
|
+
replacement = await createRailwayVolume({
|
|
1223
|
+
projectId,
|
|
1224
|
+
environmentId,
|
|
1225
|
+
serviceId,
|
|
1226
|
+
name,
|
|
1227
|
+
mountPath,
|
|
1228
|
+
env,
|
|
1229
|
+
fetchImpl
|
|
1230
|
+
});
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
if (!looksLikeRailwayVolumeCreateRace(error)) {
|
|
1233
|
+
throw error;
|
|
1234
|
+
}
|
|
1235
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
1236
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
1237
|
+
const refreshed = await listRailwayVolumes({ projectId, env, fetchImpl });
|
|
1238
|
+
const existing = findRailwayVolumeForService(refreshed, serviceId, environmentId);
|
|
1239
|
+
if (existing) {
|
|
1240
|
+
return existing;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
throw error;
|
|
1244
|
+
}
|
|
1213
1245
|
created = true;
|
|
1214
1246
|
return replacement;
|
|
1215
1247
|
};
|
|
@@ -1259,6 +1291,17 @@ async function ensureRailwayServiceVolume({
|
|
|
1259
1291
|
}
|
|
1260
1292
|
return { volume, instance, created, updated };
|
|
1261
1293
|
}
|
|
1294
|
+
function findRailwayVolumeForService(volumes, serviceId, environmentId) {
|
|
1295
|
+
return volumes.find(
|
|
1296
|
+
(candidate) => candidate.instances.some(
|
|
1297
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && isActiveRailwayVolumeInstance(instance)
|
|
1298
|
+
)
|
|
1299
|
+
) ?? null;
|
|
1300
|
+
}
|
|
1301
|
+
function looksLikeRailwayVolumeCreateRace(error) {
|
|
1302
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1303
|
+
return /would have \d+ volumes attached|can only have one volume|not authorized/iu.test(message);
|
|
1304
|
+
}
|
|
1262
1305
|
async function listRailwayCustomDomains({
|
|
1263
1306
|
projectId,
|
|
1264
1307
|
environmentId,
|
|
@@ -4,7 +4,16 @@ export declare function deriveRailwayMarketOperationsRunnerServiceName(baseServi
|
|
|
4
4
|
export declare function deriveRailwayWorkerRunnerVolumeName(serviceName: any, environmentName?: string): string;
|
|
5
5
|
export declare function deriveRailwayMarketOperationsRunnerVolumeName(serviceName: any, environmentName?: string): string;
|
|
6
6
|
export declare function railwayServiceRuntimeStartCommand(service: any): any;
|
|
7
|
+
export declare function parseRailwayJsonOutput(output: any): any;
|
|
7
8
|
export declare function collectRailwayDeploymentStatusChecks(statusPayload: any, scope: any, services: any): any;
|
|
9
|
+
export declare function listRailwayServiceVolumesWithCli({ cwd, serviceId, environmentId, name, mountPath, env, }: {
|
|
10
|
+
cwd: any;
|
|
11
|
+
serviceId: any;
|
|
12
|
+
environmentId: any;
|
|
13
|
+
name: any;
|
|
14
|
+
mountPath: any;
|
|
15
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
16
|
+
}): any;
|
|
8
17
|
export declare function isUsableRailwayToken(value: any): boolean;
|
|
9
18
|
export declare function resolveRailwayAuthToken(env?: NodeJS.ProcessEnv): string;
|
|
10
19
|
export declare function buildRailwayCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
@@ -144,6 +153,23 @@ export declare function planRailwayServiceLink(service: any, { env }?: {
|
|
|
144
153
|
args: string[];
|
|
145
154
|
cwd: any;
|
|
146
155
|
};
|
|
156
|
+
export declare function ensureRailwayServiceVolumeWithCliFallback({ tenantRoot, projectId, environmentId, environmentName, serviceId, serviceName, name, mountPath, preferCli, env, }: {
|
|
157
|
+
tenantRoot: any;
|
|
158
|
+
projectId: any;
|
|
159
|
+
environmentId: any;
|
|
160
|
+
environmentName: any;
|
|
161
|
+
serviceId: any;
|
|
162
|
+
serviceName: any;
|
|
163
|
+
name: any;
|
|
164
|
+
mountPath: any;
|
|
165
|
+
preferCli?: boolean | undefined;
|
|
166
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
167
|
+
}): Promise<{
|
|
168
|
+
volume: any;
|
|
169
|
+
instance: any;
|
|
170
|
+
created: boolean;
|
|
171
|
+
updated: boolean;
|
|
172
|
+
}>;
|
|
147
173
|
export declare function deployRailwayService(tenantRoot: any, service: any, { dryRun, write, prefix, env, }?: {
|
|
148
174
|
dryRun?: boolean;
|
|
149
175
|
write?: TreeseedBootstrapWriter;
|
|
@@ -225,6 +225,30 @@ function normalizeRailwayCliVolumeList(value, options) {
|
|
|
225
225
|
}
|
|
226
226
|
return value.volumes.map((entry) => normalizeRailwayCliVolume(entry, options)).filter(Boolean);
|
|
227
227
|
}
|
|
228
|
+
function listRailwayServiceVolumesWithCli({
|
|
229
|
+
cwd,
|
|
230
|
+
serviceId,
|
|
231
|
+
environmentId,
|
|
232
|
+
name,
|
|
233
|
+
mountPath,
|
|
234
|
+
env = process.env
|
|
235
|
+
}) {
|
|
236
|
+
const listResult = runRailway(["volume", "--service", serviceId, "--environment", environmentId, "list", "--json"], {
|
|
237
|
+
cwd,
|
|
238
|
+
capture: true,
|
|
239
|
+
allowFailure: true,
|
|
240
|
+
env
|
|
241
|
+
});
|
|
242
|
+
if ((listResult.status ?? 1) !== 0) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
|
|
246
|
+
serviceId,
|
|
247
|
+
environmentId,
|
|
248
|
+
fallbackName: name,
|
|
249
|
+
fallbackMountPath: mountPath
|
|
250
|
+
});
|
|
251
|
+
}
|
|
228
252
|
function isUsableRailwayToken(value) {
|
|
229
253
|
return typeof value === "string" && value.trim().length >= 8;
|
|
230
254
|
}
|
|
@@ -1758,7 +1782,43 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1758
1782
|
}
|
|
1759
1783
|
created = true;
|
|
1760
1784
|
}
|
|
1761
|
-
|
|
1785
|
+
let instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1786
|
+
if (!instance || instance.mountPath !== mountPath) {
|
|
1787
|
+
const attachResult = runRailway([...volumeArgs, "attach", "--volume", volume.id, "--yes", "--json"], {
|
|
1788
|
+
...cliOptions,
|
|
1789
|
+
allowFailure: true
|
|
1790
|
+
});
|
|
1791
|
+
if ((attachResult.status ?? 1) !== 0) {
|
|
1792
|
+
const attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1793
|
+
if (!/already mounted/iu.test(attachMessage)) {
|
|
1794
|
+
throw new Error(attachMessage || `Railway volume attach failed for ${serviceName} in ${environmentName}.`);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const attachedVolume = (attachResult.status ?? 1) === 0 ? normalizeRailwayCliVolume(parseRailwayJsonOutput(attachResult.stdout ?? ""), {
|
|
1798
|
+
serviceId,
|
|
1799
|
+
environmentId,
|
|
1800
|
+
fallbackName: name,
|
|
1801
|
+
fallbackMountPath: mountPath
|
|
1802
|
+
}) : null;
|
|
1803
|
+
volume = attachedVolume ?? {
|
|
1804
|
+
...volume,
|
|
1805
|
+
instances: [{
|
|
1806
|
+
...instance ?? {
|
|
1807
|
+
id: volume.id,
|
|
1808
|
+
serviceId,
|
|
1809
|
+
environmentId,
|
|
1810
|
+
state: "READY",
|
|
1811
|
+
sizeGb: null,
|
|
1812
|
+
usedGb: null
|
|
1813
|
+
},
|
|
1814
|
+
serviceId,
|
|
1815
|
+
environmentId,
|
|
1816
|
+
mountPath
|
|
1817
|
+
}]
|
|
1818
|
+
};
|
|
1819
|
+
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1820
|
+
updated = true;
|
|
1821
|
+
}
|
|
1762
1822
|
if (volume.name !== name || instance?.mountPath !== mountPath) {
|
|
1763
1823
|
const updateResult = runRailway([...volumeArgs, "update", "--volume", volume.id, "--name", name, "--mount-path", mountPath, "--json"], cliOptions);
|
|
1764
1824
|
const updatedVolume = normalizeRailwayCliVolume(parseRailwayJsonOutput(updateResult.stdout ?? ""), {
|
|
@@ -1774,6 +1834,18 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1774
1834
|
};
|
|
1775
1835
|
updated = true;
|
|
1776
1836
|
}
|
|
1837
|
+
const apiVolume = await waitForRailwayServiceVolumeMount({
|
|
1838
|
+
projectId,
|
|
1839
|
+
volumeId: volume.id,
|
|
1840
|
+
volumeName: name,
|
|
1841
|
+
serviceId,
|
|
1842
|
+
environmentId,
|
|
1843
|
+
mountPath,
|
|
1844
|
+
env
|
|
1845
|
+
});
|
|
1846
|
+
if (apiVolume) {
|
|
1847
|
+
volume = apiVolume;
|
|
1848
|
+
}
|
|
1777
1849
|
return {
|
|
1778
1850
|
volume,
|
|
1779
1851
|
instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
|
|
@@ -1781,6 +1853,33 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1781
1853
|
updated
|
|
1782
1854
|
};
|
|
1783
1855
|
}
|
|
1856
|
+
async function waitForRailwayServiceVolumeMount({
|
|
1857
|
+
projectId,
|
|
1858
|
+
volumeId,
|
|
1859
|
+
volumeName,
|
|
1860
|
+
serviceId,
|
|
1861
|
+
environmentId,
|
|
1862
|
+
mountPath,
|
|
1863
|
+
env
|
|
1864
|
+
}) {
|
|
1865
|
+
for (let attempt = 0; attempt <= 24; attempt += 1) {
|
|
1866
|
+
const volumes = await listRailwayVolumes({ projectId, env });
|
|
1867
|
+
const match = volumes.find(
|
|
1868
|
+
(entry) => entry.id === volumeId || entry.name === volumeName || entry.instances.some(
|
|
1869
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1870
|
+
)
|
|
1871
|
+
) ?? null;
|
|
1872
|
+
if (match?.instances.some(
|
|
1873
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1874
|
+
)) {
|
|
1875
|
+
return match;
|
|
1876
|
+
}
|
|
1877
|
+
if (attempt < 24) {
|
|
1878
|
+
await sleep(5e3);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return null;
|
|
1882
|
+
}
|
|
1784
1883
|
async function deployRailwayService(tenantRoot, service, {
|
|
1785
1884
|
dryRun = false,
|
|
1786
1885
|
write,
|
|
@@ -1925,8 +2024,11 @@ export {
|
|
|
1925
2024
|
ensureRailwayProjectExists,
|
|
1926
2025
|
ensureRailwayScheduledJobs,
|
|
1927
2026
|
ensureRailwayServiceExists,
|
|
2027
|
+
ensureRailwayServiceVolumeWithCliFallback,
|
|
1928
2028
|
isRailwayTransientFailure,
|
|
1929
2029
|
isUsableRailwayToken,
|
|
2030
|
+
listRailwayServiceVolumesWithCli,
|
|
2031
|
+
parseRailwayJsonOutput,
|
|
1930
2032
|
planRailwayServiceDeploy,
|
|
1931
2033
|
planRailwayServiceLink,
|
|
1932
2034
|
railwayServiceRuntimeStartCommand,
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
configuredRailwayServices,
|
|
33
33
|
deriveRailwayMarketOperationsRunnerVolumeName,
|
|
34
34
|
ensureRailwayProjectContext,
|
|
35
|
+
ensureRailwayServiceVolumeWithCliFallback,
|
|
36
|
+
listRailwayServiceVolumesWithCli,
|
|
35
37
|
runRailway,
|
|
36
38
|
validateRailwayDeployPrerequisites
|
|
37
39
|
} from "../operations/services/railway-deploy.js";
|
|
@@ -42,7 +44,6 @@ import {
|
|
|
42
44
|
ensureRailwayProject,
|
|
43
45
|
ensureRailwayService,
|
|
44
46
|
ensureRailwayServiceInstanceConfiguration,
|
|
45
|
-
ensureRailwayServiceVolume,
|
|
46
47
|
getRailwayServiceInstance,
|
|
47
48
|
getRailwayProject,
|
|
48
49
|
listRailwayCustomDomains,
|
|
@@ -1568,12 +1569,16 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
|
|
|
1568
1569
|
});
|
|
1569
1570
|
if (entry.configuredService.volumeMountPath) {
|
|
1570
1571
|
const volumeName = entry.configuredService.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(entry.service.name, entry.environment.name) : `${entry.service.name}-volume`;
|
|
1571
|
-
const volume = await
|
|
1572
|
+
const volume = await ensureRailwayServiceVolumeWithCliFallback({
|
|
1573
|
+
tenantRoot: input.context.tenantRoot,
|
|
1572
1574
|
projectId: entry.project.id,
|
|
1573
1575
|
environmentId: entry.environment.id,
|
|
1576
|
+
environmentName: entry.environment.name,
|
|
1574
1577
|
serviceId: entry.service.id,
|
|
1578
|
+
serviceName: entry.service.name,
|
|
1575
1579
|
name: volumeName,
|
|
1576
1580
|
mountPath: entry.configuredService.volumeMountPath,
|
|
1581
|
+
preferCli: entry.configuredService.key === "marketOperationsRunner",
|
|
1577
1582
|
env: topology.env
|
|
1578
1583
|
});
|
|
1579
1584
|
if (!volume.instance?.serviceId) {
|
|
@@ -1660,7 +1665,7 @@ async function ensureRailwayMarketDatabaseForScope(input, topology) {
|
|
|
1660
1665
|
env: topology.env
|
|
1661
1666
|
});
|
|
1662
1667
|
}
|
|
1663
|
-
for (let attempt = 0; attempt <
|
|
1668
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
1664
1669
|
const existingVolumes = await listRailwayVolumes({
|
|
1665
1670
|
projectId: firstService.project.id,
|
|
1666
1671
|
env: topology.env
|
|
@@ -1669,18 +1674,10 @@ async function ensureRailwayMarketDatabaseForScope(input, topology) {
|
|
|
1669
1674
|
(instance) => instance.serviceId === postgresService.id && instance.environmentId === firstService.environment?.id && instance.mountPath === "/var/lib/postgresql/data"
|
|
1670
1675
|
));
|
|
1671
1676
|
if (attached) {
|
|
1672
|
-
|
|
1677
|
+
return;
|
|
1673
1678
|
}
|
|
1674
|
-
await new Promise((resolve2) => setTimeout(resolve2,
|
|
1679
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1675
1680
|
}
|
|
1676
|
-
await ensureRailwayServiceVolume({
|
|
1677
|
-
projectId: firstService.project.id,
|
|
1678
|
-
environmentId: firstService.environment.id,
|
|
1679
|
-
serviceId: postgresService.id,
|
|
1680
|
-
name: `postgres-${topology.scope === "prod" ? "prod" : topology.scope}-data`,
|
|
1681
|
-
mountPath: "/var/lib/postgresql/data",
|
|
1682
|
-
env: topology.env
|
|
1683
|
-
});
|
|
1684
1681
|
}
|
|
1685
1682
|
async function observeRailwayUnit(input, { refresh = false } = {}) {
|
|
1686
1683
|
let attempt = 0;
|
|
@@ -2220,16 +2217,33 @@ async function verifyRailwayUnit(input) {
|
|
|
2220
2217
|
const volumes = entry.project?.id ? await listRailwayVolumes({ projectId: entry.project.id, env: topology.env }) : [];
|
|
2221
2218
|
const expectedServiceId = entry.service?.id ?? null;
|
|
2222
2219
|
const expectedEnvironmentId = entry.environment?.id ?? null;
|
|
2223
|
-
|
|
2220
|
+
let mountedVolume = volumes.find((volume) => volume.instances.some(
|
|
2224
2221
|
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2225
2222
|
)) ?? null;
|
|
2223
|
+
let mountedVolumeSource = "api";
|
|
2224
|
+
if (!mountedVolume && serviceKey === "marketOperationsRunner" && expectedServiceId && expectedEnvironmentId) {
|
|
2225
|
+
ensureRailwayProjectContext(service, { env: topology.env, capture: true });
|
|
2226
|
+
const cliVolumes = listRailwayServiceVolumesWithCli({
|
|
2227
|
+
cwd: service.rootDir,
|
|
2228
|
+
serviceId: expectedServiceId,
|
|
2229
|
+
environmentId: expectedEnvironmentId,
|
|
2230
|
+
name: deriveRailwayMarketOperationsRunnerVolumeName(entry.service?.name ?? service.serviceName ?? service.key, entry.environment?.name ?? service.railwayEnvironment),
|
|
2231
|
+
mountPath: service.volumeMountPath,
|
|
2232
|
+
env: topology.env
|
|
2233
|
+
});
|
|
2234
|
+
mountedVolume = cliVolumes.find((volume) => volume.instances.some(
|
|
2235
|
+
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2236
|
+
)) ?? null;
|
|
2237
|
+
mountedVolumeSource = mountedVolume ? "cli" : "api";
|
|
2238
|
+
}
|
|
2226
2239
|
checks.push(verificationCheck("railway.volume:data", "Railway service has persistent data volume mounted", "api", {
|
|
2227
2240
|
exists: Boolean(mountedVolume),
|
|
2228
2241
|
configured: Boolean(mountedVolume),
|
|
2229
2242
|
expected: service.volumeMountPath,
|
|
2230
2243
|
observed: mountedVolume ? {
|
|
2231
2244
|
name: mountedVolume.name,
|
|
2232
|
-
mountPath: service.volumeMountPath
|
|
2245
|
+
mountPath: service.volumeMountPath,
|
|
2246
|
+
source: mountedVolumeSource
|
|
2233
2247
|
} : null,
|
|
2234
2248
|
issues: mountedVolume ? [] : [`Railway service ${service.serviceName ?? service.key} is missing a persistent volume mounted at ${service.volumeMountPath}.`]
|
|
2235
2249
|
}));
|
|
@@ -2251,7 +2265,13 @@ async function verifyRailwayUnit(input) {
|
|
|
2251
2265
|
issues: Object.hasOwn(entry.currentVariables, key) ? [] : [`Railway secret ${key} is missing.`]
|
|
2252
2266
|
}));
|
|
2253
2267
|
}
|
|
2254
|
-
|
|
2268
|
+
const verification = summarizeVerification(input.unit.unitId, checks);
|
|
2269
|
+
if (!verification.verified && attempt < 12 && railwayVerificationMaySettle(verification)) {
|
|
2270
|
+
attempt += 1;
|
|
2271
|
+
sleepMs(5e3);
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
return verification;
|
|
2255
2275
|
} catch (error) {
|
|
2256
2276
|
if (attempt >= 2 || !isTransientRailwayReconcileError(error)) {
|
|
2257
2277
|
throw error;
|
|
@@ -2261,6 +2281,11 @@ async function verifyRailwayUnit(input) {
|
|
|
2261
2281
|
}
|
|
2262
2282
|
}
|
|
2263
2283
|
}
|
|
2284
|
+
function railwayVerificationMaySettle(verification) {
|
|
2285
|
+
return verification.checks.some(
|
|
2286
|
+
(check) => !check.verified && (check.key === "railway.instance" || check.key.startsWith("railway.instance.") || check.key === "railway.volume:data")
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2264
2289
|
function railwayStartCommandMatches(serviceKey, observed, expected) {
|
|
2265
2290
|
if (observed === expected) {
|
|
2266
2291
|
return true;
|