@hsuite/smart-engines-cli 1.2.1 → 1.4.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/README.md +272 -58
- package/dist/_lib/attestation.d.ts +45 -0
- package/dist/_lib/attestation.d.ts.map +1 -0
- package/dist/_lib/attestation.js +76 -0
- package/dist/_lib/attestation.js.map +1 -0
- package/dist/_lib/baas.d.ts +147 -0
- package/dist/_lib/baas.d.ts.map +1 -0
- package/dist/_lib/baas.js +260 -0
- package/dist/_lib/baas.js.map +1 -0
- package/dist/_lib/cluster.d.ts +6 -6
- package/dist/_lib/cluster.js +6 -6
- package/dist/_lib/deploy-manifest.d.ts +69 -0
- package/dist/_lib/deploy-manifest.d.ts.map +1 -0
- package/dist/_lib/deploy-manifest.js +87 -0
- package/dist/_lib/deploy-manifest.js.map +1 -0
- package/dist/_lib/docker.d.ts +82 -0
- package/dist/_lib/docker.d.ts.map +1 -0
- package/dist/_lib/docker.js +164 -0
- package/dist/_lib/docker.js.map +1 -0
- package/dist/_lib/faucet-client.d.ts +64 -0
- package/dist/_lib/faucet-client.d.ts.map +1 -0
- package/dist/_lib/faucet-client.js +92 -0
- package/dist/_lib/faucet-client.js.map +1 -0
- package/dist/_lib/framework.d.ts +59 -0
- package/dist/_lib/framework.d.ts.map +1 -0
- package/dist/_lib/framework.js +149 -0
- package/dist/_lib/framework.js.map +1 -0
- package/dist/_lib/frontend-guard.d.ts +13 -0
- package/dist/_lib/frontend-guard.d.ts.map +1 -0
- package/dist/_lib/frontend-guard.js +41 -0
- package/dist/_lib/frontend-guard.js.map +1 -0
- package/dist/_lib/registry-fanout.d.ts +114 -0
- package/dist/_lib/registry-fanout.d.ts.map +1 -0
- package/dist/_lib/registry-fanout.js +435 -0
- package/dist/_lib/registry-fanout.js.map +1 -0
- package/dist/_lib/runtime-env.d.ts +126 -0
- package/dist/_lib/runtime-env.d.ts.map +1 -0
- package/dist/_lib/runtime-env.js +172 -0
- package/dist/_lib/runtime-env.js.map +1 -0
- package/dist/_lib/subscription-client.d.ts +129 -18
- package/dist/_lib/subscription-client.d.ts.map +1 -1
- package/dist/_lib/subscription-client.js +268 -40
- package/dist/_lib/subscription-client.js.map +1 -1
- package/dist/_lib/subscription-env.d.ts +35 -0
- package/dist/_lib/subscription-env.d.ts.map +1 -1
- package/dist/_lib/subscription-env.js +46 -2
- package/dist/_lib/subscription-env.js.map +1 -1
- package/dist/commands/app-lifecycle.d.ts +36 -0
- package/dist/commands/app-lifecycle.d.ts.map +1 -0
- package/dist/commands/app-lifecycle.js +145 -0
- package/dist/commands/app-lifecycle.js.map +1 -0
- package/dist/commands/ci.d.ts +45 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +174 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/deploy-manifest.d.ts +11 -0
- package/dist/commands/deploy-manifest.d.ts.map +1 -0
- package/dist/commands/deploy-manifest.js +17 -0
- package/dist/commands/deploy-manifest.js.map +1 -0
- package/dist/commands/deploy.d.ts +70 -11
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +263 -150
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/downgrade.js +1 -1
- package/dist/commands/downgrade.js.map +1 -1
- package/dist/commands/faucet.d.ts +78 -0
- package/dist/commands/faucet.d.ts.map +1 -0
- package/dist/commands/faucet.js +184 -0
- package/dist/commands/faucet.js.map +1 -0
- package/dist/commands/governance.js +1 -1
- package/dist/commands/governance.js.map +1 -1
- package/dist/commands/hist-balance.d.ts +1 -1
- package/dist/commands/hist-balance.js +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/list.d.ts +22 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +81 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/personhood.js +2 -2
- package/dist/commands/personhood.js.map +1 -1
- package/dist/commands/redeploy.d.ts +62 -0
- package/dist/commands/redeploy.d.ts.map +1 -0
- package/dist/commands/redeploy.js +310 -0
- package/dist/commands/redeploy.js.map +1 -0
- package/dist/commands/status.d.ts +51 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/subscribe.d.ts +94 -17
- package/dist/commands/subscribe.d.ts.map +1 -1
- package/dist/commands/subscribe.js +450 -81
- package/dist/commands/subscribe.js.map +1 -1
- package/dist/commands/transactions.d.ts +60 -0
- package/dist/commands/transactions.d.ts.map +1 -0
- package/dist/commands/transactions.js +101 -0
- package/dist/commands/transactions.js.map +1 -0
- package/dist/commands/update.d.ts +40 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +114 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/verify.d.ts +10 -19
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +14 -23
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -3
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-guard.d.ts","sourceRoot":"","sources":["../../src/_lib/frontend-guard.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBxE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertNoDeploySentinels = assertNoDeploySentinels;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
/**
|
|
7
|
+
* Fail-closed guard shared by `deploy` and `redeploy`: refuse to ship a
|
|
8
|
+
* frontend build that still carries unfilled `REPLACE_BEFORE_DEPLOY:<key>`
|
|
9
|
+
* placeholders (per-deployment config that was never set).
|
|
10
|
+
*
|
|
11
|
+
* Such a build refuses to bootstrap in the browser (the SPA's `main.ts` asserts
|
|
12
|
+
* on the sentinel prefix), and the CLI would otherwise tar+upload it silently —
|
|
13
|
+
* which is exactly how a non-bootstrapping bundle reached production. Matches
|
|
14
|
+
* the sentinel VALUE form (`:` followed by a letter) so it does NOT
|
|
15
|
+
* false-positive on the guard's own comparison literal (`REPLACE_BEFORE_DEPLOY:"`).
|
|
16
|
+
*/
|
|
17
|
+
async function assertNoDeploySentinels(dir) {
|
|
18
|
+
const SENTINEL = /REPLACE_BEFORE_DEPLOY:[A-Za-z]/;
|
|
19
|
+
const TEXT_EXT = /\.(js|mjs|cjs|html|css|json|txt|map|webmanifest)$/i;
|
|
20
|
+
const hits = [];
|
|
21
|
+
const walk = async (d) => {
|
|
22
|
+
for (const ent of await fs_1.promises.readdir(d, { withFileTypes: true })) {
|
|
23
|
+
const p = (0, path_1.join)(d, ent.name);
|
|
24
|
+
if (ent.isDirectory())
|
|
25
|
+
await walk(p);
|
|
26
|
+
else if (TEXT_EXT.test(ent.name)) {
|
|
27
|
+
const m = (await fs_1.promises.readFile(p, 'utf8')).match(SENTINEL);
|
|
28
|
+
if (m)
|
|
29
|
+
hits.push(`${(0, path_1.relative)(dir, p)} (${m[0]}…)`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
await walk(dir);
|
|
34
|
+
if (hits.length) {
|
|
35
|
+
throw new Error(`frontend still contains REPLACE_BEFORE_DEPLOY placeholders — fill your ` +
|
|
36
|
+
`per-deployment config (e.g. environment.prod.ts) and rebuild before deploying. ` +
|
|
37
|
+
`Offending: ${hits.slice(0, 5).join(', ')}` +
|
|
38
|
+
(hits.length > 5 ? `, +${hits.length - 5} more` : ''));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=frontend-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-guard.js","sourceRoot":"","sources":["../../src/_lib/frontend-guard.ts"],"names":[],"mappings":";;AAcA,0DAuBC;AArCD,2BAAoC;AACpC,+BAAsC;AAEtC;;;;;;;;;;GAUG;AACI,KAAK,UAAU,uBAAuB,CAAC,GAAW;IACvD,MAAM,QAAQ,GAAG,gCAAgC,CAAC;IAClD,MAAM,QAAQ,GAAG,oDAAoD,CAAC;IACtE,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,KAAK,EAAE,CAAS,EAAiB,EAAE;QAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,aAAE,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC/D,MAAM,CAAC,GAAG,IAAA,WAAI,EAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YAC5B,IAAI,GAAG,CAAC,WAAW,EAAE;gBAAE,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;iBAChC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,GAAG,CAAC,MAAM,aAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAI,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,IAAA,eAAQ,EAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,yEAAyE;YACvE,iFAAiF;YACjF,cAAc,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAC3C,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CACxD,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
/** A request against one cluster's Harbor Registry-v2 API. `path` is always `/v2/...` (or `/token...`). */
|
|
3
|
+
export type RegistryRequest = {
|
|
4
|
+
ip: string;
|
|
5
|
+
method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH';
|
|
6
|
+
path: string;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
body?: Buffer;
|
|
9
|
+
};
|
|
10
|
+
/** A Registry-v2 response. Header keys are lower-cased by the default transport. */
|
|
11
|
+
export type RegistryResponse = {
|
|
12
|
+
status: number;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
body: Buffer;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The injectable network seam. The default implementation
|
|
18
|
+
* ({@link defaultTransport}) dials `req.ip:443` with TLS `servername` =
|
|
19
|
+
* the federated host and an added `Host` header = the federated host, with
|
|
20
|
+
* verification ON. Tests inject a fake — no real network.
|
|
21
|
+
*/
|
|
22
|
+
export type RegistryTransport = (req: RegistryRequest) => Promise<RegistryResponse>;
|
|
23
|
+
/** A fan-out target — just the cluster-ingress IP. (Kept as a type for call-site clarity.) */
|
|
24
|
+
export type FanOutTarget = {
|
|
25
|
+
ip: string;
|
|
26
|
+
};
|
|
27
|
+
/** Per-endpoint outcome of {@link ensureImageOnClusters}. */
|
|
28
|
+
export type FanOutReport = {
|
|
29
|
+
targets: Array<{
|
|
30
|
+
ip: string;
|
|
31
|
+
outcome: 'present' | 'copied' | 'failed';
|
|
32
|
+
error?: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
export type EnsureImageOpts = {
|
|
36
|
+
host: string;
|
|
37
|
+
repository: string;
|
|
38
|
+
tag: string;
|
|
39
|
+
/** The `sha256:<64hex>` manifest digest the push produced — the SSoT for present/verify. */
|
|
40
|
+
digest: string;
|
|
41
|
+
username: string;
|
|
42
|
+
password: string;
|
|
43
|
+
/** Cluster-ingress IPs (e.g. from {@link resolveClusterEndpoints}). */
|
|
44
|
+
endpoints: string[];
|
|
45
|
+
/** Test seam — defaults to {@link defaultTransport} bound to `host`. */
|
|
46
|
+
transport?: RegistryTransport;
|
|
47
|
+
};
|
|
48
|
+
/** A parsed `WWW-Authenticate: Bearer ...` token challenge. `realm` is mandatory. */
|
|
49
|
+
export type AuthChallenge = {
|
|
50
|
+
realm: string;
|
|
51
|
+
service?: string;
|
|
52
|
+
scope?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Parse a `Bearer realm="...",service="...",scope="..."` challenge. Pure.
|
|
56
|
+
*
|
|
57
|
+
* Returns `null` for a non-Bearer scheme or a Bearer challenge missing the
|
|
58
|
+
* mandatory `realm` (without a realm there is nowhere to fetch a token from).
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseAuthChallenge(wwwAuthenticate: string): AuthChallenge | null;
|
|
61
|
+
/**
|
|
62
|
+
* DNS-resolve the federated host's A-records into the de-duplicated, sorted list
|
|
63
|
+
* of cluster-ingress IPs to fan out across. Injectable `resolve4` for tests;
|
|
64
|
+
* defaults to `dns.promises.resolve4`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveClusterEndpoints(host: string, resolve4?: (h: string) => Promise<string[]>): Promise<string[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Real {@link RegistryTransport}: HTTPS to `req.ip:443` with TLS SNI + HTTP
|
|
69
|
+
* `Host` pinned to `host`, verification ON. All registry paths are `/v2/...`;
|
|
70
|
+
* absolute token-realm URLs are handled by {@link fetchAbsolute}.
|
|
71
|
+
*/
|
|
72
|
+
export declare function defaultTransport(host: string): RegistryTransport;
|
|
73
|
+
/**
|
|
74
|
+
* Fan out an already-pushed image to every cluster Harbor and prove it landed
|
|
75
|
+
* on all of them. See the module header for the full algorithm. Throws if no
|
|
76
|
+
* endpoint serves the digest to copy from, or if re-verification finds any
|
|
77
|
+
* endpoint not serving the pushed digest (the error names the failed IPs).
|
|
78
|
+
*/
|
|
79
|
+
export declare function ensureImageOnClusters(opts: EnsureImageOpts): Promise<FanOutReport>;
|
|
80
|
+
/** Registry coordinates a push produced (shape of `BaasInitResponse.registry`). */
|
|
81
|
+
export type FanOutRegistry = {
|
|
82
|
+
server: string;
|
|
83
|
+
username: string;
|
|
84
|
+
password: string;
|
|
85
|
+
repository: string;
|
|
86
|
+
};
|
|
87
|
+
export type FanOutPushedImageOpts = {
|
|
88
|
+
registry: FanOutRegistry;
|
|
89
|
+
tag: string;
|
|
90
|
+
/** The `sha256:<64hex>` digest the push produced. */
|
|
91
|
+
digest: string;
|
|
92
|
+
/** Test seam — DNS A-record resolver (defaults to `dns.promises.resolve4`). */
|
|
93
|
+
resolve4?: (h: string) => Promise<string[]>;
|
|
94
|
+
/** Test seam — the registry transport. */
|
|
95
|
+
transport?: RegistryTransport;
|
|
96
|
+
/** Optional progress log sink (defaults to no-op). */
|
|
97
|
+
log?: (msg: string) => void;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The deploy-time entrypoint: take an image just pushed to the federated host
|
|
101
|
+
* (landed on ONE cluster) and guarantee it is present on EVERY cluster Harbor.
|
|
102
|
+
*
|
|
103
|
+
* Resolves the registry host's A-records into the cluster-ingress set, then:
|
|
104
|
+
* - 0/1 endpoint (a non-federated or local/dev registry, or DNS that doesn't
|
|
105
|
+
* fan out) → nothing to distribute; returns `null`.
|
|
106
|
+
* - ≥2 endpoints → {@link ensureImageOnClusters}, which copies + re-verifies
|
|
107
|
+
* and THROWS if any cluster ends up not serving the pushed digest.
|
|
108
|
+
*
|
|
109
|
+
* A DNS resolution failure is treated as "single-target" (logged, returns
|
|
110
|
+
* `null`) rather than fatal: a real federated push has already proven the host
|
|
111
|
+
* resolves, so a failure here means a dev/local registry where fan-out is moot.
|
|
112
|
+
*/
|
|
113
|
+
export declare function fanOutPushedImage(opts: FanOutPushedImageOpts): Promise<FanOutReport | null>;
|
|
114
|
+
//# sourceMappingURL=registry-fanout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-fanout.d.ts","sourceRoot":"","sources":["../../src/_lib/registry-fanout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseAuthChallenge = parseAuthChallenge;
|
|
37
|
+
exports.resolveClusterEndpoints = resolveClusterEndpoints;
|
|
38
|
+
exports.defaultTransport = defaultTransport;
|
|
39
|
+
exports.ensureImageOnClusters = ensureImageOnClusters;
|
|
40
|
+
exports.fanOutPushedImage = fanOutPushedImage;
|
|
41
|
+
/**
|
|
42
|
+
* Synchronous Registry-v2 fan-out: copy one already-pushed image to EVERY
|
|
43
|
+
* federated Harbor cluster, then prove it landed on all of them.
|
|
44
|
+
*
|
|
45
|
+
* ## Why this exists
|
|
46
|
+
*
|
|
47
|
+
* `harbor.testnet.hsuite.network` is public-DNS round-robin over the 3
|
|
48
|
+
* cluster-ingress IPs, and each IP fronts its OWN independent Harbor instance.
|
|
49
|
+
* A single `docker push` (via {@link import('./baas').buildAndPush}) therefore
|
|
50
|
+
* lands the image on exactly ONE cluster — whichever IP the OS resolver picked
|
|
51
|
+
* that second. The Harbor cross-cluster replication mesh is unreliable, so we do
|
|
52
|
+
* the fan-out ourselves, at the Registry-v2 API level, and BLOCK the deploy
|
|
53
|
+
* until every cluster serves the exact pushed digest.
|
|
54
|
+
*
|
|
55
|
+
* ## How the fan-out works
|
|
56
|
+
*
|
|
57
|
+
* We talk to each cluster by its IP while pinning TLS SNI + the HTTP `Host`
|
|
58
|
+
* header to the federated hostname ({@link defaultTransport}) — the cert and the
|
|
59
|
+
* Harbor vhost are keyed on that name, the IP is just the dial target. The
|
|
60
|
+
* push-robot credential is BLS-deterministic and identical on all 3 Harbors, so
|
|
61
|
+
* one credential authenticates against every IP.
|
|
62
|
+
*
|
|
63
|
+
* 1. HEAD the tag on each IP; an IP whose `Docker-Content-Digest` equals the
|
|
64
|
+
* pushed digest is already `present`.
|
|
65
|
+
* 2. At least one IP MUST be present (the federated push landed somewhere); pick
|
|
66
|
+
* it as the copy SOURCE.
|
|
67
|
+
* 3. To each non-present IP, copy: pull the source manifest (exact bytes +
|
|
68
|
+
* content-type), and for the config blob + every layer blob, HEAD it on the
|
|
69
|
+
* dest and — only if absent — monolithically PUT it (start an upload session,
|
|
70
|
+
* fetch the blob from source, PUT to the session `Location` with `?digest=`).
|
|
71
|
+
* Then PUT the manifest with the source's exact bytes + content-type.
|
|
72
|
+
* 4. RE-VERIFY by HEADing the tag on EVERY IP. If any IP's served digest still
|
|
73
|
+
* mismatches, throw an aggregated error naming the failed IPs — a deploy must
|
|
74
|
+
* never proceed with a partially-distributed image.
|
|
75
|
+
*
|
|
76
|
+
* Single-arch only: the CLI builds `linux/amd64 --provenance=false --sbom=false`,
|
|
77
|
+
* so the artifact is always a single image manifest, never a manifest list /
|
|
78
|
+
* image index. A manifest-list mediaType is treated as a hard error rather than
|
|
79
|
+
* silently fanning out a list whose per-arch children we never copied.
|
|
80
|
+
*
|
|
81
|
+
* The transport is the ONLY injectable seam ({@link RegistryTransport}); specs
|
|
82
|
+
* pass an in-memory Harbor simulation so no real network is touched. The robot
|
|
83
|
+
* password rides only inside the Basic-auth header of the token request and is
|
|
84
|
+
* never logged.
|
|
85
|
+
*/
|
|
86
|
+
const https = __importStar(require("https"));
|
|
87
|
+
const buffer_1 = require("buffer");
|
|
88
|
+
const dns_1 = require("dns");
|
|
89
|
+
/** Media types that identify a multi-arch manifest list / image index (unsupported). */
|
|
90
|
+
const MANIFEST_LIST_TYPES = [
|
|
91
|
+
'application/vnd.docker.distribution.manifest.list.v2+json',
|
|
92
|
+
'application/vnd.oci.image.index.v1+json',
|
|
93
|
+
];
|
|
94
|
+
/** Accept header offered on manifest HEAD/GET — single-arch image manifests, either flavour. */
|
|
95
|
+
const MANIFEST_ACCEPT = [
|
|
96
|
+
'application/vnd.docker.distribution.manifest.v2+json',
|
|
97
|
+
'application/vnd.oci.image.manifest.v1+json',
|
|
98
|
+
// Also accept the list types so the registry hands us the list (we then reject
|
|
99
|
+
// it with a clear error) rather than 406-ing into an opaque failure.
|
|
100
|
+
...MANIFEST_LIST_TYPES,
|
|
101
|
+
].join(', ');
|
|
102
|
+
/**
|
|
103
|
+
* Parse a `Bearer realm="...",service="...",scope="..."` challenge. Pure.
|
|
104
|
+
*
|
|
105
|
+
* Returns `null` for a non-Bearer scheme or a Bearer challenge missing the
|
|
106
|
+
* mandatory `realm` (without a realm there is nowhere to fetch a token from).
|
|
107
|
+
*/
|
|
108
|
+
function parseAuthChallenge(wwwAuthenticate) {
|
|
109
|
+
const trimmed = wwwAuthenticate.trim();
|
|
110
|
+
if (!/^Bearer\s/i.test(trimmed))
|
|
111
|
+
return null;
|
|
112
|
+
const params = trimmed.replace(/^Bearer\s+/i, '');
|
|
113
|
+
const out = {};
|
|
114
|
+
// Match `key="value"` pairs; values are quoted in every real registry challenge.
|
|
115
|
+
const re = /([a-zA-Z0-9_]+)="([^"]*)"/g;
|
|
116
|
+
let m;
|
|
117
|
+
while ((m = re.exec(params)) !== null) {
|
|
118
|
+
out[m[1]] = m[2];
|
|
119
|
+
}
|
|
120
|
+
if (!out.realm)
|
|
121
|
+
return null;
|
|
122
|
+
const result = { realm: out.realm };
|
|
123
|
+
if (out.service !== undefined)
|
|
124
|
+
result.service = out.service;
|
|
125
|
+
if (out.scope !== undefined)
|
|
126
|
+
result.scope = out.scope;
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* DNS-resolve the federated host's A-records into the de-duplicated, sorted list
|
|
131
|
+
* of cluster-ingress IPs to fan out across. Injectable `resolve4` for tests;
|
|
132
|
+
* defaults to `dns.promises.resolve4`.
|
|
133
|
+
*/
|
|
134
|
+
async function resolveClusterEndpoints(host, resolve4 = (h) => dns_1.promises.resolve4(h)) {
|
|
135
|
+
const ips = await resolve4(host);
|
|
136
|
+
return Array.from(new Set(ips)).sort();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Real {@link RegistryTransport}: HTTPS to `req.ip:443` with TLS SNI + HTTP
|
|
140
|
+
* `Host` pinned to `host`, verification ON. All registry paths are `/v2/...`;
|
|
141
|
+
* absolute token-realm URLs are handled by {@link fetchAbsolute}.
|
|
142
|
+
*/
|
|
143
|
+
function defaultTransport(host) {
|
|
144
|
+
return (req) => new Promise((resolvePromise, reject) => {
|
|
145
|
+
const r = https.request({
|
|
146
|
+
host: req.ip,
|
|
147
|
+
servername: host, // TLS SNI → the federated cert
|
|
148
|
+
port: 443,
|
|
149
|
+
method: req.method,
|
|
150
|
+
path: req.path,
|
|
151
|
+
rejectUnauthorized: true,
|
|
152
|
+
headers: { ...(req.headers ?? {}), Host: host },
|
|
153
|
+
}, (res) => {
|
|
154
|
+
const chunks = [];
|
|
155
|
+
res.on('data', (d) => chunks.push(d));
|
|
156
|
+
res.on('end', () => resolvePromise({
|
|
157
|
+
status: res.statusCode ?? 0,
|
|
158
|
+
headers: lowerCaseHeaders(res.headers),
|
|
159
|
+
body: buffer_1.Buffer.concat(chunks),
|
|
160
|
+
}));
|
|
161
|
+
});
|
|
162
|
+
r.on('error', reject);
|
|
163
|
+
if (req.body)
|
|
164
|
+
r.write(req.body);
|
|
165
|
+
r.end();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Lower-case header keys; collapse array values to the first entry. */
|
|
169
|
+
function lowerCaseHeaders(h) {
|
|
170
|
+
const out = {};
|
|
171
|
+
for (const [k, v] of Object.entries(h)) {
|
|
172
|
+
if (v === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
out[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/** Case-insensitive header read. */
|
|
179
|
+
function header(res, name) {
|
|
180
|
+
const lower = name.toLowerCase();
|
|
181
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
182
|
+
if (k.toLowerCase() === lower)
|
|
183
|
+
return v;
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
/** Resolve a possibly-relative `Location` against the `/v2/...` request space. */
|
|
188
|
+
function locationPath(location) {
|
|
189
|
+
if (location.startsWith('http://') || location.startsWith('https://')) {
|
|
190
|
+
const u = new URL(location);
|
|
191
|
+
return u.pathname + u.search;
|
|
192
|
+
}
|
|
193
|
+
return location;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* An authenticating Registry client over the injected transport. Transparently
|
|
197
|
+
* handles the 401 + Bearer-challenge dance: on a 401 it parses the challenge,
|
|
198
|
+
* fetches a token from the realm with HTTP Basic over the robot creds (caching
|
|
199
|
+
* it per `(ip, scope)`), then retries the original request with the Bearer.
|
|
200
|
+
*/
|
|
201
|
+
function makeClient(transport, username, password) {
|
|
202
|
+
const tokens = new Map();
|
|
203
|
+
const basic = 'Basic ' + buffer_1.Buffer.from(`${username}:${password}`).toString('base64');
|
|
204
|
+
async function fetchToken(ip, realm, service, scope) {
|
|
205
|
+
const key = `${ip}|${scope ?? ''}`;
|
|
206
|
+
const cached = tokens.get(key);
|
|
207
|
+
if (cached)
|
|
208
|
+
return cached;
|
|
209
|
+
const u = new URL(realm);
|
|
210
|
+
if (service)
|
|
211
|
+
u.searchParams.set('service', service);
|
|
212
|
+
if (scope)
|
|
213
|
+
u.searchParams.set('scope', scope);
|
|
214
|
+
const res = await transport({
|
|
215
|
+
ip,
|
|
216
|
+
method: 'GET',
|
|
217
|
+
path: u.pathname + u.search,
|
|
218
|
+
headers: { Authorization: basic },
|
|
219
|
+
});
|
|
220
|
+
if (res.status !== 200) {
|
|
221
|
+
throw new Error(`token request to ${ip} (${realm}) failed: HTTP ${res.status}`);
|
|
222
|
+
}
|
|
223
|
+
const parsed = JSON.parse(res.body.toString('utf8'));
|
|
224
|
+
const token = parsed.token ?? parsed.access_token;
|
|
225
|
+
if (!token)
|
|
226
|
+
throw new Error(`token request to ${ip} returned no token`);
|
|
227
|
+
tokens.set(key, token);
|
|
228
|
+
return token;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Send `req`, transparently authenticating on a 401. `scopeHint` is the scope
|
|
232
|
+
* we proactively request (`repository:<repo>:pull,push`) when the challenge
|
|
233
|
+
* itself omits one.
|
|
234
|
+
*/
|
|
235
|
+
async function send(req, scopeHint) {
|
|
236
|
+
const res = await transport(req);
|
|
237
|
+
if (res.status !== 401)
|
|
238
|
+
return res;
|
|
239
|
+
const wwwAuth = header(res, 'www-authenticate');
|
|
240
|
+
const challenge = wwwAuth ? parseAuthChallenge(wwwAuth) : null;
|
|
241
|
+
if (!challenge)
|
|
242
|
+
return res; // not a bearer challenge we can satisfy
|
|
243
|
+
const token = await fetchToken(req.ip, challenge.realm, challenge.service, challenge.scope ?? scopeHint);
|
|
244
|
+
return transport({
|
|
245
|
+
...req,
|
|
246
|
+
headers: { ...(req.headers ?? {}), Authorization: `Bearer ${token}` },
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return { send };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Parse a single-arch image manifest into its config + layer descriptors.
|
|
253
|
+
* Throws on a manifest-list / image-index mediaType (single-arch deploy only).
|
|
254
|
+
*/
|
|
255
|
+
function parseManifest(bytes, contentType) {
|
|
256
|
+
const doc = JSON.parse(bytes.toString('utf8'));
|
|
257
|
+
const mediaType = contentType ?? doc.mediaType;
|
|
258
|
+
if (mediaType && MANIFEST_LIST_TYPES.includes(mediaType.split(';')[0].trim())) {
|
|
259
|
+
throw new Error(`manifest list not supported (single-arch deploy only): got mediaType "${mediaType}". ` +
|
|
260
|
+
'The CLI builds linux/amd64 --provenance=false --sbom=false, which must yield a single image manifest.');
|
|
261
|
+
}
|
|
262
|
+
if (!doc.config?.digest || !Array.isArray(doc.layers)) {
|
|
263
|
+
throw new Error('source manifest is not a single-arch image manifest (no config/layers)');
|
|
264
|
+
}
|
|
265
|
+
return { config: doc.config, layers: doc.layers };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Fan out an already-pushed image to every cluster Harbor and prove it landed
|
|
269
|
+
* on all of them. See the module header for the full algorithm. Throws if no
|
|
270
|
+
* endpoint serves the digest to copy from, or if re-verification finds any
|
|
271
|
+
* endpoint not serving the pushed digest (the error names the failed IPs).
|
|
272
|
+
*/
|
|
273
|
+
async function ensureImageOnClusters(opts) {
|
|
274
|
+
const transport = opts.transport ?? defaultTransport(opts.host);
|
|
275
|
+
const client = makeClient(transport, opts.username, opts.password);
|
|
276
|
+
const pullPushScope = `repository:${opts.repository}:pull,push`;
|
|
277
|
+
const manifestPath = `/v2/${opts.repository}/manifests/${opts.tag}`;
|
|
278
|
+
// 1. Which endpoints already serve the pushed digest?
|
|
279
|
+
const presence = await Promise.all(opts.endpoints.map(async (ip) => {
|
|
280
|
+
const res = await client.send({ ip, method: 'HEAD', path: manifestPath, headers: { Accept: MANIFEST_ACCEPT } }, pullPushScope);
|
|
281
|
+
const served = res.status === 200 ? header(res, 'docker-content-digest') : undefined;
|
|
282
|
+
return { ip, present: served === opts.digest };
|
|
283
|
+
}));
|
|
284
|
+
const source = presence.find((p) => p.present);
|
|
285
|
+
if (!source) {
|
|
286
|
+
throw new Error(`no cluster endpoint serves ${opts.repository}:${opts.tag}@${opts.digest} — ` +
|
|
287
|
+
'nothing to copy from (the federated push must land on at least one cluster first).');
|
|
288
|
+
}
|
|
289
|
+
// Fast path: every endpoint already serves the digest (e.g. a single-cluster
|
|
290
|
+
// deployment, or a re-run). Nothing to copy — skip the source manifest GET.
|
|
291
|
+
if (presence.every((p) => p.present)) {
|
|
292
|
+
return { targets: presence.map((p) => ({ ip: p.ip, outcome: 'present' })) };
|
|
293
|
+
}
|
|
294
|
+
// 2. Pull the source manifest once (exact bytes + content-type).
|
|
295
|
+
const srcManifestRes = await client.send({ ip: source.ip, method: 'GET', path: manifestPath, headers: { Accept: MANIFEST_ACCEPT } }, pullPushScope);
|
|
296
|
+
if (srcManifestRes.status !== 200) {
|
|
297
|
+
throw new Error(`failed to read source manifest from ${source.ip}: HTTP ${srcManifestRes.status}`);
|
|
298
|
+
}
|
|
299
|
+
const manifestContentType = header(srcManifestRes, 'content-type');
|
|
300
|
+
const parsed = parseManifest(srcManifestRes.body, manifestContentType); // throws on manifest list
|
|
301
|
+
const blobDigests = [parsed.config.digest, ...parsed.layers.map((l) => l.digest)];
|
|
302
|
+
// 3. Copy to every non-present endpoint.
|
|
303
|
+
const report = { targets: [] };
|
|
304
|
+
for (const p of presence) {
|
|
305
|
+
if (p.present) {
|
|
306
|
+
report.targets.push({ ip: p.ip, outcome: 'present' });
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
await copyToDest({
|
|
310
|
+
client,
|
|
311
|
+
source: source.ip,
|
|
312
|
+
dest: p.ip,
|
|
313
|
+
repository: opts.repository,
|
|
314
|
+
tag: opts.tag,
|
|
315
|
+
pullPushScope,
|
|
316
|
+
blobDigests,
|
|
317
|
+
manifestBytes: srcManifestRes.body,
|
|
318
|
+
manifestContentType,
|
|
319
|
+
manifestPath,
|
|
320
|
+
});
|
|
321
|
+
report.targets.push({ ip: p.ip, outcome: 'copied' });
|
|
322
|
+
}
|
|
323
|
+
// 4. Re-verify the tag on EVERY endpoint; any mismatch is fatal.
|
|
324
|
+
const failures = [];
|
|
325
|
+
await Promise.all(opts.endpoints.map(async (ip) => {
|
|
326
|
+
const res = await client.send({ ip, method: 'HEAD', path: manifestPath, headers: { Accept: MANIFEST_ACCEPT } }, pullPushScope);
|
|
327
|
+
const served = res.status === 200 ? header(res, 'docker-content-digest') : undefined;
|
|
328
|
+
if (served !== opts.digest) {
|
|
329
|
+
failures.push(ip);
|
|
330
|
+
const t = report.targets.find((x) => x.ip === ip);
|
|
331
|
+
if (t) {
|
|
332
|
+
t.outcome = 'failed';
|
|
333
|
+
t.error = `re-verify: served digest ${served ?? '<none>'} !== ${opts.digest}`;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}));
|
|
337
|
+
if (failures.length > 0) {
|
|
338
|
+
throw new Error(`image fan-out incomplete: ${failures.sort().join(', ')} do not serve ` +
|
|
339
|
+
`${opts.repository}:${opts.tag}@${opts.digest} after copy — deploy aborted.`);
|
|
340
|
+
}
|
|
341
|
+
return report;
|
|
342
|
+
}
|
|
343
|
+
/** Copy the config + layer blobs (skipping ones the dest already has) and PUT the manifest. */
|
|
344
|
+
async function copyToDest(args) {
|
|
345
|
+
const { client, source, dest, repository, pullPushScope } = args;
|
|
346
|
+
for (const digest of args.blobDigests) {
|
|
347
|
+
// HEAD on dest — skip the upload entirely if the blob already exists.
|
|
348
|
+
const head = await client.send({ ip: dest, method: 'HEAD', path: `/v2/${repository}/blobs/${digest}` }, pullPushScope);
|
|
349
|
+
if (head.status === 200)
|
|
350
|
+
continue;
|
|
351
|
+
// Fetch the blob bytes from the source.
|
|
352
|
+
const blob = await client.send({ ip: source, method: 'GET', path: `/v2/${repository}/blobs/${digest}` }, pullPushScope);
|
|
353
|
+
if (blob.status !== 200) {
|
|
354
|
+
throw new Error(`failed to fetch blob ${digest} from source ${source}: HTTP ${blob.status}`);
|
|
355
|
+
}
|
|
356
|
+
// Start an upload session, then monolithic-PUT to its Location with ?digest=.
|
|
357
|
+
const start = await client.send({ ip: dest, method: 'POST', path: `/v2/${repository}/blobs/uploads/` }, pullPushScope);
|
|
358
|
+
if (start.status !== 202) {
|
|
359
|
+
throw new Error(`failed to start blob upload on ${dest}: HTTP ${start.status}`);
|
|
360
|
+
}
|
|
361
|
+
const location = header(start, 'location');
|
|
362
|
+
if (!location)
|
|
363
|
+
throw new Error(`blob upload on ${dest} returned no Location`);
|
|
364
|
+
const putPath = appendDigestQuery(locationPath(location), digest);
|
|
365
|
+
const put = await client.send({
|
|
366
|
+
ip: dest,
|
|
367
|
+
method: 'PUT',
|
|
368
|
+
path: putPath,
|
|
369
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
370
|
+
body: blob.body,
|
|
371
|
+
}, pullPushScope);
|
|
372
|
+
if (put.status !== 201) {
|
|
373
|
+
throw new Error(`failed to upload blob ${digest} to ${dest}: HTTP ${put.status}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// All blobs present on dest → PUT the manifest with the EXACT source bytes + content-type.
|
|
377
|
+
const headers = {};
|
|
378
|
+
if (args.manifestContentType)
|
|
379
|
+
headers['Content-Type'] = args.manifestContentType;
|
|
380
|
+
const put = await client.send({ ip: dest, method: 'PUT', path: args.manifestPath, headers, body: args.manifestBytes }, pullPushScope);
|
|
381
|
+
if (put.status !== 201 && put.status !== 200) {
|
|
382
|
+
throw new Error(`failed to PUT manifest to ${dest}: HTTP ${put.status}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/** Append `digest=<d>` to a `Location`, respecting any query string already present. */
|
|
386
|
+
function appendDigestQuery(path, digest) {
|
|
387
|
+
const sep = path.includes('?') ? '&' : '?';
|
|
388
|
+
return `${path}${sep}digest=${encodeURIComponent(digest)}`;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The deploy-time entrypoint: take an image just pushed to the federated host
|
|
392
|
+
* (landed on ONE cluster) and guarantee it is present on EVERY cluster Harbor.
|
|
393
|
+
*
|
|
394
|
+
* Resolves the registry host's A-records into the cluster-ingress set, then:
|
|
395
|
+
* - 0/1 endpoint (a non-federated or local/dev registry, or DNS that doesn't
|
|
396
|
+
* fan out) → nothing to distribute; returns `null`.
|
|
397
|
+
* - ≥2 endpoints → {@link ensureImageOnClusters}, which copies + re-verifies
|
|
398
|
+
* and THROWS if any cluster ends up not serving the pushed digest.
|
|
399
|
+
*
|
|
400
|
+
* A DNS resolution failure is treated as "single-target" (logged, returns
|
|
401
|
+
* `null`) rather than fatal: a real federated push has already proven the host
|
|
402
|
+
* resolves, so a failure here means a dev/local registry where fan-out is moot.
|
|
403
|
+
*/
|
|
404
|
+
async function fanOutPushedImage(opts) {
|
|
405
|
+
const log = opts.log ?? (() => undefined);
|
|
406
|
+
const host = opts.registry.server.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
|
407
|
+
let endpoints;
|
|
408
|
+
try {
|
|
409
|
+
endpoints = await resolveClusterEndpoints(host, opts.resolve4);
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
log(` distribute: ${host} did not resolve (${err.message}); single-target, skipping fan-out`);
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
if (endpoints.length <= 1) {
|
|
416
|
+
log(` distribute: ${host} resolves to ${endpoints.length} endpoint(s); single-target, skipping fan-out`);
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
log(` distribute: fanning ${opts.registry.repository}:${opts.tag} to ${endpoints.length} clusters (${endpoints.join(', ')})`);
|
|
420
|
+
const report = await ensureImageOnClusters({
|
|
421
|
+
host,
|
|
422
|
+
repository: opts.registry.repository,
|
|
423
|
+
tag: opts.tag,
|
|
424
|
+
digest: opts.digest,
|
|
425
|
+
username: opts.registry.username,
|
|
426
|
+
password: opts.registry.password,
|
|
427
|
+
endpoints,
|
|
428
|
+
transport: opts.transport,
|
|
429
|
+
});
|
|
430
|
+
for (const t of report.targets) {
|
|
431
|
+
log(` distribute: ${t.ip} → ${t.outcome}${t.error ? ` (${t.error})` : ''}`);
|
|
432
|
+
}
|
|
433
|
+
return report;
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=registry-fanout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-fanout.js","sourceRoot":"","sources":["../../src/_lib/registry-fanout.ts"],"names":[],"mappings":"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"}
|