@le-space/node 0.1.0 → 0.1.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/index.d.ts +1 -0
- package/index.js +74 -12
- package/package.json +3 -3
package/index.d.ts
CHANGED
|
@@ -133,6 +133,7 @@ interface DeployExecutorDependencies {
|
|
|
133
133
|
sender?: string;
|
|
134
134
|
hasher?: MessageHasher;
|
|
135
135
|
manifest?: RootfsManifest | null;
|
|
136
|
+
log?: (message: string) => void;
|
|
136
137
|
}
|
|
137
138
|
declare function executeDeployPlan(plan: DeployPlan, dependencies?: DeployExecutorDependencies): Promise<DeployOutputResult>;
|
|
138
139
|
|
package/index.js
CHANGED
|
@@ -664,12 +664,14 @@ async function waitForDeploymentResult(itemHash, options) {
|
|
|
664
664
|
const delayMs = Math.max(0, Number(options.delayMs ?? 2e3));
|
|
665
665
|
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
666
666
|
let lastResult = await inspectDeploymentResult(itemHash, options);
|
|
667
|
+
options.onAttempt?.(lastResult, 1, attempts);
|
|
667
668
|
for (let attempt = 1; attempt < attempts; attempt += 1) {
|
|
668
669
|
if (lastResult.status === "processed" || lastResult.status === "rejected") {
|
|
669
670
|
return lastResult;
|
|
670
671
|
}
|
|
671
672
|
await sleep2(delayMs);
|
|
672
673
|
lastResult = await inspectDeploymentResult(itemHash, options);
|
|
674
|
+
options.onAttempt?.(lastResult, attempt + 1, attempts);
|
|
673
675
|
}
|
|
674
676
|
return lastResult;
|
|
675
677
|
}
|
|
@@ -1374,12 +1376,14 @@ async function waitForVmRuntime(args) {
|
|
|
1374
1376
|
const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
|
|
1375
1377
|
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1376
1378
|
let lastRuntime = await fetchVmRuntime(args);
|
|
1379
|
+
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1377
1380
|
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1378
1381
|
if (lastRuntime.hostIpv4 && Object.keys(lastRuntime.mappedPorts ?? {}).length > 0) {
|
|
1379
1382
|
return lastRuntime;
|
|
1380
1383
|
}
|
|
1381
1384
|
await sleep2(delayMs);
|
|
1382
1385
|
lastRuntime = await fetchVmRuntime(args);
|
|
1386
|
+
args.onAttempt?.(lastRuntime, attempt + 2, attempts);
|
|
1383
1387
|
}
|
|
1384
1388
|
if (lastRuntime.diagnostics) {
|
|
1385
1389
|
lastRuntime.diagnostics.timedOut = true;
|
|
@@ -1458,10 +1462,12 @@ async function waitForSetupEndpoint(args) {
|
|
|
1458
1462
|
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1459
1463
|
const url = `http://${args.hostIpv4}:${args.setupPort}/health`;
|
|
1460
1464
|
let result = await defaultHttpProbe(args.fetch, url, Number(args.httpTimeoutMs ?? 1e4));
|
|
1465
|
+
args.onAttempt?.(result, 1, attempts);
|
|
1461
1466
|
for (let attempt = 1; attempt < attempts; attempt += 1) {
|
|
1462
1467
|
if (result.ok) return result;
|
|
1463
1468
|
await sleep2(delayMs);
|
|
1464
1469
|
result = await defaultHttpProbe(args.fetch, url, Number(args.httpTimeoutMs ?? 1e4));
|
|
1470
|
+
args.onAttempt?.(result, attempt + 1, attempts);
|
|
1465
1471
|
}
|
|
1466
1472
|
return result;
|
|
1467
1473
|
}
|
|
@@ -1512,6 +1518,12 @@ async function fetchUcGoPeerMetadata(args) {
|
|
|
1512
1518
|
});
|
|
1513
1519
|
const payload = await response.json().catch(() => null);
|
|
1514
1520
|
lastPayload = payload;
|
|
1521
|
+
args.onAttempt?.(
|
|
1522
|
+
payload,
|
|
1523
|
+
Boolean(response.ok && payload && typeof payload === "object" && payload.status === "ready"),
|
|
1524
|
+
attempt + 1,
|
|
1525
|
+
attempts
|
|
1526
|
+
);
|
|
1515
1527
|
if (response.ok && payload && typeof payload === "object" && payload.status === "ready") {
|
|
1516
1528
|
return payload;
|
|
1517
1529
|
}
|
|
@@ -1653,13 +1665,21 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1653
1665
|
const hasher = dependencies.hasher ?? defaultHasher;
|
|
1654
1666
|
const tcpProbe = dependencies.tcpProbe ?? defaultTcpProbe;
|
|
1655
1667
|
const sleepImpl = dependencies.sleep ?? ((ms) => sleep(ms).then(() => void 0));
|
|
1668
|
+
const log = dependencies.log ?? ((message) => console.log(message));
|
|
1656
1669
|
const identity = dependencies.sender && dependencies.signer ? { address: dependencies.sender, signer: dependencies.signer } : await createPrivateKeyIdentity(plan.privateKey);
|
|
1657
1670
|
const candidateCrns = await candidateCrnsForPlan(plan, fetchImpl);
|
|
1658
1671
|
if (candidateCrns.length === 0) {
|
|
1659
1672
|
throw new Error("No compatible CRN was available for deployment.");
|
|
1660
1673
|
}
|
|
1674
|
+
log(`[deploy] profile=${plan.profile} sender=${identity.address}`);
|
|
1675
|
+
log(
|
|
1676
|
+
`[deploy] candidate CRNs=${candidateCrns.map((crn) => `${crn.name ?? crn.hash}:${crn.hash}`).join(", ")}`
|
|
1677
|
+
);
|
|
1661
1678
|
let lastError = null;
|
|
1662
|
-
for (const candidateCrn of candidateCrns) {
|
|
1679
|
+
for (const [candidateIndex, candidateCrn] of candidateCrns.entries()) {
|
|
1680
|
+
log(
|
|
1681
|
+
`[deploy] attempting CRN ${candidateIndex + 1}/${candidateCrns.length}: ${candidateCrn.name ?? candidateCrn.hash} (${candidateCrn.hash})`
|
|
1682
|
+
);
|
|
1663
1683
|
const content = createInstanceContent({
|
|
1664
1684
|
address: identity.address,
|
|
1665
1685
|
name: plan.name,
|
|
@@ -1683,21 +1703,33 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1683
1703
|
channel: plan.channel,
|
|
1684
1704
|
sync: true
|
|
1685
1705
|
});
|
|
1706
|
+
log(`[deploy] broadcasted INSTANCE message ${deployment.itemHash} on ${candidateCrn.name ?? candidateCrn.hash}`);
|
|
1686
1707
|
const inspection = await waitForDeploymentResult(deployment.itemHash, {
|
|
1687
1708
|
rootfsRef: plan.rootfsItemHash,
|
|
1688
1709
|
apiHost: plan.apiHost,
|
|
1689
1710
|
fetch: fetchImpl,
|
|
1690
1711
|
attempts: plan.waitAttempts,
|
|
1691
1712
|
delayMs: plan.waitDelayMs,
|
|
1692
|
-
sleep: sleepImpl
|
|
1713
|
+
sleep: sleepImpl,
|
|
1714
|
+
onAttempt: (result, attempt, attempts) => {
|
|
1715
|
+
log(
|
|
1716
|
+
`[deploy] Aleph processing ${attempt}/${attempts} for ${deployment.itemHash}: status=${result.status}${result.rejectionReason ? ` reason=${result.rejectionReason}` : ""}`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1693
1719
|
});
|
|
1694
1720
|
if (inspection.status === "rejected") {
|
|
1721
|
+
log(
|
|
1722
|
+
`[deploy] CRN ${candidateCrn.name ?? candidateCrn.hash} rejected deployment ${deployment.itemHash}: ${inspection.rejectionReason ?? "no additional reason"}`
|
|
1723
|
+
);
|
|
1695
1724
|
lastError = new Error(
|
|
1696
1725
|
`Deployment on ${candidateCrn.name ?? candidateCrn.hash} was rejected: ${inspection.rejectionReason ?? "no additional rejection reason from Aleph"}.`
|
|
1697
1726
|
);
|
|
1698
1727
|
continue;
|
|
1699
1728
|
}
|
|
1700
1729
|
if (inspection.status !== "processed") {
|
|
1730
|
+
log(
|
|
1731
|
+
`[deploy] deployment ${deployment.itemHash} on ${candidateCrn.name ?? candidateCrn.hash} did not become processed; cleaning up`
|
|
1732
|
+
);
|
|
1701
1733
|
await cleanupFailedDeployment({
|
|
1702
1734
|
sender: identity.address,
|
|
1703
1735
|
instanceItemHash: deployment.itemHash,
|
|
@@ -1715,6 +1747,7 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1715
1747
|
}
|
|
1716
1748
|
let portForwarding = null;
|
|
1717
1749
|
if (plan.publishPortForwards && plan.requiredPorts.length > 0) {
|
|
1750
|
+
log(`[deploy] publishing required port-forward aggregate for ${deployment.itemHash}`);
|
|
1718
1751
|
const aggregate = await ensureInstancePortForwards({
|
|
1719
1752
|
sender: identity.address,
|
|
1720
1753
|
instanceItemHash: deployment.itemHash,
|
|
@@ -1730,7 +1763,19 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1730
1763
|
aggregateItemHash: aggregate.aggregateItemHash,
|
|
1731
1764
|
aggregateStatus: aggregate.aggregateStatus
|
|
1732
1765
|
};
|
|
1766
|
+
log(
|
|
1767
|
+
`[deploy] port-forward aggregate published: item_hash=${aggregate.aggregateItemHash} status=${aggregate.aggregateStatus}`
|
|
1768
|
+
);
|
|
1733
1769
|
}
|
|
1770
|
+
if (candidateCrn.address) {
|
|
1771
|
+
log(`[deploy] notifying CRN allocation endpoint ${candidateCrn.address}`);
|
|
1772
|
+
await notifyCrnAllocation({
|
|
1773
|
+
crnUrl: candidateCrn.address,
|
|
1774
|
+
itemHash: deployment.itemHash,
|
|
1775
|
+
fetch: fetchImpl
|
|
1776
|
+
}).catch(() => null);
|
|
1777
|
+
}
|
|
1778
|
+
log(`[deploy] waiting for runtime networking on ${candidateCrn.name ?? candidateCrn.hash}`);
|
|
1734
1779
|
const runtime = await waitForVmRuntime({
|
|
1735
1780
|
itemHash: deployment.itemHash,
|
|
1736
1781
|
fetch: fetchImpl,
|
|
@@ -1739,7 +1784,12 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1739
1784
|
crnListUrl: plan.crnListUrl,
|
|
1740
1785
|
attempts: plan.runtimeAttempts,
|
|
1741
1786
|
delayMs: plan.runtimeDelayMs,
|
|
1742
|
-
sleep: sleepImpl
|
|
1787
|
+
sleep: sleepImpl,
|
|
1788
|
+
onAttempt: (runtimeAttempt, attempt, attempts) => {
|
|
1789
|
+
log(
|
|
1790
|
+
`[deploy] runtime ${attempt}/${attempts} for ${deployment.itemHash}: state=${runtimeAttempt.diagnostics?.state ?? "unknown"} host=${runtimeAttempt.hostIpv4 ?? "-"} mapped_ports=${Object.keys(runtimeAttempt.mappedPorts ?? {}).length}`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1743
1793
|
}).catch(() => null);
|
|
1744
1794
|
const runtimeMetadata = runtime ? {
|
|
1745
1795
|
allocation: runtime.allocation,
|
|
@@ -1763,6 +1813,9 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1763
1813
|
selectedCrn: { hash: candidateCrn.hash, name: candidateCrn.name ?? "" }
|
|
1764
1814
|
};
|
|
1765
1815
|
if (!runtime?.hostIpv4 || Object.keys(runtime?.mappedPorts ?? {}).length === 0) {
|
|
1816
|
+
log(
|
|
1817
|
+
`[deploy] processed deployment ${deployment.itemHash} never exposed usable runtime networking on ${candidateCrn.name ?? candidateCrn.hash}; cleaning up`
|
|
1818
|
+
);
|
|
1766
1819
|
await cleanupFailedDeployment({
|
|
1767
1820
|
sender: identity.address,
|
|
1768
1821
|
instanceItemHash: deployment.itemHash,
|
|
@@ -1780,13 +1833,6 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1780
1833
|
}
|
|
1781
1834
|
let configuration = null;
|
|
1782
1835
|
let verification = null;
|
|
1783
|
-
if (runtime.selectedCrn?.address) {
|
|
1784
|
-
await notifyCrnAllocation({
|
|
1785
|
-
crnUrl: runtime.selectedCrn.address,
|
|
1786
|
-
itemHash: deployment.itemHash,
|
|
1787
|
-
fetch: fetchImpl
|
|
1788
|
-
}).catch(() => null);
|
|
1789
|
-
}
|
|
1790
1836
|
if (runtime.hostIpv4 && plan.autoConfigure !== false && plan.profile === "uc-go-peer") {
|
|
1791
1837
|
const mappedPorts = runtime.mappedPorts ?? {};
|
|
1792
1838
|
const setupPort = mappedPorts["80"]?.host ?? null;
|
|
@@ -1795,6 +1841,7 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1795
1841
|
const udpPort = mappedPorts["9095"]?.udp === true ? mappedPorts["9095"]?.host ?? null : null;
|
|
1796
1842
|
const proxyUrl = plan.enableCaddyProxy ? runtime.proxyUrl ?? null : null;
|
|
1797
1843
|
if (setupPort && runtime.hostIpv4) {
|
|
1844
|
+
log(`[deploy] waiting for temporary setup endpoint on http://${runtime.hostIpv4}:${setupPort}/health`);
|
|
1798
1845
|
const setupHealth = await waitForSetupEndpoint({
|
|
1799
1846
|
hostIpv4: runtime.hostIpv4,
|
|
1800
1847
|
setupPort,
|
|
@@ -1802,10 +1849,16 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1802
1849
|
attempts: plan.setupAttempts,
|
|
1803
1850
|
delayMs: plan.setupDelayMs,
|
|
1804
1851
|
httpTimeoutMs: plan.httpTimeoutMs,
|
|
1805
|
-
sleep: sleepImpl
|
|
1852
|
+
sleep: sleepImpl,
|
|
1853
|
+
onAttempt: (result, attempt, attempts) => {
|
|
1854
|
+
log(
|
|
1855
|
+
`[deploy] setup endpoint ${attempt}/${attempts}: ok=${result.ok} status=${result.status ?? "-"} error=${result.error ?? "-"}`
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1806
1858
|
});
|
|
1807
1859
|
runtimeMetadata.setupHealth = setupHealth;
|
|
1808
1860
|
if (!setupHealth.ok) {
|
|
1861
|
+
log(`[deploy] setup endpoint never became reachable; cleaning up ${deployment.itemHash}`);
|
|
1809
1862
|
await cleanupFailedDeployment({
|
|
1810
1863
|
sender: identity.address,
|
|
1811
1864
|
instanceItemHash: deployment.itemHash,
|
|
@@ -1819,6 +1872,7 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1819
1872
|
lastError = new Error(`Temporary setup endpoint did not become reachable at http://${runtime.hostIpv4}:${setupPort}/health.`);
|
|
1820
1873
|
continue;
|
|
1821
1874
|
}
|
|
1875
|
+
log(`[deploy] calling guest /configure for ${deployment.itemHash}`);
|
|
1822
1876
|
const configureResult = await configureUcGoPeer({
|
|
1823
1877
|
hostIpv4: runtime.hostIpv4,
|
|
1824
1878
|
publicIpv6: runtime.ipv6,
|
|
@@ -1832,6 +1886,7 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1832
1886
|
fetch: fetchImpl,
|
|
1833
1887
|
timeoutMs: plan.configureTimeoutMs
|
|
1834
1888
|
});
|
|
1889
|
+
log(`[deploy] polling guest /metadata until ready`);
|
|
1835
1890
|
const metadataResult = await fetchUcGoPeerMetadata({
|
|
1836
1891
|
hostIpv4: runtime.hostIpv4,
|
|
1837
1892
|
setupPort,
|
|
@@ -1839,7 +1894,10 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1839
1894
|
attempts: plan.metadataAttempts,
|
|
1840
1895
|
delayMs: plan.metadataDelayMs,
|
|
1841
1896
|
timeoutMs: plan.metadataTimeoutMs,
|
|
1842
|
-
sleep: sleepImpl
|
|
1897
|
+
sleep: sleepImpl,
|
|
1898
|
+
onAttempt: (_payload, ready, attempt, attempts) => {
|
|
1899
|
+
log(`[deploy] guest metadata ${attempt}/${attempts}: ready=${ready}`);
|
|
1900
|
+
}
|
|
1843
1901
|
});
|
|
1844
1902
|
configuration = {
|
|
1845
1903
|
...configureResult && typeof configureResult === "object" ? configureResult : {},
|
|
@@ -1848,6 +1906,7 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1848
1906
|
if (plan.verifyReachability !== false) {
|
|
1849
1907
|
let latestVerification = null;
|
|
1850
1908
|
for (let attempt = 0; attempt < plan.verifyAttempts; attempt += 1) {
|
|
1909
|
+
log(`[deploy] reachability verification ${attempt + 1}/${plan.verifyAttempts}`);
|
|
1851
1910
|
latestVerification = await verifyUcGoPeerReachability({
|
|
1852
1911
|
hostIpv4: runtime.hostIpv4,
|
|
1853
1912
|
mappedPorts,
|
|
@@ -1860,9 +1919,11 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1860
1919
|
tcpProbe
|
|
1861
1920
|
});
|
|
1862
1921
|
if (latestVerification?.ok) {
|
|
1922
|
+
log(`[deploy] reachability verification succeeded`);
|
|
1863
1923
|
break;
|
|
1864
1924
|
}
|
|
1865
1925
|
if (attempt < plan.verifyAttempts - 1) {
|
|
1926
|
+
log(`[deploy] reachability verification not ready yet; sleeping ${plan.verifyDelayMs}ms`);
|
|
1866
1927
|
await sleepImpl(plan.verifyDelayMs);
|
|
1867
1928
|
}
|
|
1868
1929
|
}
|
|
@@ -1886,6 +1947,7 @@ async function executeDeployPlan(plan, dependencies = {}) {
|
|
|
1886
1947
|
verification
|
|
1887
1948
|
};
|
|
1888
1949
|
}
|
|
1950
|
+
log(`[deploy] no candidate CRN succeeded`);
|
|
1889
1951
|
throw lastError ?? new Error("No compatible CRN deployment attempt succeeded.");
|
|
1890
1952
|
}
|
|
1891
1953
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Node and GitHub Actions adapters for shared Aleph tooling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@le-space/core": "0.1.
|
|
20
|
-
"@le-space/shared-types": "0.1.
|
|
19
|
+
"@le-space/core": "0.1.2",
|
|
20
|
+
"@le-space/shared-types": "0.1.2",
|
|
21
21
|
"ethers": "^6.15.0"
|
|
22
22
|
}
|
|
23
23
|
}
|