@serve.zone/interfaces 31.4.0 → 31.6.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/changelog.md +78 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/backup.d.ts +13 -0
- package/dist_ts/data/cluster.d.ts +44 -9
- package/dist_ts/data/cluster.js +35 -12
- package/dist_ts/data/clusternode.d.ts +49 -0
- package/dist_ts/data/clusternode.js +47 -1
- package/dist_ts/data/corestore.d.ts +27 -0
- package/dist_ts/data/corestore.js +11 -0
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/isolatedrestore.js +42 -2
- package/dist_ts/requests/backup.d.ts +223 -0
- package/dist_ts/requests/cluster.d.ts +17 -0
- package/dist_ts/requests/corestore.d.ts +57 -0
- package/dist_ts/requests/corestore.js +102 -1
- package/dist_ts/requests/platform.d.ts +30 -0
- package/dist_ts/runtime.corestore.d.ts +7 -10
- package/dist_ts/runtime.corestore.js +1 -1
- package/dist_ts/runtime.corestore.relay.d.ts +47 -0
- package/dist_ts/runtime.corestore.relay.js +57 -0
- package/dist_ts/runtime.d.ts +1 -0
- package/dist_ts/runtime.js +2 -1
- package/package.json +1 -1
- package/readme.md +120 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/backup.ts +14 -0
- package/ts/data/cluster.ts +76 -17
- package/ts/data/clusternode.ts +93 -0
- package/ts/data/corestore.ts +28 -0
- package/ts/data/index.ts +1 -0
- package/ts/data/isolatedrestore.ts +53 -1
- package/ts/requests/backup.ts +253 -0
- package/ts/requests/cluster.ts +21 -0
- package/ts/requests/corestore.ts +183 -0
- package/ts/requests/platform.ts +36 -0
- package/ts/runtime.corestore.relay.ts +111 -0
- package/ts/runtime.corestore.ts +7 -8
- package/ts/runtime.ts +1 -0
package/ts/data/clusternode.ts
CHANGED
|
@@ -1010,5 +1010,98 @@ export interface IClusterNode {
|
|
|
1010
1010
|
* Debian packages installed on this node
|
|
1011
1011
|
*/
|
|
1012
1012
|
requiredDebianPackages: string[];
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Where this node's Corestore answers its control API.
|
|
1016
|
+
*
|
|
1017
|
+
* Cloudly owns it and the relay reads it. Absent means this node runs no
|
|
1018
|
+
* Corestore the relay may reach, which is a fact to report rather than a
|
|
1019
|
+
* reason to guess a hostname: the cluster-local alias every component used
|
|
1020
|
+
* while Docker Swarm resolved it is exactly what stops resolving.
|
|
1021
|
+
*/
|
|
1022
|
+
corestore?: IClusterNodeCorestore;
|
|
1013
1023
|
};
|
|
1014
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* How the cluster relay reaches one node's Corestore.
|
|
1028
|
+
*
|
|
1029
|
+
* Corestore is node-local: the database and the bucket of a workload live on
|
|
1030
|
+
* the node the workload runs on, so one relay serving a whole cluster must be
|
|
1031
|
+
* told, per node, where that node's control API listens. This is control-plane
|
|
1032
|
+
* state, owned by Cloudly and stored in its database like every other field of
|
|
1033
|
+
* the cluster document — never a file, an alias or an environment variable on
|
|
1034
|
+
* the relay.
|
|
1035
|
+
*/
|
|
1036
|
+
export interface IClusterNodeCorestore {
|
|
1037
|
+
/**
|
|
1038
|
+
* The canonical origin of the control API, for example
|
|
1039
|
+
* `http://10.20.0.15:3000`.
|
|
1040
|
+
*
|
|
1041
|
+
* `http:` is admitted because Corestore terminates no TLS today — it binds a
|
|
1042
|
+
* plain control port and advertises no transport-encryption guarantee — and
|
|
1043
|
+
* the hop is cluster-private by construction, reachable only from inside the
|
|
1044
|
+
* cluster. `https:` is admitted too, so a deployment that puts TLS in front
|
|
1045
|
+
* of Corestore needs no contract change.
|
|
1046
|
+
*
|
|
1047
|
+
* The endpoint says where, never with what: the bearer control token that
|
|
1048
|
+
* authorizes every call is obtained from Cloudly through
|
|
1049
|
+
* `getCorestoreControlCredentialMaterial`, sealed to the X25519 recipient the
|
|
1050
|
+
* relay enrolled with `beginSecretRecipientEnrollment` and
|
|
1051
|
+
* `completeSecretRecipientEnrollment`. No credential ever travels in this
|
|
1052
|
+
* document, and none is read from the relay's environment.
|
|
1053
|
+
*/
|
|
1054
|
+
endpoint: string;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const maximumClusterNodeCorestoreEndpointLength = 2048;
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Validates one node's Corestore endpoint and names every reason it is refused.
|
|
1061
|
+
*
|
|
1062
|
+
* A relay that dialled a wrong or ambiguous endpoint would provision a
|
|
1063
|
+
* workload's database on a node that is not the workload's, so the origin must
|
|
1064
|
+
* be exact: a scheme this contract admits, a host, an optional port, and
|
|
1065
|
+
* nothing else — no path, no query, no fragment, no userinfo, no trailing
|
|
1066
|
+
* slash.
|
|
1067
|
+
*/
|
|
1068
|
+
export const validateClusterNodeCorestore = (
|
|
1069
|
+
valueArg: unknown,
|
|
1070
|
+
pathArg = 'cluster node corestore',
|
|
1071
|
+
): string[] => {
|
|
1072
|
+
if (!valueArg
|
|
1073
|
+
|| typeof valueArg !== 'object'
|
|
1074
|
+
|| Array.isArray(valueArg)
|
|
1075
|
+
|| (Object.getPrototypeOf(valueArg) !== Object.prototype
|
|
1076
|
+
&& Object.getPrototypeOf(valueArg) !== null)
|
|
1077
|
+
|| Object.keys(valueArg as Record<string, unknown>).join(',') !== 'endpoint') {
|
|
1078
|
+
return [`${pathArg} must use its exact schema`];
|
|
1079
|
+
}
|
|
1080
|
+
const endpoint = (valueArg as { endpoint: unknown }).endpoint;
|
|
1081
|
+
if (typeof endpoint !== 'string'
|
|
1082
|
+
|| endpoint.length === 0
|
|
1083
|
+
|| endpoint.length > maximumClusterNodeCorestoreEndpointLength) {
|
|
1084
|
+
return [`${pathArg} endpoint must be a string of 1 to `
|
|
1085
|
+
+ `${maximumClusterNodeCorestoreEndpointLength} characters`];
|
|
1086
|
+
}
|
|
1087
|
+
let parsedEndpoint: URL;
|
|
1088
|
+
try {
|
|
1089
|
+
parsedEndpoint = new URL(endpoint);
|
|
1090
|
+
} catch {
|
|
1091
|
+
return [`${pathArg} endpoint must be a parsable URL`];
|
|
1092
|
+
}
|
|
1093
|
+
const errors: string[] = [];
|
|
1094
|
+
if (parsedEndpoint.protocol !== 'http:' && parsedEndpoint.protocol !== 'https:') {
|
|
1095
|
+
errors.push(`${pathArg} endpoint must use the http or https scheme`);
|
|
1096
|
+
}
|
|
1097
|
+
if (parsedEndpoint.username || parsedEndpoint.password) {
|
|
1098
|
+
errors.push(`${pathArg} endpoint must not carry userinfo`);
|
|
1099
|
+
}
|
|
1100
|
+
if (parsedEndpoint.origin !== endpoint) {
|
|
1101
|
+
errors.push(
|
|
1102
|
+
`${pathArg} endpoint must be the canonical origin ${parsedEndpoint.origin}, `
|
|
1103
|
+
+ 'without a path, query, fragment, trailing slash or default port',
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
return errors;
|
|
1107
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Corestore shapes the control plane exchanges outside the Node-only
|
|
3
|
+
* `/runtime` export.
|
|
4
|
+
*
|
|
5
|
+
* Only what a browser-visible contract has to name lives here. Everything that
|
|
6
|
+
* needs Corestore's canonical JSON, digests or receipts stays in
|
|
7
|
+
* `runtime.corestore.*`, which imports this declaration rather than repeating
|
|
8
|
+
* it: one structure, one rule set, two entry points.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The safe reference to an allocation-managed Corestore database.
|
|
13
|
+
*
|
|
14
|
+
* Corestore returns it when a database is allocated and requires the exact same
|
|
15
|
+
* value back on every later operation against that database. It intentionally
|
|
16
|
+
* excludes the NoSQLDB principal, the publication capability, the database URI,
|
|
17
|
+
* the password and every provider-private evidence field, so it can travel
|
|
18
|
+
* through a control plane, be persisted as authority and be quoted in a
|
|
19
|
+
* validation reason without disclosing a credential.
|
|
20
|
+
*/
|
|
21
|
+
export interface ICorestoreDatabaseAllocationReference {
|
|
22
|
+
purpose: 'scratch-rehearsal';
|
|
23
|
+
rehearsalId: string;
|
|
24
|
+
allocationId: string;
|
|
25
|
+
generation: number;
|
|
26
|
+
bindingSha256: string;
|
|
27
|
+
receiptSha256: string;
|
|
28
|
+
}
|
package/ts/data/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './cloudlyconfig.js';
|
|
|
2
2
|
export * from './cluster.js';
|
|
3
3
|
export * from './clusteringress.js';
|
|
4
4
|
export * from './config.js';
|
|
5
|
+
export * from './corestore.js';
|
|
5
6
|
export * from './coremail.js';
|
|
6
7
|
export * from './coremail.runtime.js';
|
|
7
8
|
export * from './deployment.js';
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
IBackupArchiveObject,
|
|
4
4
|
TIsolatedRestoreResourceMapping,
|
|
5
5
|
} from './backup.js';
|
|
6
|
+
import type { ICorestoreDatabaseAllocationReference } from './corestore.js';
|
|
6
7
|
import {
|
|
7
8
|
canonicalizeStrictJson,
|
|
8
9
|
createCanonicalJsonSha256Hex,
|
|
@@ -614,6 +615,44 @@ const readSafeInteger = (
|
|
|
614
615
|
return valueArg;
|
|
615
616
|
};
|
|
616
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Corestore's safe reference to an allocation-managed database, exactly as it
|
|
620
|
+
* issued it.
|
|
621
|
+
*
|
|
622
|
+
* Every field is read here rather than trusted, because this value is bound
|
|
623
|
+
* into the restore grant: a reference the coordinator mistyped would be signed
|
|
624
|
+
* authority for a database nobody meant to replace.
|
|
625
|
+
*/
|
|
626
|
+
const readDatabaseAllocationReference = (
|
|
627
|
+
valueArg: unknown,
|
|
628
|
+
fieldNameArg: string,
|
|
629
|
+
): ICorestoreDatabaseAllocationReference => {
|
|
630
|
+
const value = readRecord(valueArg, fieldNameArg);
|
|
631
|
+
assertExactKeys(
|
|
632
|
+
value,
|
|
633
|
+
[
|
|
634
|
+
'purpose',
|
|
635
|
+
'rehearsalId',
|
|
636
|
+
'allocationId',
|
|
637
|
+
'generation',
|
|
638
|
+
'bindingSha256',
|
|
639
|
+
'receiptSha256',
|
|
640
|
+
],
|
|
641
|
+
fieldNameArg,
|
|
642
|
+
);
|
|
643
|
+
if (value.purpose !== 'scratch-rehearsal') {
|
|
644
|
+
return fail(`${fieldNameArg}.purpose must be scratch-rehearsal`);
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
purpose: 'scratch-rehearsal',
|
|
648
|
+
rehearsalId: readScopedId(value.rehearsalId, `${fieldNameArg}.rehearsalId`),
|
|
649
|
+
allocationId: readSha256(value.allocationId, `${fieldNameArg}.allocationId`),
|
|
650
|
+
generation: readSafeInteger(value.generation, `${fieldNameArg}.generation`, 1),
|
|
651
|
+
bindingSha256: readSha256(value.bindingSha256, `${fieldNameArg}.bindingSha256`),
|
|
652
|
+
receiptSha256: readSha256(value.receiptSha256, `${fieldNameArg}.receiptSha256`),
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
|
|
617
656
|
const readMountPath = (valueArg: unknown, fieldNameArg: string): string => {
|
|
618
657
|
const value = readString(
|
|
619
658
|
valueArg,
|
|
@@ -871,7 +910,9 @@ const normalizeMapping = (
|
|
|
871
910
|
);
|
|
872
911
|
assertExactKeys(
|
|
873
912
|
target,
|
|
874
|
-
|
|
913
|
+
Object.hasOwn(target, 'databaseAllocation')
|
|
914
|
+
? ['resourceName', 'databaseName', 'databaseAllocation']
|
|
915
|
+
: ['resourceName', 'databaseName'],
|
|
875
916
|
`${fieldName}.target`,
|
|
876
917
|
);
|
|
877
918
|
const sourceResourceName = readResourceName(
|
|
@@ -910,6 +951,17 @@ const normalizeMapping = (
|
|
|
910
951
|
target: {
|
|
911
952
|
resourceName: targetResourceName,
|
|
912
953
|
databaseName: targetDatabaseName,
|
|
954
|
+
// Present exactly when the target is allocation-managed, and therefore
|
|
955
|
+
// part of what the mapping digest binds: Corestore refuses an allocated
|
|
956
|
+
// target whose exact reference the authority did not cover.
|
|
957
|
+
...(Object.hasOwn(target, 'databaseAllocation')
|
|
958
|
+
? {
|
|
959
|
+
databaseAllocation: readDatabaseAllocationReference(
|
|
960
|
+
target.databaseAllocation,
|
|
961
|
+
`${fieldName}.target.databaseAllocation`,
|
|
962
|
+
),
|
|
963
|
+
}
|
|
964
|
+
: {}),
|
|
913
965
|
},
|
|
914
966
|
};
|
|
915
967
|
}
|
package/ts/requests/backup.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
} from '../data/backup.js';
|
|
14
14
|
import type { IService } from '../data/service.js';
|
|
15
15
|
import type { IIdentity, IIdentityCredential } from '../data/user.js';
|
|
16
|
+
import type { ICorestoreDatabaseClosureDescriptor } from '../runtime.corestore.relay.js';
|
|
16
17
|
|
|
17
18
|
export interface IReq_Any_Cloudly_CreateServiceBackup
|
|
18
19
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
@@ -80,6 +81,15 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Superseded by `backupClusterService`, which names the node.
|
|
86
|
+
*
|
|
87
|
+
* This method was dispatched at the coreflow that had tagged itself with the
|
|
88
|
+
* node's hostname; a cluster relay authors no tags and serves every node of its
|
|
89
|
+
* cluster, so the node it should act on can no longer be inferred from which
|
|
90
|
+
* connection answered. It is retired together with the tag in the coordinated
|
|
91
|
+
* major.
|
|
92
|
+
*/
|
|
83
93
|
export interface IReq_Cloudly_Coreflow_ExecuteServiceBackup
|
|
84
94
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
85
95
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
@@ -100,6 +110,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
100
110
|
};
|
|
101
111
|
}
|
|
102
112
|
|
|
113
|
+
/** Superseded by `restoreClusterService`; see `executeServiceBackup`. */
|
|
103
114
|
export interface IReq_Cloudly_Coreflow_ExecuteServiceRestore
|
|
104
115
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
105
116
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
@@ -137,6 +148,14 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
137
148
|
};
|
|
138
149
|
}
|
|
139
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Retiring: `contentsBase64` carries a whole archive object inside one JSON
|
|
153
|
+
* envelope, which costs a third of the payload in encoding and bounds a backup
|
|
154
|
+
* by the message ceiling rather than by the transport. `backupServiceDatabaseClosure`
|
|
155
|
+
* streams the same bytes as a VirtualStream. Removal is the coordinated major,
|
|
156
|
+
* once every relay and every Cloudly speak the streamed contract; Corestore
|
|
157
|
+
* already documents its repository-wide archive object routes as legacy.
|
|
158
|
+
*/
|
|
140
159
|
export interface IReq_Coreflow_Cloudly_UploadBackupArchiveObject
|
|
141
160
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
142
161
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
@@ -185,6 +204,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
185
204
|
};
|
|
186
205
|
}
|
|
187
206
|
|
|
207
|
+
/** Retiring with `uploadBackupArchiveObject`; `restoreServiceDatabaseClosure` replaces it. */
|
|
188
208
|
export interface IReq_Coreflow_Cloudly_DownloadBackupArchiveObject
|
|
189
209
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
190
210
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
@@ -202,6 +222,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
202
222
|
};
|
|
203
223
|
}
|
|
204
224
|
|
|
225
|
+
/** Superseded by `pruneClusterNodeArchive`; see `executeServiceBackup`. */
|
|
205
226
|
export interface IReq_Cloudly_Coreflow_PruneNodeArchive
|
|
206
227
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
207
228
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
@@ -218,6 +239,152 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
218
239
|
};
|
|
219
240
|
}
|
|
220
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Backs up one service on one exact node of this cluster.
|
|
244
|
+
*
|
|
245
|
+
* The cluster relay serves every node of its cluster over one session, so the
|
|
246
|
+
* node is a request field: the connection that answers proves which cluster is
|
|
247
|
+
* speaking, never which node is meant. Everything else is what
|
|
248
|
+
* `executeServiceBackup` carried, so a caller moving to this method changes its
|
|
249
|
+
* routing and nothing about its backup.
|
|
250
|
+
*/
|
|
251
|
+
export interface IReq_Cloudly_Relay_BackupClusterService
|
|
252
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
253
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
254
|
+
IReq_Cloudly_Relay_BackupClusterService
|
|
255
|
+
> {
|
|
256
|
+
method: 'backupClusterService';
|
|
257
|
+
request: {
|
|
258
|
+
/** The cluster node whose Corestore holds this service's data. */
|
|
259
|
+
nodeId: string;
|
|
260
|
+
backupId: string;
|
|
261
|
+
service: IService;
|
|
262
|
+
tags?: Record<string, string>;
|
|
263
|
+
replication?: {
|
|
264
|
+
enabled: boolean;
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
response: {
|
|
268
|
+
snapshots: TBackupSnapshot[];
|
|
269
|
+
replication?: IBackupReplicationResult;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Restores one service on one exact node of this cluster; see `backupClusterService`. */
|
|
274
|
+
export interface IReq_Cloudly_Relay_RestoreClusterService
|
|
275
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
276
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
277
|
+
IReq_Cloudly_Relay_RestoreClusterService
|
|
278
|
+
> {
|
|
279
|
+
method: 'restoreClusterService';
|
|
280
|
+
request: {
|
|
281
|
+
/** The cluster node whose Corestore receives the restore. */
|
|
282
|
+
nodeId: string;
|
|
283
|
+
backupId: string;
|
|
284
|
+
service: IService;
|
|
285
|
+
snapshots: TBackupSnapshot[];
|
|
286
|
+
clear?: boolean;
|
|
287
|
+
resourceTypes?: Array<TBackupSnapshot['type']>;
|
|
288
|
+
replication?: {
|
|
289
|
+
enabled: boolean;
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
response: {
|
|
293
|
+
restored: TBackupSnapshot[];
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Prunes the archive of one exact node of this cluster.
|
|
299
|
+
*
|
|
300
|
+
* `found` stays false when that node has no archive to prune, which is a fact
|
|
301
|
+
* about the node rather than a failure of the relay.
|
|
302
|
+
*/
|
|
303
|
+
export interface IReq_Cloudly_Relay_PruneClusterNodeArchive
|
|
304
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
305
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
306
|
+
IReq_Cloudly_Relay_PruneClusterNodeArchive
|
|
307
|
+
> {
|
|
308
|
+
method: 'pruneClusterNodeArchive';
|
|
309
|
+
request: {
|
|
310
|
+
/** The cluster node whose archive is pruned. */
|
|
311
|
+
nodeId: string;
|
|
312
|
+
retention: IArchiveRetentionPolicy;
|
|
313
|
+
dryRun?: boolean;
|
|
314
|
+
};
|
|
315
|
+
response: {
|
|
316
|
+
found: boolean;
|
|
317
|
+
result?: IArchivePruneResult;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The relay carries one service database closure to Cloudly.
|
|
323
|
+
*
|
|
324
|
+
* Relay-initiated, like every other byte a cluster sends: a cluster is
|
|
325
|
+
* outbound-only, so Cloudly cannot pull from it. The relay exports the closure
|
|
326
|
+
* from its node's Corestore and sends those exact bytes on its own session.
|
|
327
|
+
*
|
|
328
|
+
* `accepted` permits sending; it never confirms storage. The stream's own
|
|
329
|
+
* acceptance receipt, observed through `completion`, is what confirms that
|
|
330
|
+
* Cloudly durably holds the closure — exactly as `pushImageVersion` works.
|
|
331
|
+
*
|
|
332
|
+
* No identity field: the cluster is the one the verified machine identity on
|
|
333
|
+
* the relay's own session names, exactly as `getClusterRouteTable` and
|
|
334
|
+
* `getClusterPlatformDesiredState` work. A request-level identity would be a
|
|
335
|
+
* second authority for the same fact, and the weaker of the two.
|
|
336
|
+
*/
|
|
337
|
+
export interface IReq_Relay_Cloudly_BackupServiceDatabaseClosure
|
|
338
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
339
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
340
|
+
IReq_Relay_Cloudly_BackupServiceDatabaseClosure
|
|
341
|
+
> {
|
|
342
|
+
method: 'backupServiceDatabaseClosure';
|
|
343
|
+
request: {
|
|
344
|
+
backupId: string;
|
|
345
|
+
serviceId: string;
|
|
346
|
+
/** The cluster node this closure was exported from. */
|
|
347
|
+
nodeId: string;
|
|
348
|
+
/** Exact bytes and Corestore's receipt for them; validated before any byte moves. */
|
|
349
|
+
descriptor: ICorestoreDatabaseClosureDescriptor;
|
|
350
|
+
/** Requester sends closure bytes; the TypedHandler receives the opposite endpoint. */
|
|
351
|
+
closure: plugins.typedrequestInterfaces.TVirtualStream<'send'>;
|
|
352
|
+
};
|
|
353
|
+
response: {
|
|
354
|
+
accepted: boolean;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The relay fetches one service database closure back from Cloudly.
|
|
360
|
+
*
|
|
361
|
+
* The descriptor arrives with the stream rather than after it, because
|
|
362
|
+
* Corestore's restore needs the exact byte length and receipt before the first
|
|
363
|
+
* byte is written: the relay opens its Corestore request from this descriptor
|
|
364
|
+
* and only then drains the stream into it.
|
|
365
|
+
*
|
|
366
|
+
* No identity field, for the same reason as `backupServiceDatabaseClosure`: the
|
|
367
|
+
* relay's own session names the cluster.
|
|
368
|
+
*/
|
|
369
|
+
export interface IReq_Relay_Cloudly_RestoreServiceDatabaseClosure
|
|
370
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
371
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
372
|
+
IReq_Relay_Cloudly_RestoreServiceDatabaseClosure
|
|
373
|
+
> {
|
|
374
|
+
method: 'restoreServiceDatabaseClosure';
|
|
375
|
+
request: {
|
|
376
|
+
backupId: string;
|
|
377
|
+
serviceId: string;
|
|
378
|
+
/** The cluster node the closure is restored onto. */
|
|
379
|
+
nodeId: string;
|
|
380
|
+
};
|
|
381
|
+
response: {
|
|
382
|
+
descriptor: ICorestoreDatabaseClosureDescriptor;
|
|
383
|
+
/** Requester receives closure bytes; the TypedHandler returns a sender endpoint. */
|
|
384
|
+
closure: plugins.typedrequestInterfaces.TVirtualStream<'receive'>;
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
221
388
|
/**
|
|
222
389
|
* Creates a restore in a new non-runnable data-plane scratch namespace.
|
|
223
390
|
* Cloudly derives clusterId and sourceServiceId from the authoritative backup,
|
|
@@ -316,6 +483,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
316
483
|
> {
|
|
317
484
|
method: 'coreflowPrepareIsolatedRestore';
|
|
318
485
|
request: {
|
|
486
|
+
/**
|
|
487
|
+
* The compact RS256 restore grant Cloudly signed for exactly this
|
|
488
|
+
* operation, carried untouched to Corestore.
|
|
489
|
+
*
|
|
490
|
+
* Corestore accepts no other authority on an isolated-restore route: the
|
|
491
|
+
* grant names the operation, expires within minutes, and binds the cluster,
|
|
492
|
+
* the node, the canonical resource mappings and the archive manifest, so a
|
|
493
|
+
* relay can carry it but can neither widen it nor mint one. Sensitive
|
|
494
|
+
* ephemeral input: exclude the complete request from hooks, logs and
|
|
495
|
+
* journals on every hop.
|
|
496
|
+
*/
|
|
497
|
+
restoreGrant: string;
|
|
319
498
|
clusterId: string;
|
|
320
499
|
targetNodeName: string;
|
|
321
500
|
restoreId: string;
|
|
@@ -332,6 +511,44 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
332
511
|
};
|
|
333
512
|
}
|
|
334
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Cloudly stages one archive object of an isolated restore on the relay's node.
|
|
516
|
+
*
|
|
517
|
+
* Cloudly holds the source backup, so these bytes travel Cloudly → relay and
|
|
518
|
+
* the requester is the sender. One object per call, as the archive manifest
|
|
519
|
+
* lists it: Corestore writes staged objects under a `write-object` grant, one
|
|
520
|
+
* bounded chunk at a time, and the relay is what turns this stream into those
|
|
521
|
+
* chunks. The object descriptor is the manifest entry itself — path, exact size
|
|
522
|
+
* and digest — so the relay can prove the complete object before it counts as
|
|
523
|
+
* written, and so a replayed stage settles on the same bytes.
|
|
524
|
+
*/
|
|
525
|
+
export interface IReq_Cloudly_Relay_StageIsolatedRestoreArchive
|
|
526
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
527
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
528
|
+
IReq_Cloudly_Relay_StageIsolatedRestoreArchive
|
|
529
|
+
> {
|
|
530
|
+
method: 'stageIsolatedRestoreArchive';
|
|
531
|
+
request: {
|
|
532
|
+
/**
|
|
533
|
+
* The compact RS256 restore grant for the `write-object` operation, carried
|
|
534
|
+
* untouched to Corestore. Sensitive ephemeral input, like every other
|
|
535
|
+
* grant on these routes.
|
|
536
|
+
*/
|
|
537
|
+
restoreGrant: string;
|
|
538
|
+
clusterId: string;
|
|
539
|
+
targetNodeName: string;
|
|
540
|
+
restoreId: string;
|
|
541
|
+
expectedRevision: number;
|
|
542
|
+
/** The manifest entry these bytes complete: `path`, `size`, `sha256`. */
|
|
543
|
+
object: IBackupArchiveObject;
|
|
544
|
+
/** Requester sends the object bytes; the TypedHandler receives them. */
|
|
545
|
+
archive: plugins.typedrequestInterfaces.TVirtualStream<'send'>;
|
|
546
|
+
};
|
|
547
|
+
response: {
|
|
548
|
+
state: IIsolatedRestoreNodeState;
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
335
552
|
/** Read-only status for one exact cluster/node/restore routing key. */
|
|
336
553
|
export interface IReq_Cloudly_Coreflow_GetIsolatedRestoreStatus
|
|
337
554
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
@@ -340,6 +557,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
340
557
|
> {
|
|
341
558
|
method: 'coreflowGetIsolatedRestoreStatus';
|
|
342
559
|
request: {
|
|
560
|
+
/**
|
|
561
|
+
* The compact RS256 restore grant Cloudly signed for exactly this
|
|
562
|
+
* operation, carried untouched to Corestore.
|
|
563
|
+
*
|
|
564
|
+
* Corestore accepts no other authority on an isolated-restore route: the
|
|
565
|
+
* grant names the operation, expires within minutes, and binds the cluster,
|
|
566
|
+
* the node, the canonical resource mappings and the archive manifest, so a
|
|
567
|
+
* relay can carry it but can neither widen it nor mint one. Sensitive
|
|
568
|
+
* ephemeral input: exclude the complete request from hooks, logs and
|
|
569
|
+
* journals on every hop.
|
|
570
|
+
*/
|
|
571
|
+
restoreGrant: string;
|
|
343
572
|
clusterId: string;
|
|
344
573
|
targetNodeName: string;
|
|
345
574
|
restoreId: string;
|
|
@@ -357,6 +586,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
357
586
|
> {
|
|
358
587
|
method: 'coreflowExecuteIsolatedRestore';
|
|
359
588
|
request: {
|
|
589
|
+
/**
|
|
590
|
+
* The compact RS256 restore grant Cloudly signed for exactly this
|
|
591
|
+
* operation, carried untouched to Corestore.
|
|
592
|
+
*
|
|
593
|
+
* Corestore accepts no other authority on an isolated-restore route: the
|
|
594
|
+
* grant names the operation, expires within minutes, and binds the cluster,
|
|
595
|
+
* the node, the canonical resource mappings and the archive manifest, so a
|
|
596
|
+
* relay can carry it but can neither widen it nor mint one. Sensitive
|
|
597
|
+
* ephemeral input: exclude the complete request from hooks, logs and
|
|
598
|
+
* journals on every hop.
|
|
599
|
+
*/
|
|
600
|
+
restoreGrant: string;
|
|
360
601
|
clusterId: string;
|
|
361
602
|
targetNodeName: string;
|
|
362
603
|
restoreId: string;
|
|
@@ -378,6 +619,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
378
619
|
> {
|
|
379
620
|
method: 'coreflowCleanupIsolatedRestore';
|
|
380
621
|
request: {
|
|
622
|
+
/**
|
|
623
|
+
* The compact RS256 restore grant Cloudly signed for exactly this
|
|
624
|
+
* operation, carried untouched to Corestore.
|
|
625
|
+
*
|
|
626
|
+
* Corestore accepts no other authority on an isolated-restore route: the
|
|
627
|
+
* grant names the operation, expires within minutes, and binds the cluster,
|
|
628
|
+
* the node, the canonical resource mappings and the archive manifest, so a
|
|
629
|
+
* relay can carry it but can neither widen it nor mint one. Sensitive
|
|
630
|
+
* ephemeral input: exclude the complete request from hooks, logs and
|
|
631
|
+
* journals on every hop.
|
|
632
|
+
*/
|
|
633
|
+
restoreGrant: string;
|
|
381
634
|
clusterId: string;
|
|
382
635
|
targetNodeName: string;
|
|
383
636
|
restoreId: string;
|
package/ts/requests/cluster.ts
CHANGED
|
@@ -109,6 +109,27 @@ export interface IReq_Any_Cloudly_DisableClusterRelayV1 extends plugins.typedreq
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The relay registrations an administrator asks Cloudly about.
|
|
114
|
+
*
|
|
115
|
+
* Read-only: it answers with what Cloudly accepted, plus whether a verified socket carries each
|
|
116
|
+
* relay at this instant. `clusterIds` narrows the answer to the named clusters; omitting it asks
|
|
117
|
+
* for every cluster whose relay ever registered.
|
|
118
|
+
*/
|
|
119
|
+
export interface IReq_Any_Cloudly_GetClusterRelayRegistrations extends plugins.typedrequestInterfaces.implementsTR<
|
|
120
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
121
|
+
IReq_Any_Cloudly_GetClusterRelayRegistrations
|
|
122
|
+
> {
|
|
123
|
+
method: 'getClusterRelayRegistrations';
|
|
124
|
+
request: {
|
|
125
|
+
identity: userInterfaces.IIdentity;
|
|
126
|
+
clusterIds?: string[];
|
|
127
|
+
};
|
|
128
|
+
response: {
|
|
129
|
+
registrations: clusterInterfaces.IClusterRelayRegistration[];
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
/**
|
|
113
134
|
* The cluster relay tells Cloudly where it listens, over its own authenticated socket.
|
|
114
135
|
*
|