@serve.zone/interfaces 11.2.0 → 11.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/changelog.md +22 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/deployment.d.ts +6 -0
- package/dist_ts/data/deploymentpreflight.d.ts +142 -0
- package/dist_ts/data/deploymentpreflight.js +93 -0
- package/dist_ts/data/image.d.ts +4 -2
- package/dist_ts/data/immutableimage.d.ts +145 -0
- package/dist_ts/data/immutableimage.js +146 -0
- package/dist_ts/data/index.d.ts +2 -0
- package/dist_ts/data/index.js +3 -1
- package/dist_ts/data/registry.d.ts +22 -0
- package/dist_ts/data/service.d.ts +9 -0
- package/dist_ts/data/user.d.ts +11 -0
- package/dist_ts/requests/deployment.d.ts +3 -0
- package/dist_ts/requests/service.d.ts +54 -2
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/deployment.ts +8 -0
- package/ts/data/deploymentpreflight.ts +297 -0
- package/ts/data/image.ts +4 -2
- package/ts/data/immutableimage.ts +373 -0
- package/ts/data/index.ts +2 -0
- package/ts/data/registry.ts +27 -0
- package/ts/data/service.ts +16 -0
- package/ts/data/user.ts +17 -0
- package/ts/requests/deployment.ts +3 -0
- package/ts/requests/service.ts +77 -2
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/** Canonical lower-case OCI sha256 digest. */
|
|
2
|
+
export type TSha256Digest = `sha256:${string}`;
|
|
3
|
+
|
|
4
|
+
export type TImageDeploymentPolicy = 'legacy-tag' | 'immutable-digest';
|
|
5
|
+
|
|
6
|
+
export type TOciManifestMediaType =
|
|
7
|
+
| 'application/vnd.oci.image.index.v1+json'
|
|
8
|
+
| 'application/vnd.oci.image.manifest.v1+json'
|
|
9
|
+
| 'application/vnd.docker.distribution.manifest.list.v2+json'
|
|
10
|
+
| 'application/vnd.docker.distribution.manifest.v2+json';
|
|
11
|
+
|
|
12
|
+
export type TImmutableImageRolloutMode = 'promotion' | 'automatic' | 'rollback';
|
|
13
|
+
|
|
14
|
+
export type TImmutableImageMismatchReason =
|
|
15
|
+
| 'immutable-plan-missing'
|
|
16
|
+
| 'requested-digest-missing'
|
|
17
|
+
| 'requested-digest-malformed'
|
|
18
|
+
| 'registry-digest-inconsistent'
|
|
19
|
+
| 'runtime-capability-missing'
|
|
20
|
+
| 'pull-failed'
|
|
21
|
+
| 'pulled-digest-missing'
|
|
22
|
+
| 'pulled-digest-mismatch'
|
|
23
|
+
| 'service-image-not-digest-pinned'
|
|
24
|
+
| 'service-image-digest-mismatch'
|
|
25
|
+
| 'task-image-not-digest-pinned'
|
|
26
|
+
| 'task-image-digest-mismatch'
|
|
27
|
+
| 'container-inspection-unavailable'
|
|
28
|
+
| 'container-image-id-missing'
|
|
29
|
+
| 'container-repodigest-missing'
|
|
30
|
+
| 'container-repodigest-mismatch'
|
|
31
|
+
| 'runtime-attestation-missing'
|
|
32
|
+
| 'required-replica-missing'
|
|
33
|
+
| 'replica-unhealthy'
|
|
34
|
+
| 'replica-failed'
|
|
35
|
+
| 'stale-rollout-report'
|
|
36
|
+
| 'rollback-target-not-accepted';
|
|
37
|
+
|
|
38
|
+
export type TRuntimeImageVerificationStatus =
|
|
39
|
+
| 'not-required'
|
|
40
|
+
| 'pending'
|
|
41
|
+
| 'verified'
|
|
42
|
+
| 'missing'
|
|
43
|
+
| 'mismatched';
|
|
44
|
+
|
|
45
|
+
export type TImmutableImageRolloutStatus =
|
|
46
|
+
| 'pending'
|
|
47
|
+
| 'deploying'
|
|
48
|
+
| 'verifying'
|
|
49
|
+
| 'succeeded'
|
|
50
|
+
| 'failed'
|
|
51
|
+
| 'mismatched'
|
|
52
|
+
| 'rolling-back'
|
|
53
|
+
| 'rolled-back';
|
|
54
|
+
|
|
55
|
+
export interface ICoreflowRuntimeCapabilities {
|
|
56
|
+
immutableImageDeploymentVersion: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A registry manifest observed by Cloudly. Observation alone does not make the
|
|
61
|
+
* release deployable: acceptedAt is set only by an explicit promotion or a
|
|
62
|
+
* configured automatic promotion.
|
|
63
|
+
*/
|
|
64
|
+
export interface IImageRelease {
|
|
65
|
+
id: string;
|
|
66
|
+
data: {
|
|
67
|
+
serviceId: string;
|
|
68
|
+
imageId: string;
|
|
69
|
+
registryHost: string;
|
|
70
|
+
repository: string;
|
|
71
|
+
/** Digest of the registry root descriptor, preserving an index/list when submitted. */
|
|
72
|
+
registryRootDigest: TSha256Digest;
|
|
73
|
+
digestPinnedImageReference: string;
|
|
74
|
+
manifestMediaType: TOciManifestMediaType;
|
|
75
|
+
observedTags: string[];
|
|
76
|
+
firstObservedAt: number;
|
|
77
|
+
lastObservedAt: number;
|
|
78
|
+
observationCount: number;
|
|
79
|
+
acceptedAt?: number;
|
|
80
|
+
lastPromotedAt?: number;
|
|
81
|
+
promotionCount?: number;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface IImmutableImageTargetScope {
|
|
86
|
+
targetClusterIds: string[];
|
|
87
|
+
targetNodeNames: string[];
|
|
88
|
+
replicasPerNode: number;
|
|
89
|
+
requiredReplicaCount: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Immutable desired state issued by Cloudly and consumed by Coreflow. */
|
|
93
|
+
export interface IImmutableImageDeploymentPlan {
|
|
94
|
+
policy: 'immutable-digest';
|
|
95
|
+
rolloutId: string;
|
|
96
|
+
rolloutGeneration: number;
|
|
97
|
+
releaseId: string;
|
|
98
|
+
registryHost: string;
|
|
99
|
+
repository: string;
|
|
100
|
+
requestedDigest: TSha256Digest;
|
|
101
|
+
digestPinnedImageReference: string;
|
|
102
|
+
manifestMediaType: TOciManifestMediaType;
|
|
103
|
+
createdAt: number;
|
|
104
|
+
mode: TImmutableImageRolloutMode;
|
|
105
|
+
rollbackOfRolloutId?: string;
|
|
106
|
+
targetScope: IImmutableImageTargetScope;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Runtime evidence collected from Docker service, task, container and image state. */
|
|
110
|
+
interface IRuntimeImageEvidenceBase {
|
|
111
|
+
rolloutId?: string;
|
|
112
|
+
rolloutGeneration?: number;
|
|
113
|
+
expectedDigest?: TSha256Digest;
|
|
114
|
+
serviceImageReference?: string;
|
|
115
|
+
taskImageReference?: string;
|
|
116
|
+
taskReportedDigest?: TSha256Digest;
|
|
117
|
+
containerImageId?: string;
|
|
118
|
+
containerConfigImageReference?: string;
|
|
119
|
+
resolvedDigest?: TSha256Digest;
|
|
120
|
+
platformManifestDigest?: TSha256Digest;
|
|
121
|
+
repoDigests?: string[];
|
|
122
|
+
evidenceSource?: 'task-spec' | 'local-container-inspect';
|
|
123
|
+
observedAt: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface IVerifiedRuntimeImageEvidence extends IRuntimeImageEvidenceBase {
|
|
127
|
+
verificationStatus: 'verified';
|
|
128
|
+
rolloutId: string;
|
|
129
|
+
rolloutGeneration: number;
|
|
130
|
+
expectedDigest: TSha256Digest;
|
|
131
|
+
serviceImageReference: string;
|
|
132
|
+
taskImageReference: string;
|
|
133
|
+
taskReportedDigest: TSha256Digest;
|
|
134
|
+
containerImageId: string;
|
|
135
|
+
containerConfigImageReference: string;
|
|
136
|
+
resolvedDigest: TSha256Digest;
|
|
137
|
+
repoDigests: string[];
|
|
138
|
+
evidenceSource: 'local-container-inspect';
|
|
139
|
+
mismatchReason?: never;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface IUnverifiedRuntimeImageEvidence extends IRuntimeImageEvidenceBase {
|
|
143
|
+
verificationStatus: 'missing' | 'mismatched';
|
|
144
|
+
mismatchReason: TImmutableImageMismatchReason;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface IPendingRuntimeImageEvidence extends IRuntimeImageEvidenceBase {
|
|
148
|
+
verificationStatus: 'pending' | 'not-required';
|
|
149
|
+
mismatchReason?: never;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type IRuntimeImageEvidence =
|
|
153
|
+
| IVerifiedRuntimeImageEvidence
|
|
154
|
+
| IUnverifiedRuntimeImageEvidence
|
|
155
|
+
| IPendingRuntimeImageEvidence;
|
|
156
|
+
|
|
157
|
+
/** Mandatory security envelope when reporting an immutable rollout. */
|
|
158
|
+
export interface IImmutableRolloutReportEnvelope {
|
|
159
|
+
rolloutId: string;
|
|
160
|
+
rolloutGeneration: number;
|
|
161
|
+
reporterSessionId: string;
|
|
162
|
+
reportSequence: number;
|
|
163
|
+
reporterCapabilities: ICoreflowRuntimeCapabilities;
|
|
164
|
+
reporterNodeIds: string[];
|
|
165
|
+
reporterNodeNames: string[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface IImageRolloutReporterStatus {
|
|
169
|
+
reporterId: string;
|
|
170
|
+
reporterSessionId: string;
|
|
171
|
+
clusterId: string;
|
|
172
|
+
reporterNodeNames: string[];
|
|
173
|
+
rolloutId: string;
|
|
174
|
+
rolloutGeneration: number;
|
|
175
|
+
reportSequence: number;
|
|
176
|
+
reportedAt: number;
|
|
177
|
+
requiredReplicaCount: number;
|
|
178
|
+
observedReplicaCount: number;
|
|
179
|
+
healthyReplicaCount: number;
|
|
180
|
+
verifiedReplicaCount: number;
|
|
181
|
+
failedReplicaCount: number;
|
|
182
|
+
mismatchReason?: TImmutableImageMismatchReason;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface IImageRolloutStatus {
|
|
186
|
+
rolloutId: string;
|
|
187
|
+
rolloutGeneration: number;
|
|
188
|
+
expectedDigest: TSha256Digest;
|
|
189
|
+
status: TImmutableImageRolloutStatus;
|
|
190
|
+
requiredReplicaCount: number;
|
|
191
|
+
observedReplicaCount: number;
|
|
192
|
+
healthyReplicaCount: number;
|
|
193
|
+
verifiedReplicaCount: number;
|
|
194
|
+
failedReplicaCount: number;
|
|
195
|
+
mismatchReason?: TImmutableImageMismatchReason;
|
|
196
|
+
updatedAt: number;
|
|
197
|
+
reporters: IImageRolloutReporterStatus[];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const sha256DigestRegex = /^sha256:[a-f0-9]{64}$/;
|
|
201
|
+
|
|
202
|
+
export const normalizeSha256Digest = (digest: string): TSha256Digest | undefined => {
|
|
203
|
+
const normalizedDigest = digest.trim().toLowerCase();
|
|
204
|
+
return sha256DigestRegex.test(normalizedDigest)
|
|
205
|
+
? (normalizedDigest as TSha256Digest)
|
|
206
|
+
: undefined;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const isSha256Digest = (digest: string): digest is TSha256Digest =>
|
|
210
|
+
sha256DigestRegex.test(digest);
|
|
211
|
+
|
|
212
|
+
const registryHostRegex = /^(?:localhost|[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)(?::[1-9][0-9]{0,4})?$/;
|
|
213
|
+
const repositoryRegex = /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
|
|
214
|
+
|
|
215
|
+
export const buildDigestPinnedImageReference = (
|
|
216
|
+
registryHost: string,
|
|
217
|
+
repository: string,
|
|
218
|
+
digest: TSha256Digest
|
|
219
|
+
): string => {
|
|
220
|
+
if (!registryHostRegex.test(registryHost) || !repositoryRegex.test(repository)) {
|
|
221
|
+
throw new Error('registry host and repository must be canonical OCI names');
|
|
222
|
+
}
|
|
223
|
+
if (!isSha256Digest(digest)) {
|
|
224
|
+
throw new Error('digest must be a canonical sha256 digest');
|
|
225
|
+
}
|
|
226
|
+
return `${registryHost}/${repository}@${digest}`;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const getDigestFromPinnedImageReference = (
|
|
230
|
+
imageReference: string
|
|
231
|
+
): TSha256Digest | undefined => {
|
|
232
|
+
const separatorIndex = imageReference.lastIndexOf('@');
|
|
233
|
+
if (separatorIndex < 0) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
return normalizeSha256Digest(imageReference.slice(separatorIndex + 1));
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const validateImmutableImageDeploymentPlan = (
|
|
240
|
+
planArg: IImmutableImageDeploymentPlan
|
|
241
|
+
): string[] => {
|
|
242
|
+
const errors: string[] = [];
|
|
243
|
+
if (!Number.isSafeInteger(planArg.rolloutGeneration) || planArg.rolloutGeneration < 1) {
|
|
244
|
+
errors.push('rolloutGeneration must be a positive safe integer');
|
|
245
|
+
}
|
|
246
|
+
if (!Number.isSafeInteger(planArg.createdAt) || planArg.createdAt < 1) {
|
|
247
|
+
errors.push('createdAt must be a positive safe integer');
|
|
248
|
+
}
|
|
249
|
+
let expectedReference: string | undefined;
|
|
250
|
+
try {
|
|
251
|
+
expectedReference = buildDigestPinnedImageReference(
|
|
252
|
+
planArg.registryHost,
|
|
253
|
+
planArg.repository,
|
|
254
|
+
planArg.requestedDigest
|
|
255
|
+
);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
errors.push((error as Error).message);
|
|
258
|
+
}
|
|
259
|
+
if (expectedReference && planArg.digestPinnedImageReference !== expectedReference) {
|
|
260
|
+
errors.push('digestPinnedImageReference does not match registry, repository and digest');
|
|
261
|
+
}
|
|
262
|
+
const scope = planArg.targetScope;
|
|
263
|
+
if (!Number.isSafeInteger(scope.replicasPerNode) || scope.replicasPerNode < 1) {
|
|
264
|
+
errors.push('replicasPerNode must be a positive safe integer');
|
|
265
|
+
}
|
|
266
|
+
if (scope.targetClusterIds.length < 1 || scope.targetNodeNames.length < 1) {
|
|
267
|
+
errors.push('immutable rollout target scope must contain clusters and nodes');
|
|
268
|
+
}
|
|
269
|
+
if (new Set(scope.targetClusterIds).size !== scope.targetClusterIds.length
|
|
270
|
+
|| new Set(scope.targetNodeNames).size !== scope.targetNodeNames.length) {
|
|
271
|
+
errors.push('immutable rollout target scope must not contain duplicates');
|
|
272
|
+
}
|
|
273
|
+
if (scope.requiredReplicaCount !== scope.targetNodeNames.length * scope.replicasPerNode) {
|
|
274
|
+
errors.push('requiredReplicaCount does not match target nodes and replicasPerNode');
|
|
275
|
+
}
|
|
276
|
+
if (planArg.mode === 'rollback' && !planArg.rollbackOfRolloutId) {
|
|
277
|
+
errors.push('rollback rollout must identify the rollout it replaces');
|
|
278
|
+
}
|
|
279
|
+
if (planArg.mode !== 'rollback' && planArg.rollbackOfRolloutId) {
|
|
280
|
+
errors.push('only rollback rollouts may set rollbackOfRolloutId');
|
|
281
|
+
}
|
|
282
|
+
return errors;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export const validateImageRolloutStatus = (statusArg: IImageRolloutStatus): string[] => {
|
|
286
|
+
const errors: string[] = [];
|
|
287
|
+
const counts = [
|
|
288
|
+
statusArg.requiredReplicaCount,
|
|
289
|
+
statusArg.observedReplicaCount,
|
|
290
|
+
statusArg.healthyReplicaCount,
|
|
291
|
+
statusArg.verifiedReplicaCount,
|
|
292
|
+
statusArg.failedReplicaCount,
|
|
293
|
+
];
|
|
294
|
+
if (counts.some((countArg) => !Number.isSafeInteger(countArg) || countArg < 0)) {
|
|
295
|
+
errors.push('rollout replica counts must be non-negative safe integers');
|
|
296
|
+
}
|
|
297
|
+
const reporterIds = statusArg.reporters.map((reporterArg) => reporterArg.reporterId);
|
|
298
|
+
const reporterSessionIds = statusArg.reporters.map((reporterArg) => reporterArg.reporterSessionId);
|
|
299
|
+
const reporterNodeNames = statusArg.reporters.flatMap(
|
|
300
|
+
(reporterArg) => reporterArg.reporterNodeNames
|
|
301
|
+
);
|
|
302
|
+
if (new Set(reporterIds).size !== reporterIds.length
|
|
303
|
+
|| new Set(reporterSessionIds).size !== reporterSessionIds.length) {
|
|
304
|
+
errors.push('rollout status must contain one latest report per reporter and session');
|
|
305
|
+
}
|
|
306
|
+
for (const reporter of statusArg.reporters) {
|
|
307
|
+
const reporterCounts = [
|
|
308
|
+
reporter.requiredReplicaCount,
|
|
309
|
+
reporter.observedReplicaCount,
|
|
310
|
+
reporter.healthyReplicaCount,
|
|
311
|
+
reporter.verifiedReplicaCount,
|
|
312
|
+
reporter.failedReplicaCount,
|
|
313
|
+
];
|
|
314
|
+
if (reporterCounts.some((countArg) => !Number.isSafeInteger(countArg) || countArg < 0)) {
|
|
315
|
+
errors.push('reporter replica counts must be non-negative safe integers');
|
|
316
|
+
}
|
|
317
|
+
if (reporter.rolloutId !== statusArg.rolloutId
|
|
318
|
+
|| reporter.rolloutGeneration !== statusArg.rolloutGeneration) {
|
|
319
|
+
errors.push('reporter rollout identity does not match aggregate rollout identity');
|
|
320
|
+
}
|
|
321
|
+
if (
|
|
322
|
+
reporter.observedReplicaCount > reporter.requiredReplicaCount
|
|
323
|
+
|| reporter.healthyReplicaCount > reporter.observedReplicaCount
|
|
324
|
+
|| reporter.verifiedReplicaCount > reporter.observedReplicaCount
|
|
325
|
+
) {
|
|
326
|
+
errors.push('reporter replica counts are contradictory');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (statusArg.status === 'succeeded' || statusArg.status === 'rolled-back') {
|
|
330
|
+
if (
|
|
331
|
+
statusArg.requiredReplicaCount < 1
|
|
332
|
+
|| statusArg.observedReplicaCount !== statusArg.requiredReplicaCount
|
|
333
|
+
|| statusArg.healthyReplicaCount !== statusArg.requiredReplicaCount
|
|
334
|
+
|| statusArg.verifiedReplicaCount !== statusArg.requiredReplicaCount
|
|
335
|
+
|| statusArg.failedReplicaCount !== 0
|
|
336
|
+
) {
|
|
337
|
+
errors.push('successful rollout status requires every replica to be observed, healthy and verified');
|
|
338
|
+
}
|
|
339
|
+
if (statusArg.mismatchReason || statusArg.reporters.length < 1) {
|
|
340
|
+
errors.push('successful rollout status requires reporters without mismatch reasons');
|
|
341
|
+
}
|
|
342
|
+
if (new Set(reporterNodeNames).size !== reporterNodeNames.length) {
|
|
343
|
+
errors.push('successful rollout status must not count a node in multiple reporters');
|
|
344
|
+
}
|
|
345
|
+
if (statusArg.reporters.some((reporterArg) => (
|
|
346
|
+
reporterArg.requiredReplicaCount < 1
|
|
347
|
+
|| reporterArg.observedReplicaCount !== reporterArg.requiredReplicaCount
|
|
348
|
+
|| reporterArg.healthyReplicaCount !== reporterArg.requiredReplicaCount
|
|
349
|
+
|| reporterArg.verifiedReplicaCount !== reporterArg.requiredReplicaCount
|
|
350
|
+
|| reporterArg.failedReplicaCount !== 0
|
|
351
|
+
|| Boolean(reporterArg.mismatchReason)
|
|
352
|
+
))) {
|
|
353
|
+
errors.push('successful rollout requires every reporter replica to be healthy and verified');
|
|
354
|
+
}
|
|
355
|
+
const reporterTotals = statusArg.reporters.reduce((totalsArg, reporterArg) => ({
|
|
356
|
+
required: totalsArg.required + reporterArg.requiredReplicaCount,
|
|
357
|
+
observed: totalsArg.observed + reporterArg.observedReplicaCount,
|
|
358
|
+
healthy: totalsArg.healthy + reporterArg.healthyReplicaCount,
|
|
359
|
+
verified: totalsArg.verified + reporterArg.verifiedReplicaCount,
|
|
360
|
+
failed: totalsArg.failed + reporterArg.failedReplicaCount,
|
|
361
|
+
}), { required: 0, observed: 0, healthy: 0, verified: 0, failed: 0 });
|
|
362
|
+
if (
|
|
363
|
+
reporterTotals.required !== statusArg.requiredReplicaCount
|
|
364
|
+
|| reporterTotals.observed !== statusArg.observedReplicaCount
|
|
365
|
+
|| reporterTotals.healthy !== statusArg.healthyReplicaCount
|
|
366
|
+
|| reporterTotals.verified !== statusArg.verifiedReplicaCount
|
|
367
|
+
|| reporterTotals.failed !== statusArg.failedReplicaCount
|
|
368
|
+
) {
|
|
369
|
+
errors.push('successful rollout aggregate must exactly match successful reporter evidence');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return errors;
|
|
373
|
+
};
|
package/ts/data/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './cloudlyconfig.js';
|
|
|
2
2
|
export * from './cluster.js';
|
|
3
3
|
export * from './config.js';
|
|
4
4
|
export * from './deployment.js';
|
|
5
|
+
export * from './deploymentpreflight.js';
|
|
5
6
|
export * from './migration.js';
|
|
6
7
|
export * from './dns.js';
|
|
7
8
|
export * from './docker.js';
|
|
@@ -11,6 +12,7 @@ export * from './externalregistry.js';
|
|
|
11
12
|
export * from './gateway.js';
|
|
12
13
|
export * from './hostedapp.js';
|
|
13
14
|
export * from './image.js';
|
|
15
|
+
export * from './immutableimage.js';
|
|
14
16
|
export * from './isolatedrestore.js';
|
|
15
17
|
export * from './isolatedrestore.golden.js';
|
|
16
18
|
export * from './mail.js';
|
package/ts/data/registry.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export type TRegistryProtocol = 'oci';
|
|
2
2
|
|
|
3
|
+
import type {
|
|
4
|
+
TOciManifestMediaType,
|
|
5
|
+
TSha256Digest,
|
|
6
|
+
} from './immutableimage.js';
|
|
7
|
+
|
|
3
8
|
export interface IRegistryTarget {
|
|
4
9
|
protocol: TRegistryProtocol;
|
|
5
10
|
registryHost: string;
|
|
@@ -11,6 +16,8 @@ export interface IRegistryTarget {
|
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export interface IRegistryPushEvent {
|
|
19
|
+
eventId?: string;
|
|
20
|
+
releaseId?: string;
|
|
14
21
|
protocol: TRegistryProtocol;
|
|
15
22
|
registryHost: string;
|
|
16
23
|
repository: string;
|
|
@@ -18,8 +25,28 @@ export interface IRegistryPushEvent {
|
|
|
18
25
|
digest: string;
|
|
19
26
|
imageUrl: string;
|
|
20
27
|
pushedAt: number;
|
|
28
|
+
observedAt?: number;
|
|
21
29
|
serviceId?: string;
|
|
22
30
|
imageId?: string;
|
|
23
31
|
actorUserId?: string;
|
|
24
32
|
manifestMediaType?: string;
|
|
25
33
|
}
|
|
34
|
+
|
|
35
|
+
/** Fully verified registry observation used by immutable release processing. */
|
|
36
|
+
export interface IImmutableRegistryPushEvent {
|
|
37
|
+
deploymentPolicy: 'immutable-digest';
|
|
38
|
+
eventId: string;
|
|
39
|
+
releaseId: string;
|
|
40
|
+
protocol: TRegistryProtocol;
|
|
41
|
+
registryHost: string;
|
|
42
|
+
repository: string;
|
|
43
|
+
tag: string;
|
|
44
|
+
registryRootDigest: TSha256Digest;
|
|
45
|
+
digestPinnedImageReference: string;
|
|
46
|
+
imageUrl: string;
|
|
47
|
+
observedAt: number;
|
|
48
|
+
serviceId: string;
|
|
49
|
+
imageId: string;
|
|
50
|
+
manifestMediaType: TOciManifestMediaType;
|
|
51
|
+
actorUserId?: string;
|
|
52
|
+
}
|
package/ts/data/service.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { IRegistryTarget } from './registry.js';
|
|
|
3
3
|
import type { IAppStorePublishedPort } from '../appstore/index.js';
|
|
4
4
|
import type { IHostedAppLifecycleState } from './hostedapp.js';
|
|
5
5
|
import type { IServiceMailConfig } from './mail.js';
|
|
6
|
+
import type {
|
|
7
|
+
IImageRolloutStatus,
|
|
8
|
+
IImmutableImageDeploymentPlan,
|
|
9
|
+
} from './immutableimage.js';
|
|
6
10
|
import type {
|
|
7
11
|
IServiceDomainRoute,
|
|
8
12
|
IServicePublicPortMapping,
|
|
@@ -70,6 +74,12 @@ export interface IService {
|
|
|
70
74
|
imageVersion: string;
|
|
71
75
|
registryTarget?: IRegistryTarget;
|
|
72
76
|
deployOnPush?: boolean;
|
|
77
|
+
/** Fail closed unless Cloudly supplies and Coreflow verifies a digest-pinned rollout. */
|
|
78
|
+
immutableImageRequired?: boolean;
|
|
79
|
+
/** Server-managed immutable desired state. Use the promotion/rollback APIs to change it. */
|
|
80
|
+
imageDeployment?: IImmutableImageDeploymentPlan;
|
|
81
|
+
/** Server-managed aggregate of task-derived runtime evidence. */
|
|
82
|
+
imageRolloutStatus?: IImageRolloutStatus;
|
|
73
83
|
|
|
74
84
|
/**
|
|
75
85
|
* When true the service tolerates overlapping deployments during
|
|
@@ -151,3 +161,9 @@ export interface IService {
|
|
|
151
161
|
deploymentIds: string[];
|
|
152
162
|
};
|
|
153
163
|
}
|
|
164
|
+
|
|
165
|
+
/** Caller-writable service data. Immutable policy and rollout fields are server-managed. */
|
|
166
|
+
export type TServiceWritableData = Omit<
|
|
167
|
+
IService['data'],
|
|
168
|
+
'immutableImageRequired' | 'imageDeployment' | 'imageRolloutStatus'
|
|
169
|
+
>;
|
package/ts/data/user.ts
CHANGED
|
@@ -4,6 +4,22 @@ export interface IToken {
|
|
|
4
4
|
assignedRoles: string[];
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export type TServiceDeploymentCapability =
|
|
8
|
+
| 'deployment:preflight'
|
|
9
|
+
| 'deployment:create-greenfield'
|
|
10
|
+
| 'deployment:promote-route'
|
|
11
|
+
| 'deployment:rollback-route';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Server-persisted authority for one logical service. These grants are never
|
|
15
|
+
* accepted from the identity payload supplied by a caller; Cloudly reloads
|
|
16
|
+
* the owning user from the verified JWT before evaluating them.
|
|
17
|
+
*/
|
|
18
|
+
export interface IServiceDeploymentGrant {
|
|
19
|
+
serviceId: string;
|
|
20
|
+
capabilities: TServiceDeploymentCapability[];
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
export interface IIdentityCredential {
|
|
8
24
|
jwt: string;
|
|
9
25
|
}
|
|
@@ -28,5 +44,6 @@ export interface IUser {
|
|
|
28
44
|
username?: string;
|
|
29
45
|
password?: string;
|
|
30
46
|
tokens?: IToken[];
|
|
47
|
+
serviceDeploymentGrants?: IServiceDeploymentGrant[];
|
|
31
48
|
}
|
|
32
49
|
}
|
|
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import type { IDeployment } from '../data/deployment.js';
|
|
3
3
|
import type { IService } from '../data/service.js';
|
|
4
4
|
import type { IIdentity } from '../data/user.js';
|
|
5
|
+
import type { IImmutableRolloutReportEnvelope } from '../data/immutableimage.js';
|
|
5
6
|
|
|
6
7
|
export interface IDeploymentWorkspaceFileEntry {
|
|
7
8
|
type: 'file' | 'directory';
|
|
@@ -452,6 +453,8 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
452
453
|
* When absent, the snapshot replaces all persisted deployments of the service.
|
|
453
454
|
*/
|
|
454
455
|
reporterNodeNames?: string[];
|
|
456
|
+
/** Mandatory when the referenced service requires immutable deployment. */
|
|
457
|
+
immutableReport?: IImmutableRolloutReportEnvelope;
|
|
455
458
|
reportedAt: number;
|
|
456
459
|
};
|
|
457
460
|
response: Record<string, never>;
|
package/ts/requests/service.ts
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import type { IService } from '../data/service.js';
|
|
3
|
+
import type { TServiceWritableData } from '../data/service.js';
|
|
3
4
|
import type { IRegistryTarget } from '../data/registry.js';
|
|
4
5
|
import type { IIdentity } from '../data/user.js';
|
|
5
6
|
import type { IServiceRessources } from '../data/docker.js';
|
|
7
|
+
import type { IImageRelease } from '../data/immutableimage.js';
|
|
8
|
+
import type {
|
|
9
|
+
IDeploymentPreflightReport,
|
|
10
|
+
IDeploymentPreflightRequestData,
|
|
11
|
+
} from '../data/deploymentpreflight.js';
|
|
12
|
+
|
|
13
|
+
export interface IRequest_Any_Cloudly_GetDeploymentPreflight
|
|
14
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
15
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
16
|
+
IRequest_Any_Cloudly_GetDeploymentPreflight
|
|
17
|
+
> {
|
|
18
|
+
method: 'getDeploymentPreflight';
|
|
19
|
+
request: IDeploymentPreflightRequestData & {
|
|
20
|
+
identity: IIdentity;
|
|
21
|
+
};
|
|
22
|
+
response: {
|
|
23
|
+
report: IDeploymentPreflightReport;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
6
26
|
|
|
7
27
|
export interface IRequest_Any_Cloudly_GetServiceById
|
|
8
28
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
@@ -49,6 +69,61 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
49
69
|
};
|
|
50
70
|
}
|
|
51
71
|
|
|
72
|
+
export interface IRequest_Any_Cloudly_GetServiceImageReleases
|
|
73
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
74
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
75
|
+
IRequest_Any_Cloudly_GetServiceImageReleases
|
|
76
|
+
> {
|
|
77
|
+
method: 'getServiceImageReleases';
|
|
78
|
+
request: {
|
|
79
|
+
identity: IIdentity;
|
|
80
|
+
serviceId: string;
|
|
81
|
+
};
|
|
82
|
+
response: {
|
|
83
|
+
releases: IImageRelease[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface IRequest_Any_Cloudly_PromoteServiceImageRelease
|
|
88
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
89
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
90
|
+
IRequest_Any_Cloudly_PromoteServiceImageRelease
|
|
91
|
+
> {
|
|
92
|
+
method: 'promoteServiceImageRelease';
|
|
93
|
+
request: {
|
|
94
|
+
identity: IIdentity;
|
|
95
|
+
serviceId: string;
|
|
96
|
+
releaseId: string;
|
|
97
|
+
idempotencyKey: string;
|
|
98
|
+
expectedRolloutGeneration: number;
|
|
99
|
+
expectedCurrentRolloutId?: string;
|
|
100
|
+
/** Explicitly opts the service into fail-closed immutable deployment. */
|
|
101
|
+
enableImmutableImage: true;
|
|
102
|
+
};
|
|
103
|
+
response: {
|
|
104
|
+
service: IService;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface IRequest_Any_Cloudly_RollbackServiceImageRelease
|
|
109
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
110
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
111
|
+
IRequest_Any_Cloudly_RollbackServiceImageRelease
|
|
112
|
+
> {
|
|
113
|
+
method: 'rollbackServiceImageRelease';
|
|
114
|
+
request: {
|
|
115
|
+
identity: IIdentity;
|
|
116
|
+
serviceId: string;
|
|
117
|
+
releaseId: string;
|
|
118
|
+
idempotencyKey: string;
|
|
119
|
+
expectedCurrentRolloutId: string;
|
|
120
|
+
expectedRolloutGeneration: number;
|
|
121
|
+
};
|
|
122
|
+
response: {
|
|
123
|
+
service: IService;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
52
127
|
export interface IRequest_Any_Cloudly_CreateService
|
|
53
128
|
extends plugins.typedrequestInterfaces.implementsTR<
|
|
54
129
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
@@ -57,7 +132,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
57
132
|
method: 'createService';
|
|
58
133
|
request: {
|
|
59
134
|
identity: IIdentity;
|
|
60
|
-
serviceData:
|
|
135
|
+
serviceData: TServiceWritableData;
|
|
61
136
|
};
|
|
62
137
|
response: {
|
|
63
138
|
service: IService;
|
|
@@ -73,7 +148,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
73
148
|
request: {
|
|
74
149
|
identity: IIdentity;
|
|
75
150
|
serviceId: string;
|
|
76
|
-
serviceData:
|
|
151
|
+
serviceData: TServiceWritableData;
|
|
77
152
|
};
|
|
78
153
|
response: {
|
|
79
154
|
service: IService;
|