@serve.zone/interfaces 15.0.0 → 16.0.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 +9 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/deploymentadoption.d.ts +95 -0
- package/dist_ts/data/deploymentadoption.js +303 -0
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/user.d.ts +1 -1
- package/dist_ts/data/user.js +6 -1
- package/dist_ts/requests/service.d.ts +20 -0
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/deploymentadoption.ts +445 -0
- package/ts/data/index.ts +1 -0
- package/ts/data/user.ts +6 -0
- package/ts/requests/service.ts +35 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-07-20 - 16.0.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- add attested existing-service adoption contracts (deployment)
|
|
8
|
+
- Add a mutation-free adoption preflight and a digest-fenced, idempotent adoption request.
|
|
9
|
+
- Require explicit service-scoped deployment:adopt-existing authority.
|
|
10
|
+
- Bind registry, release, target-scope, and healthy runtime digest evidence before switching a mutable service to immutable rollout state.
|
|
11
|
+
|
|
3
12
|
## 2026-07-20 - 15.0.0
|
|
4
13
|
|
|
5
14
|
### Breaking Changes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '
|
|
6
|
+
version: '16.0.0',
|
|
7
7
|
description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { IDeploymentPreflightRuntimeTask, IOciPlatformDescriptorSummary } from './deploymentpreflight.js';
|
|
2
|
+
import type { IImmutableImageDeploymentPlan, IImmutableImageTargetScope, TOciManifestMediaType, TSha256Digest } from './immutableimage.js';
|
|
3
|
+
import type { IRegistryTarget } from './registry.js';
|
|
4
|
+
export type TServiceDeploymentAdoptionDecision = 'GO' | 'NO-GO';
|
|
5
|
+
/**
|
|
6
|
+
* Canonical sha256 hash of the complete adoption preflight snapshot. It is a
|
|
7
|
+
* state/TOCTOU fence and must never be substituted with the OCI image digest.
|
|
8
|
+
*/
|
|
9
|
+
export type TServiceDeploymentAdoptionDigest = TSha256Digest & {
|
|
10
|
+
readonly __serviceDeploymentAdoptionDigest: unique symbol;
|
|
11
|
+
};
|
|
12
|
+
export type TServiceDeploymentAdoptionBlockerCode = 'INVALID_REQUEST' | 'AUTHORIZATION_MISSING' | 'SERVICE_NOT_FOUND' | 'SERVICE_OWNERSHIP_MISMATCH' | 'SERVICE_ALREADY_IMMUTABLE' | 'SERVICE_STATE_CONFLICT' | 'REGISTRY_TARGET_MISSING' | 'REGISTRY_REFERENCE_INVALID' | 'REGISTRY_DIGEST_MISSING' | 'OCI_INDEX_REQUIRED' | 'OCI_PLATFORM_MISSING' | 'RELEASE_NOT_FOUND' | 'RELEASE_TAG_AMBIGUOUS' | 'TARGET_SCOPE_UNAVAILABLE' | 'RUNTIME_ATTESTATION_INCOMPLETE' | 'RUNTIME_DIGEST_MISMATCH';
|
|
13
|
+
export interface IServiceDeploymentAdoptionBlocker {
|
|
14
|
+
code: TServiceDeploymentAdoptionBlockerCode;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
export interface IServiceDeploymentAdoptionPreflightInput {
|
|
18
|
+
requestId: string;
|
|
19
|
+
organizationId: string;
|
|
20
|
+
serviceId: string;
|
|
21
|
+
}
|
|
22
|
+
export interface IServiceDeploymentAdoptionInput extends IServiceDeploymentAdoptionPreflightInput {
|
|
23
|
+
idempotencyKey: string;
|
|
24
|
+
expectedAdoptionDigest: TServiceDeploymentAdoptionDigest;
|
|
25
|
+
}
|
|
26
|
+
interface IServiceDeploymentAdoptionReportBase {
|
|
27
|
+
schemaVersion: 1;
|
|
28
|
+
mutationPerformed: false;
|
|
29
|
+
requestId: string;
|
|
30
|
+
generatedAt: number;
|
|
31
|
+
runtimeAttestation: {
|
|
32
|
+
requiredReplicaCount: number;
|
|
33
|
+
observedReplicaCount: number;
|
|
34
|
+
healthyReplicaCount: number;
|
|
35
|
+
observedDigestReplicaCount: number;
|
|
36
|
+
observedDigestConsistent: boolean;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
interface IServiceDeploymentAdoptionServiceSnapshot {
|
|
40
|
+
organizationId: string;
|
|
41
|
+
serviceId: string;
|
|
42
|
+
serviceName: string;
|
|
43
|
+
imageId: string;
|
|
44
|
+
deployOnPush: boolean;
|
|
45
|
+
immutableImageRequired: boolean;
|
|
46
|
+
configuredReference: string;
|
|
47
|
+
registryTarget: IRegistryTarget;
|
|
48
|
+
}
|
|
49
|
+
interface IServiceDeploymentAdoptionRegistrySnapshot {
|
|
50
|
+
tag?: string;
|
|
51
|
+
digest?: TSha256Digest;
|
|
52
|
+
mediaType?: TOciManifestMediaType;
|
|
53
|
+
platforms: IOciPlatformDescriptorSummary[];
|
|
54
|
+
releaseId?: string;
|
|
55
|
+
releaseTag?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface IServiceDeploymentAdoptionGoReport extends IServiceDeploymentAdoptionReportBase {
|
|
58
|
+
decision: 'GO';
|
|
59
|
+
service: IServiceDeploymentAdoptionServiceSnapshot & {
|
|
60
|
+
deployOnPush: true;
|
|
61
|
+
immutableImageRequired: false;
|
|
62
|
+
};
|
|
63
|
+
registry: IServiceDeploymentAdoptionRegistrySnapshot & {
|
|
64
|
+
tag: string;
|
|
65
|
+
digest: TSha256Digest;
|
|
66
|
+
mediaType: 'application/vnd.oci.image.index.v1+json' | 'application/vnd.docker.distribution.manifest.list.v2+json';
|
|
67
|
+
platforms: [IOciPlatformDescriptorSummary, ...IOciPlatformDescriptorSummary[]];
|
|
68
|
+
releaseId: string;
|
|
69
|
+
releaseTag: string;
|
|
70
|
+
};
|
|
71
|
+
runtimeTasks: [IDeploymentPreflightRuntimeTask, ...IDeploymentPreflightRuntimeTask[]];
|
|
72
|
+
targetScope: IImmutableImageTargetScope;
|
|
73
|
+
adoptionDigest: TServiceDeploymentAdoptionDigest;
|
|
74
|
+
blockers: [];
|
|
75
|
+
}
|
|
76
|
+
export interface IServiceDeploymentAdoptionNoGoReport extends IServiceDeploymentAdoptionReportBase {
|
|
77
|
+
decision: 'NO-GO';
|
|
78
|
+
service?: IServiceDeploymentAdoptionServiceSnapshot;
|
|
79
|
+
registry: IServiceDeploymentAdoptionRegistrySnapshot;
|
|
80
|
+
runtimeTasks: IDeploymentPreflightRuntimeTask[];
|
|
81
|
+
targetScope?: IImmutableImageTargetScope;
|
|
82
|
+
adoptionDigest?: never;
|
|
83
|
+
blockers: [IServiceDeploymentAdoptionBlocker, ...IServiceDeploymentAdoptionBlocker[]];
|
|
84
|
+
}
|
|
85
|
+
export type IServiceDeploymentAdoptionReport = IServiceDeploymentAdoptionGoReport | IServiceDeploymentAdoptionNoGoReport;
|
|
86
|
+
export interface IServiceDeploymentAdoptionResult {
|
|
87
|
+
adoptionId: string;
|
|
88
|
+
adoptionDigest: TServiceDeploymentAdoptionDigest;
|
|
89
|
+
idempotentReplay: boolean;
|
|
90
|
+
plan: IImmutableImageDeploymentPlan;
|
|
91
|
+
}
|
|
92
|
+
export declare const validateServiceDeploymentAdoptionPreflightInput: (inputArg: unknown) => string[];
|
|
93
|
+
export declare const validateServiceDeploymentAdoptionInput: (inputArg: unknown) => string[];
|
|
94
|
+
export declare const validateServiceDeploymentAdoptionReport: (reportArg: unknown) => string[];
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { normalizeImmutableReleaseTag, normalizeSha256Digest } from './immutableimage.js';
|
|
2
|
+
const boundaryIdentifierRegex = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
|
|
3
|
+
const indexMediaTypes = new Set([
|
|
4
|
+
'application/vnd.oci.image.index.v1+json',
|
|
5
|
+
'application/vnd.docker.distribution.manifest.list.v2+json',
|
|
6
|
+
]);
|
|
7
|
+
const adoptionBlockerCodes = new Set([
|
|
8
|
+
'INVALID_REQUEST',
|
|
9
|
+
'AUTHORIZATION_MISSING',
|
|
10
|
+
'SERVICE_NOT_FOUND',
|
|
11
|
+
'SERVICE_OWNERSHIP_MISMATCH',
|
|
12
|
+
'SERVICE_ALREADY_IMMUTABLE',
|
|
13
|
+
'SERVICE_STATE_CONFLICT',
|
|
14
|
+
'REGISTRY_TARGET_MISSING',
|
|
15
|
+
'REGISTRY_REFERENCE_INVALID',
|
|
16
|
+
'REGISTRY_DIGEST_MISSING',
|
|
17
|
+
'OCI_INDEX_REQUIRED',
|
|
18
|
+
'OCI_PLATFORM_MISSING',
|
|
19
|
+
'RELEASE_NOT_FOUND',
|
|
20
|
+
'RELEASE_TAG_AMBIGUOUS',
|
|
21
|
+
'TARGET_SCOPE_UNAVAILABLE',
|
|
22
|
+
'RUNTIME_ATTESTATION_INCOMPLETE',
|
|
23
|
+
'RUNTIME_DIGEST_MISMATCH',
|
|
24
|
+
]);
|
|
25
|
+
const isRecord = (valueArg) => Boolean(valueArg) && typeof valueArg === 'object' && !Array.isArray(valueArg);
|
|
26
|
+
const isBoundedIdentifier = (valueArg) => typeof valueArg === 'string' && boundaryIdentifierRegex.test(valueArg);
|
|
27
|
+
const isCanonicalDigest = (valueArg) => Boolean(normalizeSha256Digest(valueArg)) && normalizeSha256Digest(valueArg) === valueArg;
|
|
28
|
+
const isNonNegativeInteger = (valueArg) => Number.isSafeInteger(valueArg) && valueArg >= 0;
|
|
29
|
+
export const validateServiceDeploymentAdoptionPreflightInput = (inputArg) => {
|
|
30
|
+
if (!isRecord(inputArg))
|
|
31
|
+
return ['adoption preflight input must be an object'];
|
|
32
|
+
const errors = [];
|
|
33
|
+
if (!isBoundedIdentifier(inputArg.requestId)) {
|
|
34
|
+
errors.push('requestId must be a bounded canonical identifier');
|
|
35
|
+
}
|
|
36
|
+
for (const key of ['organizationId', 'serviceId']) {
|
|
37
|
+
if (!isBoundedIdentifier(inputArg[key])) {
|
|
38
|
+
errors.push(`${key} must be a bounded canonical identifier`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return errors;
|
|
42
|
+
};
|
|
43
|
+
export const validateServiceDeploymentAdoptionInput = (inputArg) => {
|
|
44
|
+
const errors = validateServiceDeploymentAdoptionPreflightInput(inputArg);
|
|
45
|
+
if (!isRecord(inputArg))
|
|
46
|
+
return errors;
|
|
47
|
+
if (!isBoundedIdentifier(inputArg.idempotencyKey)) {
|
|
48
|
+
errors.push('idempotencyKey must be a bounded canonical identifier');
|
|
49
|
+
}
|
|
50
|
+
if (!isCanonicalDigest(inputArg.expectedAdoptionDigest)) {
|
|
51
|
+
errors.push('expectedAdoptionDigest must be a canonical sha256 digest');
|
|
52
|
+
}
|
|
53
|
+
return errors;
|
|
54
|
+
};
|
|
55
|
+
export const validateServiceDeploymentAdoptionReport = (reportArg) => {
|
|
56
|
+
if (!isRecord(reportArg))
|
|
57
|
+
return ['adoption report must be an object'];
|
|
58
|
+
const errors = [];
|
|
59
|
+
if (reportArg.schemaVersion !== 1)
|
|
60
|
+
errors.push('schemaVersion must be 1');
|
|
61
|
+
if (reportArg.mutationPerformed !== false) {
|
|
62
|
+
errors.push('adoption preflight must be mutation-free');
|
|
63
|
+
}
|
|
64
|
+
if (!isBoundedIdentifier(reportArg.requestId)) {
|
|
65
|
+
errors.push('requestId must be a bounded canonical identifier');
|
|
66
|
+
}
|
|
67
|
+
if (!isNonNegativeInteger(reportArg.generatedAt)) {
|
|
68
|
+
errors.push('generatedAt must be a non-negative integer');
|
|
69
|
+
}
|
|
70
|
+
if (reportArg.decision !== 'GO' && reportArg.decision !== 'NO-GO') {
|
|
71
|
+
errors.push('decision must be GO or NO-GO');
|
|
72
|
+
}
|
|
73
|
+
const attestation = reportArg.runtimeAttestation;
|
|
74
|
+
if (!isRecord(attestation)) {
|
|
75
|
+
errors.push('runtimeAttestation must be an object');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
for (const key of [
|
|
79
|
+
'requiredReplicaCount',
|
|
80
|
+
'observedReplicaCount',
|
|
81
|
+
'healthyReplicaCount',
|
|
82
|
+
'observedDigestReplicaCount',
|
|
83
|
+
]) {
|
|
84
|
+
if (!isNonNegativeInteger(attestation[key])) {
|
|
85
|
+
errors.push(`runtimeAttestation.${key} must be a non-negative integer`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (typeof attestation.observedDigestConsistent !== 'boolean') {
|
|
89
|
+
errors.push('runtimeAttestation.observedDigestConsistent must be boolean');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const blockers = reportArg.blockers;
|
|
93
|
+
if (!Array.isArray(blockers)) {
|
|
94
|
+
errors.push('blockers must be an array');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
for (const blocker of blockers) {
|
|
98
|
+
if (!isRecord(blocker)
|
|
99
|
+
|| !adoptionBlockerCodes.has(blocker.code)
|
|
100
|
+
|| typeof blocker.message !== 'string'
|
|
101
|
+
|| !blocker.message.trim()) {
|
|
102
|
+
errors.push('each blocker must contain a supported code and non-empty message');
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (reportArg.decision === 'NO-GO') {
|
|
108
|
+
if (Array.isArray(blockers) && blockers.length === 0) {
|
|
109
|
+
errors.push('a NO-GO report must contain at least one blocker');
|
|
110
|
+
}
|
|
111
|
+
if (reportArg.adoptionDigest !== undefined) {
|
|
112
|
+
errors.push('a NO-GO report must not contain an adoption digest');
|
|
113
|
+
}
|
|
114
|
+
return errors;
|
|
115
|
+
}
|
|
116
|
+
if (reportArg.decision !== 'GO')
|
|
117
|
+
return errors;
|
|
118
|
+
if (Array.isArray(blockers) && blockers.length !== 0) {
|
|
119
|
+
errors.push('a GO report must not contain blockers');
|
|
120
|
+
}
|
|
121
|
+
if (!isCanonicalDigest(reportArg.adoptionDigest)) {
|
|
122
|
+
errors.push('a GO report requires a canonical adoption digest');
|
|
123
|
+
}
|
|
124
|
+
const service = reportArg.service;
|
|
125
|
+
const registry = reportArg.registry;
|
|
126
|
+
const targetScope = reportArg.targetScope;
|
|
127
|
+
const runtimeTasks = reportArg.runtimeTasks;
|
|
128
|
+
if (!isRecord(service)) {
|
|
129
|
+
errors.push('a GO report requires a service snapshot');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
for (const key of ['organizationId', 'serviceId', 'serviceName', 'imageId']) {
|
|
133
|
+
if (!isBoundedIdentifier(service[key]))
|
|
134
|
+
errors.push(`service.${key} is invalid`);
|
|
135
|
+
}
|
|
136
|
+
if (service.deployOnPush !== true || service.immutableImageRequired !== false) {
|
|
137
|
+
errors.push('a GO report requires the legacy mutable service state');
|
|
138
|
+
}
|
|
139
|
+
if (typeof service.configuredReference !== 'string' || !service.configuredReference.trim()) {
|
|
140
|
+
errors.push('service.configuredReference must be non-empty');
|
|
141
|
+
}
|
|
142
|
+
if (!isRecord(service.registryTarget)) {
|
|
143
|
+
errors.push('service.registryTarget must be present');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
let registryDigest;
|
|
147
|
+
if (!isRecord(registry)) {
|
|
148
|
+
errors.push('a GO report requires a registry snapshot');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
if (!normalizeImmutableReleaseTag(registry.tag) || registry.tag !== registry.releaseTag) {
|
|
152
|
+
errors.push('registry tag and releaseTag must be the same immutable tag');
|
|
153
|
+
}
|
|
154
|
+
if (!isCanonicalDigest(registry.digest)) {
|
|
155
|
+
errors.push('registry.digest must be a canonical sha256 digest');
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
registryDigest = registry.digest;
|
|
159
|
+
if (reportArg.adoptionDigest === registry.digest) {
|
|
160
|
+
errors.push('adoption digest must be a distinct full-state fence, not the image digest');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!indexMediaTypes.has(registry.mediaType)) {
|
|
164
|
+
errors.push('registry.mediaType must identify an OCI image index');
|
|
165
|
+
}
|
|
166
|
+
if (!isBoundedIdentifier(registry.releaseId)) {
|
|
167
|
+
errors.push('registry.releaseId must be a bounded canonical identifier');
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(registry.platforms) || registry.platforms.length === 0) {
|
|
170
|
+
errors.push('registry.platforms must contain OCI platform evidence');
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
for (const architecture of ['amd64', 'arm64']) {
|
|
174
|
+
if (!registry.platforms.some((platformArg) => isRecord(platformArg)
|
|
175
|
+
&& platformArg.os === 'linux'
|
|
176
|
+
&& platformArg.architecture === architecture
|
|
177
|
+
&& platformArg.runnable === true
|
|
178
|
+
&& platformArg.manifestPresent === true
|
|
179
|
+
&& isCanonicalDigest(platformArg.digest))) {
|
|
180
|
+
errors.push(`registry.platforms must contain runnable linux/${architecture} evidence`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
let requiredReplicaCount;
|
|
186
|
+
let targetNodeNames = [];
|
|
187
|
+
let replicasPerNode;
|
|
188
|
+
if (!isRecord(targetScope)) {
|
|
189
|
+
errors.push('a GO report requires an immutable target scope');
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const clusterIds = targetScope.targetClusterIds;
|
|
193
|
+
const nodeNames = targetScope.targetNodeNames;
|
|
194
|
+
replicasPerNode = targetScope.replicasPerNode;
|
|
195
|
+
requiredReplicaCount = targetScope.requiredReplicaCount;
|
|
196
|
+
if (!Array.isArray(clusterIds) || clusterIds.length !== 1
|
|
197
|
+
|| clusterIds.some((valueArg) => !isBoundedIdentifier(valueArg))
|
|
198
|
+
|| new Set(clusterIds).size !== clusterIds.length) {
|
|
199
|
+
errors.push('targetScope.targetClusterIds must identify exactly one attested cluster');
|
|
200
|
+
}
|
|
201
|
+
if (!Array.isArray(nodeNames) || nodeNames.length === 0
|
|
202
|
+
|| nodeNames.some((valueArg) => !isBoundedIdentifier(valueArg))
|
|
203
|
+
|| new Set(nodeNames).size !== nodeNames.length) {
|
|
204
|
+
errors.push('targetScope.targetNodeNames must be non-empty and unique');
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
targetNodeNames = nodeNames;
|
|
208
|
+
}
|
|
209
|
+
if (!Number.isSafeInteger(replicasPerNode) || replicasPerNode < 1) {
|
|
210
|
+
errors.push('targetScope.replicasPerNode must be a positive integer');
|
|
211
|
+
}
|
|
212
|
+
if (!Number.isSafeInteger(requiredReplicaCount) || requiredReplicaCount < 1
|
|
213
|
+
|| requiredReplicaCount !== targetNodeNames.length * (replicasPerNode || 0)) {
|
|
214
|
+
errors.push('targetScope.requiredReplicaCount must match nodes times replicas');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(runtimeTasks) || runtimeTasks.length === 0) {
|
|
218
|
+
errors.push('a GO report requires runtime task evidence');
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (requiredReplicaCount !== undefined && runtimeTasks.length !== requiredReplicaCount) {
|
|
222
|
+
errors.push('runtime task count must equal the required replica count');
|
|
223
|
+
}
|
|
224
|
+
const deploymentIds = new Set();
|
|
225
|
+
const taskCountsByNode = new Map();
|
|
226
|
+
for (const task of runtimeTasks) {
|
|
227
|
+
if (!isRecord(task)
|
|
228
|
+
|| !isBoundedIdentifier(task.deploymentId)
|
|
229
|
+
|| deploymentIds.has(task.deploymentId)) {
|
|
230
|
+
errors.push('runtime task deployment IDs must be valid and unique');
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
deploymentIds.add(task.deploymentId);
|
|
234
|
+
if (!isBoundedIdentifier(task.nodeName) || !targetNodeNames.includes(task.nodeName)) {
|
|
235
|
+
errors.push('every runtime task must belong to the exact target node scope');
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
taskCountsByNode.set(task.nodeName, (taskCountsByNode.get(task.nodeName) || 0) + 1);
|
|
239
|
+
}
|
|
240
|
+
if (task.status !== 'running' || task.healthStatus !== 'healthy') {
|
|
241
|
+
errors.push('every runtime task must be running and healthy');
|
|
242
|
+
}
|
|
243
|
+
if (!registryDigest || task.observedDigest !== registryDigest) {
|
|
244
|
+
errors.push('every runtime task must attest the exact registry digest');
|
|
245
|
+
}
|
|
246
|
+
if (typeof task.imageReference !== 'string' || !task.imageReference.trim()) {
|
|
247
|
+
errors.push('every runtime task must identify its image reference');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (replicasPerNode !== undefined
|
|
251
|
+
&& targetNodeNames.some((nodeNameArg) => taskCountsByNode.get(nodeNameArg) !== replicasPerNode)) {
|
|
252
|
+
errors.push('runtime task evidence must cover every target node and replica');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (isRecord(attestation) && requiredReplicaCount !== undefined) {
|
|
256
|
+
if (attestation.requiredReplicaCount !== requiredReplicaCount
|
|
257
|
+
|| attestation.observedReplicaCount !== requiredReplicaCount
|
|
258
|
+
|| attestation.healthyReplicaCount !== requiredReplicaCount
|
|
259
|
+
|| attestation.observedDigestReplicaCount !== requiredReplicaCount
|
|
260
|
+
|| attestation.observedDigestConsistent !== true) {
|
|
261
|
+
errors.push('runtime attestation must consistently cover every required replica');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (isRecord(service) && isRecord(service.registryTarget) && isRecord(registry)) {
|
|
265
|
+
const target = service.registryTarget;
|
|
266
|
+
if (target.protocol !== 'oci'
|
|
267
|
+
|| target.tag !== registry.tag
|
|
268
|
+
|| target.serviceId !== service.serviceId
|
|
269
|
+
|| target.imageId !== service.imageId
|
|
270
|
+
|| typeof target.registryHost !== 'string'
|
|
271
|
+
|| !target.registryHost
|
|
272
|
+
|| typeof target.repository !== 'string'
|
|
273
|
+
|| !target.repository
|
|
274
|
+
|| target.imageUrl !== `${target.registryHost}/${target.repository}:${target.tag}`) {
|
|
275
|
+
errors.push('service registry target must exactly bind the adopted service, image, and tag');
|
|
276
|
+
}
|
|
277
|
+
if (registryDigest) {
|
|
278
|
+
const digestPinnedReference = `${target.registryHost}/${target.repository}@${registryDigest}`;
|
|
279
|
+
const tagAndDigestReference = `${target.imageUrl}@${registryDigest}`;
|
|
280
|
+
const configuredReferences = new Set([
|
|
281
|
+
target.tag,
|
|
282
|
+
target.imageUrl,
|
|
283
|
+
digestPinnedReference,
|
|
284
|
+
tagAndDigestReference,
|
|
285
|
+
]);
|
|
286
|
+
if (!configuredReferences.has(service.configuredReference)) {
|
|
287
|
+
errors.push('service configured reference must exactly bind the adopted registry target');
|
|
288
|
+
}
|
|
289
|
+
const runtimeReferences = new Set([
|
|
290
|
+
target.imageUrl,
|
|
291
|
+
digestPinnedReference,
|
|
292
|
+
tagAndDigestReference,
|
|
293
|
+
]);
|
|
294
|
+
if (Array.isArray(runtimeTasks)
|
|
295
|
+
&& runtimeTasks.some((taskArg) => !isRecord(taskArg)
|
|
296
|
+
|| !runtimeReferences.has(taskArg.imageReference))) {
|
|
297
|
+
errors.push('every runtime task image reference must exactly bind the adopted registry target');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return errors;
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwbG95bWVudGFkb3B0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZGF0YS9kZXBsb3ltZW50YWRvcHRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBVUEsT0FBTyxFQUFFLDRCQUE0QixFQUFFLHFCQUFxQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUErSDFGLE1BQU0sdUJBQXVCLEdBQUcscUNBQXFDLENBQUM7QUFDdEUsTUFBTSxlQUFlLEdBQUcsSUFBSSxHQUFHLENBQXdCO0lBQ3JELHlDQUF5QztJQUN6QywyREFBMkQ7Q0FDNUQsQ0FBQyxDQUFDO0FBQ0gsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLEdBQUcsQ0FBd0M7SUFDMUUsaUJBQWlCO0lBQ2pCLHVCQUF1QjtJQUN2QixtQkFBbUI7SUFDbkIsNEJBQTRCO0lBQzVCLDJCQUEyQjtJQUMzQix3QkFBd0I7SUFDeEIseUJBQXlCO0lBQ3pCLDRCQUE0QjtJQUM1Qix5QkFBeUI7SUFDekIsb0JBQW9CO0lBQ3BCLHNCQUFzQjtJQUN0QixtQkFBbUI7SUFDbkIsdUJBQXVCO0lBQ3ZCLDBCQUEwQjtJQUMxQixnQ0FBZ0M7SUFDaEMseUJBQXlCO0NBQzFCLENBQUMsQ0FBQztBQUVILE1BQU0sUUFBUSxHQUFHLENBQUMsUUFBaUIsRUFBdUMsRUFBRSxDQUMxRSxPQUFPLENBQUMsUUFBUSxDQUFDLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUVoRixNQUFNLG1CQUFtQixHQUFHLENBQUMsUUFBaUIsRUFBc0IsRUFBRSxDQUNwRSxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksdUJBQXVCLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBRXpFLE1BQU0saUJBQWlCLEdBQUcsQ0FBQyxRQUFpQixFQUE2QixFQUFFLENBQ3pFLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxRQUFRLENBQUMs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
|
package/dist_ts/data/index.d.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 './deploymentadoption.js';
|
|
5
6
|
export * from './deploymentoperation.js';
|
|
6
7
|
export * from './deploymentpreflight.js';
|
|
7
8
|
export * from './migration.js';
|
package/dist_ts/data/index.js
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 './deploymentadoption.js';
|
|
5
6
|
export * from './deploymentoperation.js';
|
|
6
7
|
export * from './deploymentpreflight.js';
|
|
7
8
|
export * from './migration.js';
|
|
@@ -32,4 +33,4 @@ export * from './taskexecution.js';
|
|
|
32
33
|
export * from './traffic.js';
|
|
33
34
|
export * from './user.js';
|
|
34
35
|
export * from './version.js';
|
|
35
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
36
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLFlBQVksQ0FBQztBQUMzQixjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLFdBQVcsQ0FBQztBQUMxQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLFdBQVcsQ0FBQztBQUMxQixjQUFjLGNBQWMsQ0FBQyJ9
|
package/dist_ts/data/user.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export interface IToken {
|
|
|
3
3
|
expiresAt: number;
|
|
4
4
|
assignedRoles: string[];
|
|
5
5
|
}
|
|
6
|
-
export type TServiceDeploymentCapability = 'deployment:preflight' | 'deployment:reserve' | 'deployment:registry-push' | 'deployment:release-read' | 'deployment:promote-image' | 'deployment:status' | 'deployment:create-greenfield' | 'deployment:promote-route' | 'deployment:rollback-image' | 'deployment:rollback-route' | 'deployment:retry' | 'deployment:cleanup';
|
|
6
|
+
export type TServiceDeploymentCapability = 'deployment:preflight' | 'deployment:reserve' | 'deployment:registry-push' | 'deployment:release-read' | 'deployment:promote-image' | 'deployment:adopt-existing' | 'deployment:status' | 'deployment:create-greenfield' | 'deployment:promote-route' | 'deployment:rollback-image' | 'deployment:rollback-route' | 'deployment:retry' | 'deployment:cleanup';
|
|
7
7
|
/**
|
|
8
8
|
* Server-persisted authority for one logical service. These grants are never
|
|
9
9
|
* accepted from the identity payload supplied by a caller; Cloudly reloads
|
package/dist_ts/data/user.js
CHANGED
|
@@ -4,6 +4,7 @@ const serviceDeploymentCapabilities = new Set([
|
|
|
4
4
|
'deployment:registry-push',
|
|
5
5
|
'deployment:release-read',
|
|
6
6
|
'deployment:promote-image',
|
|
7
|
+
'deployment:adopt-existing',
|
|
7
8
|
'deployment:status',
|
|
8
9
|
'deployment:create-greenfield',
|
|
9
10
|
'deployment:promote-route',
|
|
@@ -49,6 +50,10 @@ export const validateServiceDeploymentGrant = (grantArg) => {
|
|
|
49
50
|
&& !capabilities.includes('deployment:create-greenfield')) {
|
|
50
51
|
errors.push('an organization-service-slot grant must authorize greenfield creation');
|
|
51
52
|
}
|
|
53
|
+
if (capabilities && grant.scope !== 'service'
|
|
54
|
+
&& capabilities.includes('deployment:adopt-existing')) {
|
|
55
|
+
errors.push('deployment:adopt-existing requires an existing-service grant');
|
|
56
|
+
}
|
|
52
57
|
return errors;
|
|
53
58
|
};
|
|
54
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
59
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvdXNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUE4Q0EsTUFBTSw2QkFBNkIsR0FBRyxJQUFJLEdBQUcsQ0FBK0I7SUFDMUUsc0JBQXNCO0lBQ3RCLG9CQUFvQjtJQUNwQiwwQkFBMEI7SUFDMUIseUJBQXlCO0lBQ3pCLDBCQUEwQjtJQUMxQiwyQkFBMkI7SUFDM0IsbUJBQW1CO0lBQ25CLDhCQUE4QjtJQUM5QiwwQkFBMEI7SUFDMUIsMkJBQTJCO0lBQzNCLDJCQUEyQjtJQUMzQixrQkFBa0I7SUFDbEIsb0JBQW9CO0NBQ3JCLENBQUMsQ0FBQztBQUVILE1BQU0saUNBQWlDLEdBQUcscUNBQXFDLENBQUM7QUFFaEYsTUFBTSxDQUFDLE1BQU0sOEJBQThCLEdBQUcsQ0FDNUMsUUFBaUIsRUFDUCxFQUFFO0lBQ1osTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBQzVCLElBQUksQ0FBQyxRQUFRLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUN6RSxPQUFPLENBQUMseUJBQXlCLENBQUMsQ0FBQztJQUNyQyxDQUFDO0lBQ0QsTUFBTSxLQUFLLEdBQUcsUUFBbUMsQ0FBQztJQUNsRCxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssMkJBQTJCLEVBQUUsQ0FBQztRQUM3RSxNQUFNLENBQUMsSUFBSSxDQUFDLHlFQUF5RSxDQUFDLENBQUM7SUFDekYsQ0FBQztJQUNELElBQUksT0FBTyxLQUFLLENBQUMsY0FBYyxLQUFLLFFBQVE7V0FDdkMsQ0FBQyxpQ0FBaUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7UUFDbkUsTUFBTSxDQUFDLElBQUksQ0FBQyx1REFBdUQsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFDRCxJQUFJLE9BQU8sS0FBSyxDQUFDLFNBQVMsS0FBSyxRQUFRO1dBQ2xDLENBQUMsaUNBQWlDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1FBQzlELE1BQU0sQ0FBQyxJQUFJLENBQUMsa0RBQWtELENBQUMsQ0FBQztJQUNsRSxDQUFDO0lBQ0QsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDO1FBQ3BELENBQUMsQ0FBQyxLQUFLLENBQUMsWUFBWTtRQUNwQixDQUFDLENBQUMsU0FBUyxDQUFDO0lBQ2QsSUFBSSxDQUFDLFlBQVk7V0FDWixZQUFZLENBQUMsTUFBTSxHQUFHLENBQUM7V0FDdkIsWUFBWSxDQUFDLE1BQU0sR0FBRyw2QkFBNkIsQ0FBQyxJQUFJO1dBQ3hELElBQUksR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDLElBQUksS0FBSyxZQUFZLENBQUMsTUFBTTtXQUNsRCxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUN0QyxPQUFPLGFBQWEsS0FBSyxRQUFRO2VBQzlCLENBQUMsNkJBQTZCLENBQUMsR0FBRyxDQUFDLGFBQTZDLENBQUMsQ0FDckYsQ0FBQyxFQUFFLENBQUM7UUFDTCxNQUFNLENBQUMsSUFBSSxDQUFDLGtGQUFrRixDQUFDLENBQUM7SUFDbEcsQ0FBQztJQUNELElBQUksWUFBWSxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssU0FBUztXQUN4QyxZQUFZLENBQUMsUUFBUSxDQUFDLDhCQUE4QixDQUFDLEVBQUUsQ0FBQztRQUMzRCxNQUFNLENBQUMsSUFBSSxDQUFDLGdFQUFnRSxDQUFDLENBQUM7SUFDaEYsQ0FBQztJQUNELElBQUksWUFBWSxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssMkJBQTJCO1dBQzFELENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyw4QkFBOEIsQ0FBQyxFQUFFLENBQUM7UUFDNUQsTUFBTSxDQUFDLElBQUksQ0FBQyx1RUFBdUUsQ0FBQyxDQUFDO0lBQ3ZGLENBQUM7SUFDRCxJQUFJLFlBQVksSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVM7V0FDeEMsWUFBWSxDQUFDLFFBQVEsQ0FBQywyQkFBMkIsQ0FBQyxFQUFFLENBQUM7UUFDeEQsTUFBTSxDQUFDLElBQUksQ0FBQyw4REFBOEQsQ0FBQyxDQUFDO0lBQzlFLENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDLENBQUMifQ==
|
|
@@ -7,6 +7,26 @@ import type { IImageRelease } from '../data/immutableimage.js';
|
|
|
7
7
|
import type { IDeploymentPreflightReport, IDeploymentPreflightRequestData } from '../data/deploymentpreflight.js';
|
|
8
8
|
import type { IDeploymentReservationInput, IServiceDeploymentOperation, IServiceDeploymentStatus } from '../data/deploymentoperation.js';
|
|
9
9
|
import type { TSha256Digest } from '../data/immutableimage.js';
|
|
10
|
+
import type { IServiceDeploymentAdoptionInput, IServiceDeploymentAdoptionPreflightInput, IServiceDeploymentAdoptionReport, IServiceDeploymentAdoptionResult } from '../data/deploymentadoption.js';
|
|
11
|
+
export interface IRequest_Any_Cloudly_GetServiceDeploymentAdoptionPreflight extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceDeploymentAdoptionPreflight> {
|
|
12
|
+
method: 'getServiceDeploymentAdoptionPreflight';
|
|
13
|
+
request: IServiceDeploymentAdoptionPreflightInput & {
|
|
14
|
+
identity: IIdentityCredential;
|
|
15
|
+
};
|
|
16
|
+
response: {
|
|
17
|
+
report: IServiceDeploymentAdoptionReport;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface IRequest_Any_Cloudly_AdoptServiceDeployment extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_AdoptServiceDeployment> {
|
|
21
|
+
method: 'adoptServiceDeployment';
|
|
22
|
+
request: IServiceDeploymentAdoptionInput & {
|
|
23
|
+
identity: IIdentityCredential;
|
|
24
|
+
};
|
|
25
|
+
response: {
|
|
26
|
+
service: IService;
|
|
27
|
+
result: IServiceDeploymentAdoptionResult;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
10
30
|
export interface IRequest_Any_Cloudly_GetDeploymentPreflight extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetDeploymentPreflight> {
|
|
11
31
|
method: 'getDeploymentPreflight';
|
|
12
32
|
request: IDeploymentPreflightRequestData & {
|
package/package.json
CHANGED
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IDeploymentPreflightRuntimeTask,
|
|
3
|
+
IOciPlatformDescriptorSummary,
|
|
4
|
+
} from './deploymentpreflight.js';
|
|
5
|
+
import type {
|
|
6
|
+
IImmutableImageDeploymentPlan,
|
|
7
|
+
IImmutableImageTargetScope,
|
|
8
|
+
TOciManifestMediaType,
|
|
9
|
+
TSha256Digest,
|
|
10
|
+
} from './immutableimage.js';
|
|
11
|
+
import { normalizeImmutableReleaseTag, normalizeSha256Digest } from './immutableimage.js';
|
|
12
|
+
import type { IRegistryTarget } from './registry.js';
|
|
13
|
+
|
|
14
|
+
export type TServiceDeploymentAdoptionDecision = 'GO' | 'NO-GO';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Canonical sha256 hash of the complete adoption preflight snapshot. It is a
|
|
18
|
+
* state/TOCTOU fence and must never be substituted with the OCI image digest.
|
|
19
|
+
*/
|
|
20
|
+
export type TServiceDeploymentAdoptionDigest = TSha256Digest & {
|
|
21
|
+
readonly __serviceDeploymentAdoptionDigest: unique symbol;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type TServiceDeploymentAdoptionBlockerCode =
|
|
25
|
+
| 'INVALID_REQUEST'
|
|
26
|
+
| 'AUTHORIZATION_MISSING'
|
|
27
|
+
| 'SERVICE_NOT_FOUND'
|
|
28
|
+
| 'SERVICE_OWNERSHIP_MISMATCH'
|
|
29
|
+
| 'SERVICE_ALREADY_IMMUTABLE'
|
|
30
|
+
| 'SERVICE_STATE_CONFLICT'
|
|
31
|
+
| 'REGISTRY_TARGET_MISSING'
|
|
32
|
+
| 'REGISTRY_REFERENCE_INVALID'
|
|
33
|
+
| 'REGISTRY_DIGEST_MISSING'
|
|
34
|
+
| 'OCI_INDEX_REQUIRED'
|
|
35
|
+
| 'OCI_PLATFORM_MISSING'
|
|
36
|
+
| 'RELEASE_NOT_FOUND'
|
|
37
|
+
| 'RELEASE_TAG_AMBIGUOUS'
|
|
38
|
+
| 'TARGET_SCOPE_UNAVAILABLE'
|
|
39
|
+
| 'RUNTIME_ATTESTATION_INCOMPLETE'
|
|
40
|
+
| 'RUNTIME_DIGEST_MISMATCH';
|
|
41
|
+
|
|
42
|
+
export interface IServiceDeploymentAdoptionBlocker {
|
|
43
|
+
code: TServiceDeploymentAdoptionBlockerCode;
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface IServiceDeploymentAdoptionPreflightInput {
|
|
48
|
+
requestId: string;
|
|
49
|
+
organizationId: string;
|
|
50
|
+
serviceId: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface IServiceDeploymentAdoptionInput
|
|
54
|
+
extends IServiceDeploymentAdoptionPreflightInput {
|
|
55
|
+
idempotencyKey: string;
|
|
56
|
+
expectedAdoptionDigest: TServiceDeploymentAdoptionDigest;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface IServiceDeploymentAdoptionReportBase {
|
|
60
|
+
schemaVersion: 1;
|
|
61
|
+
mutationPerformed: false;
|
|
62
|
+
requestId: string;
|
|
63
|
+
generatedAt: number;
|
|
64
|
+
runtimeAttestation: {
|
|
65
|
+
requiredReplicaCount: number;
|
|
66
|
+
observedReplicaCount: number;
|
|
67
|
+
healthyReplicaCount: number;
|
|
68
|
+
observedDigestReplicaCount: number;
|
|
69
|
+
observedDigestConsistent: boolean;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface IServiceDeploymentAdoptionServiceSnapshot {
|
|
74
|
+
organizationId: string;
|
|
75
|
+
serviceId: string;
|
|
76
|
+
serviceName: string;
|
|
77
|
+
imageId: string;
|
|
78
|
+
deployOnPush: boolean;
|
|
79
|
+
immutableImageRequired: boolean;
|
|
80
|
+
configuredReference: string;
|
|
81
|
+
registryTarget: IRegistryTarget;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface IServiceDeploymentAdoptionRegistrySnapshot {
|
|
85
|
+
tag?: string;
|
|
86
|
+
digest?: TSha256Digest;
|
|
87
|
+
mediaType?: TOciManifestMediaType;
|
|
88
|
+
platforms: IOciPlatformDescriptorSummary[];
|
|
89
|
+
releaseId?: string;
|
|
90
|
+
releaseTag?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface IServiceDeploymentAdoptionGoReport
|
|
94
|
+
extends IServiceDeploymentAdoptionReportBase {
|
|
95
|
+
decision: 'GO';
|
|
96
|
+
service: IServiceDeploymentAdoptionServiceSnapshot & {
|
|
97
|
+
deployOnPush: true;
|
|
98
|
+
immutableImageRequired: false;
|
|
99
|
+
};
|
|
100
|
+
registry: IServiceDeploymentAdoptionRegistrySnapshot & {
|
|
101
|
+
tag: string;
|
|
102
|
+
digest: TSha256Digest;
|
|
103
|
+
mediaType:
|
|
104
|
+
| 'application/vnd.oci.image.index.v1+json'
|
|
105
|
+
| 'application/vnd.docker.distribution.manifest.list.v2+json';
|
|
106
|
+
platforms: [IOciPlatformDescriptorSummary, ...IOciPlatformDescriptorSummary[]];
|
|
107
|
+
releaseId: string;
|
|
108
|
+
releaseTag: string;
|
|
109
|
+
};
|
|
110
|
+
runtimeTasks: [IDeploymentPreflightRuntimeTask, ...IDeploymentPreflightRuntimeTask[]];
|
|
111
|
+
targetScope: IImmutableImageTargetScope;
|
|
112
|
+
adoptionDigest: TServiceDeploymentAdoptionDigest;
|
|
113
|
+
blockers: [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface IServiceDeploymentAdoptionNoGoReport
|
|
117
|
+
extends IServiceDeploymentAdoptionReportBase {
|
|
118
|
+
decision: 'NO-GO';
|
|
119
|
+
service?: IServiceDeploymentAdoptionServiceSnapshot;
|
|
120
|
+
registry: IServiceDeploymentAdoptionRegistrySnapshot;
|
|
121
|
+
runtimeTasks: IDeploymentPreflightRuntimeTask[];
|
|
122
|
+
targetScope?: IImmutableImageTargetScope;
|
|
123
|
+
adoptionDigest?: never;
|
|
124
|
+
blockers: [IServiceDeploymentAdoptionBlocker, ...IServiceDeploymentAdoptionBlocker[]];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type IServiceDeploymentAdoptionReport =
|
|
128
|
+
| IServiceDeploymentAdoptionGoReport
|
|
129
|
+
| IServiceDeploymentAdoptionNoGoReport;
|
|
130
|
+
|
|
131
|
+
export interface IServiceDeploymentAdoptionResult {
|
|
132
|
+
adoptionId: string;
|
|
133
|
+
adoptionDigest: TServiceDeploymentAdoptionDigest;
|
|
134
|
+
idempotentReplay: boolean;
|
|
135
|
+
plan: IImmutableImageDeploymentPlan;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const boundaryIdentifierRegex = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
|
|
139
|
+
const indexMediaTypes = new Set<TOciManifestMediaType>([
|
|
140
|
+
'application/vnd.oci.image.index.v1+json',
|
|
141
|
+
'application/vnd.docker.distribution.manifest.list.v2+json',
|
|
142
|
+
]);
|
|
143
|
+
const adoptionBlockerCodes = new Set<TServiceDeploymentAdoptionBlockerCode>([
|
|
144
|
+
'INVALID_REQUEST',
|
|
145
|
+
'AUTHORIZATION_MISSING',
|
|
146
|
+
'SERVICE_NOT_FOUND',
|
|
147
|
+
'SERVICE_OWNERSHIP_MISMATCH',
|
|
148
|
+
'SERVICE_ALREADY_IMMUTABLE',
|
|
149
|
+
'SERVICE_STATE_CONFLICT',
|
|
150
|
+
'REGISTRY_TARGET_MISSING',
|
|
151
|
+
'REGISTRY_REFERENCE_INVALID',
|
|
152
|
+
'REGISTRY_DIGEST_MISSING',
|
|
153
|
+
'OCI_INDEX_REQUIRED',
|
|
154
|
+
'OCI_PLATFORM_MISSING',
|
|
155
|
+
'RELEASE_NOT_FOUND',
|
|
156
|
+
'RELEASE_TAG_AMBIGUOUS',
|
|
157
|
+
'TARGET_SCOPE_UNAVAILABLE',
|
|
158
|
+
'RUNTIME_ATTESTATION_INCOMPLETE',
|
|
159
|
+
'RUNTIME_DIGEST_MISMATCH',
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> =>
|
|
163
|
+
Boolean(valueArg) && typeof valueArg === 'object' && !Array.isArray(valueArg);
|
|
164
|
+
|
|
165
|
+
const isBoundedIdentifier = (valueArg: unknown): valueArg is string =>
|
|
166
|
+
typeof valueArg === 'string' && boundaryIdentifierRegex.test(valueArg);
|
|
167
|
+
|
|
168
|
+
const isCanonicalDigest = (valueArg: unknown): valueArg is TSha256Digest =>
|
|
169
|
+
Boolean(normalizeSha256Digest(valueArg)) && normalizeSha256Digest(valueArg) === valueArg;
|
|
170
|
+
|
|
171
|
+
const isNonNegativeInteger = (valueArg: unknown): valueArg is number =>
|
|
172
|
+
Number.isSafeInteger(valueArg) && (valueArg as number) >= 0;
|
|
173
|
+
|
|
174
|
+
export const validateServiceDeploymentAdoptionPreflightInput = (
|
|
175
|
+
inputArg: unknown,
|
|
176
|
+
): string[] => {
|
|
177
|
+
if (!isRecord(inputArg)) return ['adoption preflight input must be an object'];
|
|
178
|
+
const errors: string[] = [];
|
|
179
|
+
if (!isBoundedIdentifier(inputArg.requestId)) {
|
|
180
|
+
errors.push('requestId must be a bounded canonical identifier');
|
|
181
|
+
}
|
|
182
|
+
for (const key of ['organizationId', 'serviceId'] as const) {
|
|
183
|
+
if (!isBoundedIdentifier(inputArg[key])) {
|
|
184
|
+
errors.push(`${key} must be a bounded canonical identifier`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return errors;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const validateServiceDeploymentAdoptionInput = (inputArg: unknown): string[] => {
|
|
191
|
+
const errors = validateServiceDeploymentAdoptionPreflightInput(inputArg);
|
|
192
|
+
if (!isRecord(inputArg)) return errors;
|
|
193
|
+
if (!isBoundedIdentifier(inputArg.idempotencyKey)) {
|
|
194
|
+
errors.push('idempotencyKey must be a bounded canonical identifier');
|
|
195
|
+
}
|
|
196
|
+
if (!isCanonicalDigest(inputArg.expectedAdoptionDigest)) {
|
|
197
|
+
errors.push('expectedAdoptionDigest must be a canonical sha256 digest');
|
|
198
|
+
}
|
|
199
|
+
return errors;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const validateServiceDeploymentAdoptionReport = (reportArg: unknown): string[] => {
|
|
203
|
+
if (!isRecord(reportArg)) return ['adoption report must be an object'];
|
|
204
|
+
const errors: string[] = [];
|
|
205
|
+
if (reportArg.schemaVersion !== 1) errors.push('schemaVersion must be 1');
|
|
206
|
+
if (reportArg.mutationPerformed !== false) {
|
|
207
|
+
errors.push('adoption preflight must be mutation-free');
|
|
208
|
+
}
|
|
209
|
+
if (!isBoundedIdentifier(reportArg.requestId)) {
|
|
210
|
+
errors.push('requestId must be a bounded canonical identifier');
|
|
211
|
+
}
|
|
212
|
+
if (!isNonNegativeInteger(reportArg.generatedAt)) {
|
|
213
|
+
errors.push('generatedAt must be a non-negative integer');
|
|
214
|
+
}
|
|
215
|
+
if (reportArg.decision !== 'GO' && reportArg.decision !== 'NO-GO') {
|
|
216
|
+
errors.push('decision must be GO or NO-GO');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const attestation = reportArg.runtimeAttestation;
|
|
220
|
+
if (!isRecord(attestation)) {
|
|
221
|
+
errors.push('runtimeAttestation must be an object');
|
|
222
|
+
} else {
|
|
223
|
+
for (const key of [
|
|
224
|
+
'requiredReplicaCount',
|
|
225
|
+
'observedReplicaCount',
|
|
226
|
+
'healthyReplicaCount',
|
|
227
|
+
'observedDigestReplicaCount',
|
|
228
|
+
] as const) {
|
|
229
|
+
if (!isNonNegativeInteger(attestation[key])) {
|
|
230
|
+
errors.push(`runtimeAttestation.${key} must be a non-negative integer`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (typeof attestation.observedDigestConsistent !== 'boolean') {
|
|
234
|
+
errors.push('runtimeAttestation.observedDigestConsistent must be boolean');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const blockers = reportArg.blockers;
|
|
239
|
+
if (!Array.isArray(blockers)) {
|
|
240
|
+
errors.push('blockers must be an array');
|
|
241
|
+
} else {
|
|
242
|
+
for (const blocker of blockers) {
|
|
243
|
+
if (!isRecord(blocker)
|
|
244
|
+
|| !adoptionBlockerCodes.has(blocker.code as TServiceDeploymentAdoptionBlockerCode)
|
|
245
|
+
|| typeof blocker.message !== 'string'
|
|
246
|
+
|| !blocker.message.trim()) {
|
|
247
|
+
errors.push('each blocker must contain a supported code and non-empty message');
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (reportArg.decision === 'NO-GO') {
|
|
254
|
+
if (Array.isArray(blockers) && blockers.length === 0) {
|
|
255
|
+
errors.push('a NO-GO report must contain at least one blocker');
|
|
256
|
+
}
|
|
257
|
+
if (reportArg.adoptionDigest !== undefined) {
|
|
258
|
+
errors.push('a NO-GO report must not contain an adoption digest');
|
|
259
|
+
}
|
|
260
|
+
return errors;
|
|
261
|
+
}
|
|
262
|
+
if (reportArg.decision !== 'GO') return errors;
|
|
263
|
+
|
|
264
|
+
if (Array.isArray(blockers) && blockers.length !== 0) {
|
|
265
|
+
errors.push('a GO report must not contain blockers');
|
|
266
|
+
}
|
|
267
|
+
if (!isCanonicalDigest(reportArg.adoptionDigest)) {
|
|
268
|
+
errors.push('a GO report requires a canonical adoption digest');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const service = reportArg.service;
|
|
272
|
+
const registry = reportArg.registry;
|
|
273
|
+
const targetScope = reportArg.targetScope;
|
|
274
|
+
const runtimeTasks = reportArg.runtimeTasks;
|
|
275
|
+
if (!isRecord(service)) {
|
|
276
|
+
errors.push('a GO report requires a service snapshot');
|
|
277
|
+
} else {
|
|
278
|
+
for (const key of ['organizationId', 'serviceId', 'serviceName', 'imageId'] as const) {
|
|
279
|
+
if (!isBoundedIdentifier(service[key])) errors.push(`service.${key} is invalid`);
|
|
280
|
+
}
|
|
281
|
+
if (service.deployOnPush !== true || service.immutableImageRequired !== false) {
|
|
282
|
+
errors.push('a GO report requires the legacy mutable service state');
|
|
283
|
+
}
|
|
284
|
+
if (typeof service.configuredReference !== 'string' || !service.configuredReference.trim()) {
|
|
285
|
+
errors.push('service.configuredReference must be non-empty');
|
|
286
|
+
}
|
|
287
|
+
if (!isRecord(service.registryTarget)) {
|
|
288
|
+
errors.push('service.registryTarget must be present');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let registryDigest: TSha256Digest | undefined;
|
|
293
|
+
if (!isRecord(registry)) {
|
|
294
|
+
errors.push('a GO report requires a registry snapshot');
|
|
295
|
+
} else {
|
|
296
|
+
if (!normalizeImmutableReleaseTag(registry.tag) || registry.tag !== registry.releaseTag) {
|
|
297
|
+
errors.push('registry tag and releaseTag must be the same immutable tag');
|
|
298
|
+
}
|
|
299
|
+
if (!isCanonicalDigest(registry.digest)) {
|
|
300
|
+
errors.push('registry.digest must be a canonical sha256 digest');
|
|
301
|
+
} else {
|
|
302
|
+
registryDigest = registry.digest;
|
|
303
|
+
if (reportArg.adoptionDigest === registry.digest) {
|
|
304
|
+
errors.push('adoption digest must be a distinct full-state fence, not the image digest');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!indexMediaTypes.has(registry.mediaType as TOciManifestMediaType)) {
|
|
308
|
+
errors.push('registry.mediaType must identify an OCI image index');
|
|
309
|
+
}
|
|
310
|
+
if (!isBoundedIdentifier(registry.releaseId)) {
|
|
311
|
+
errors.push('registry.releaseId must be a bounded canonical identifier');
|
|
312
|
+
}
|
|
313
|
+
if (!Array.isArray(registry.platforms) || registry.platforms.length === 0) {
|
|
314
|
+
errors.push('registry.platforms must contain OCI platform evidence');
|
|
315
|
+
} else {
|
|
316
|
+
for (const architecture of ['amd64', 'arm64']) {
|
|
317
|
+
if (!registry.platforms.some((platformArg) => isRecord(platformArg)
|
|
318
|
+
&& platformArg.os === 'linux'
|
|
319
|
+
&& platformArg.architecture === architecture
|
|
320
|
+
&& platformArg.runnable === true
|
|
321
|
+
&& platformArg.manifestPresent === true
|
|
322
|
+
&& isCanonicalDigest(platformArg.digest))) {
|
|
323
|
+
errors.push(`registry.platforms must contain runnable linux/${architecture} evidence`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let requiredReplicaCount: number | undefined;
|
|
330
|
+
let targetNodeNames: string[] = [];
|
|
331
|
+
let replicasPerNode: number | undefined;
|
|
332
|
+
if (!isRecord(targetScope)) {
|
|
333
|
+
errors.push('a GO report requires an immutable target scope');
|
|
334
|
+
} else {
|
|
335
|
+
const clusterIds = targetScope.targetClusterIds;
|
|
336
|
+
const nodeNames = targetScope.targetNodeNames;
|
|
337
|
+
replicasPerNode = targetScope.replicasPerNode as number;
|
|
338
|
+
requiredReplicaCount = targetScope.requiredReplicaCount as number;
|
|
339
|
+
if (!Array.isArray(clusterIds) || clusterIds.length !== 1
|
|
340
|
+
|| clusterIds.some((valueArg) => !isBoundedIdentifier(valueArg))
|
|
341
|
+
|| new Set(clusterIds).size !== clusterIds.length) {
|
|
342
|
+
errors.push('targetScope.targetClusterIds must identify exactly one attested cluster');
|
|
343
|
+
}
|
|
344
|
+
if (!Array.isArray(nodeNames) || nodeNames.length === 0
|
|
345
|
+
|| nodeNames.some((valueArg) => !isBoundedIdentifier(valueArg))
|
|
346
|
+
|| new Set(nodeNames).size !== nodeNames.length) {
|
|
347
|
+
errors.push('targetScope.targetNodeNames must be non-empty and unique');
|
|
348
|
+
} else {
|
|
349
|
+
targetNodeNames = nodeNames as string[];
|
|
350
|
+
}
|
|
351
|
+
if (!Number.isSafeInteger(replicasPerNode) || replicasPerNode < 1) {
|
|
352
|
+
errors.push('targetScope.replicasPerNode must be a positive integer');
|
|
353
|
+
}
|
|
354
|
+
if (!Number.isSafeInteger(requiredReplicaCount) || requiredReplicaCount < 1
|
|
355
|
+
|| requiredReplicaCount !== targetNodeNames.length * (replicasPerNode || 0)) {
|
|
356
|
+
errors.push('targetScope.requiredReplicaCount must match nodes times replicas');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!Array.isArray(runtimeTasks) || runtimeTasks.length === 0) {
|
|
361
|
+
errors.push('a GO report requires runtime task evidence');
|
|
362
|
+
} else {
|
|
363
|
+
if (requiredReplicaCount !== undefined && runtimeTasks.length !== requiredReplicaCount) {
|
|
364
|
+
errors.push('runtime task count must equal the required replica count');
|
|
365
|
+
}
|
|
366
|
+
const deploymentIds = new Set<string>();
|
|
367
|
+
const taskCountsByNode = new Map<string, number>();
|
|
368
|
+
for (const task of runtimeTasks) {
|
|
369
|
+
if (!isRecord(task)
|
|
370
|
+
|| !isBoundedIdentifier(task.deploymentId)
|
|
371
|
+
|| deploymentIds.has(task.deploymentId as string)) {
|
|
372
|
+
errors.push('runtime task deployment IDs must be valid and unique');
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
deploymentIds.add(task.deploymentId as string);
|
|
376
|
+
if (!isBoundedIdentifier(task.nodeName) || !targetNodeNames.includes(task.nodeName)) {
|
|
377
|
+
errors.push('every runtime task must belong to the exact target node scope');
|
|
378
|
+
} else {
|
|
379
|
+
taskCountsByNode.set(task.nodeName, (taskCountsByNode.get(task.nodeName) || 0) + 1);
|
|
380
|
+
}
|
|
381
|
+
if (task.status !== 'running' || task.healthStatus !== 'healthy') {
|
|
382
|
+
errors.push('every runtime task must be running and healthy');
|
|
383
|
+
}
|
|
384
|
+
if (!registryDigest || task.observedDigest !== registryDigest) {
|
|
385
|
+
errors.push('every runtime task must attest the exact registry digest');
|
|
386
|
+
}
|
|
387
|
+
if (typeof task.imageReference !== 'string' || !task.imageReference.trim()) {
|
|
388
|
+
errors.push('every runtime task must identify its image reference');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (replicasPerNode !== undefined
|
|
392
|
+
&& targetNodeNames.some((nodeNameArg) => taskCountsByNode.get(nodeNameArg) !== replicasPerNode)) {
|
|
393
|
+
errors.push('runtime task evidence must cover every target node and replica');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (isRecord(attestation) && requiredReplicaCount !== undefined) {
|
|
398
|
+
if (attestation.requiredReplicaCount !== requiredReplicaCount
|
|
399
|
+
|| attestation.observedReplicaCount !== requiredReplicaCount
|
|
400
|
+
|| attestation.healthyReplicaCount !== requiredReplicaCount
|
|
401
|
+
|| attestation.observedDigestReplicaCount !== requiredReplicaCount
|
|
402
|
+
|| attestation.observedDigestConsistent !== true) {
|
|
403
|
+
errors.push('runtime attestation must consistently cover every required replica');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (isRecord(service) && isRecord(service.registryTarget) && isRecord(registry)) {
|
|
408
|
+
const target = service.registryTarget;
|
|
409
|
+
if (target.protocol !== 'oci'
|
|
410
|
+
|| target.tag !== registry.tag
|
|
411
|
+
|| target.serviceId !== service.serviceId
|
|
412
|
+
|| target.imageId !== service.imageId
|
|
413
|
+
|| typeof target.registryHost !== 'string'
|
|
414
|
+
|| !target.registryHost
|
|
415
|
+
|| typeof target.repository !== 'string'
|
|
416
|
+
|| !target.repository
|
|
417
|
+
|| target.imageUrl !== `${target.registryHost}/${target.repository}:${target.tag}`) {
|
|
418
|
+
errors.push('service registry target must exactly bind the adopted service, image, and tag');
|
|
419
|
+
}
|
|
420
|
+
if (registryDigest) {
|
|
421
|
+
const digestPinnedReference = `${target.registryHost}/${target.repository}@${registryDigest}`;
|
|
422
|
+
const tagAndDigestReference = `${target.imageUrl}@${registryDigest}`;
|
|
423
|
+
const configuredReferences = new Set([
|
|
424
|
+
target.tag,
|
|
425
|
+
target.imageUrl,
|
|
426
|
+
digestPinnedReference,
|
|
427
|
+
tagAndDigestReference,
|
|
428
|
+
]);
|
|
429
|
+
if (!configuredReferences.has(service.configuredReference as string)) {
|
|
430
|
+
errors.push('service configured reference must exactly bind the adopted registry target');
|
|
431
|
+
}
|
|
432
|
+
const runtimeReferences = new Set([
|
|
433
|
+
target.imageUrl,
|
|
434
|
+
digestPinnedReference,
|
|
435
|
+
tagAndDigestReference,
|
|
436
|
+
]);
|
|
437
|
+
if (Array.isArray(runtimeTasks)
|
|
438
|
+
&& runtimeTasks.some((taskArg) => !isRecord(taskArg)
|
|
439
|
+
|| !runtimeReferences.has(taskArg.imageReference as string))) {
|
|
440
|
+
errors.push('every runtime task image reference must exactly bind the adopted registry target');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return errors;
|
|
445
|
+
};
|
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 './deploymentadoption.js';
|
|
5
6
|
export * from './deploymentoperation.js';
|
|
6
7
|
export * from './deploymentpreflight.js';
|
|
7
8
|
export * from './migration.js';
|
package/ts/data/user.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type TServiceDeploymentCapability =
|
|
|
10
10
|
| 'deployment:registry-push'
|
|
11
11
|
| 'deployment:release-read'
|
|
12
12
|
| 'deployment:promote-image'
|
|
13
|
+
| 'deployment:adopt-existing'
|
|
13
14
|
| 'deployment:status'
|
|
14
15
|
| 'deployment:create-greenfield'
|
|
15
16
|
| 'deployment:promote-route'
|
|
@@ -49,6 +50,7 @@ const serviceDeploymentCapabilities = new Set<TServiceDeploymentCapability>([
|
|
|
49
50
|
'deployment:registry-push',
|
|
50
51
|
'deployment:release-read',
|
|
51
52
|
'deployment:promote-image',
|
|
53
|
+
'deployment:adopt-existing',
|
|
52
54
|
'deployment:status',
|
|
53
55
|
'deployment:create-greenfield',
|
|
54
56
|
'deployment:promote-route',
|
|
@@ -100,6 +102,10 @@ export const validateServiceDeploymentGrant = (
|
|
|
100
102
|
&& !capabilities.includes('deployment:create-greenfield')) {
|
|
101
103
|
errors.push('an organization-service-slot grant must authorize greenfield creation');
|
|
102
104
|
}
|
|
105
|
+
if (capabilities && grant.scope !== 'service'
|
|
106
|
+
&& capabilities.includes('deployment:adopt-existing')) {
|
|
107
|
+
errors.push('deployment:adopt-existing requires an existing-service grant');
|
|
108
|
+
}
|
|
103
109
|
return errors;
|
|
104
110
|
};
|
|
105
111
|
|
package/ts/requests/service.ts
CHANGED
|
@@ -15,6 +15,41 @@ import type {
|
|
|
15
15
|
IServiceDeploymentStatus,
|
|
16
16
|
} from '../data/deploymentoperation.js';
|
|
17
17
|
import type { TSha256Digest } from '../data/immutableimage.js';
|
|
18
|
+
import type {
|
|
19
|
+
IServiceDeploymentAdoptionInput,
|
|
20
|
+
IServiceDeploymentAdoptionPreflightInput,
|
|
21
|
+
IServiceDeploymentAdoptionReport,
|
|
22
|
+
IServiceDeploymentAdoptionResult,
|
|
23
|
+
} from '../data/deploymentadoption.js';
|
|
24
|
+
|
|
25
|
+
export interface IRequest_Any_Cloudly_GetServiceDeploymentAdoptionPreflight
|
|
26
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
27
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
28
|
+
IRequest_Any_Cloudly_GetServiceDeploymentAdoptionPreflight
|
|
29
|
+
> {
|
|
30
|
+
method: 'getServiceDeploymentAdoptionPreflight';
|
|
31
|
+
request: IServiceDeploymentAdoptionPreflightInput & {
|
|
32
|
+
identity: IIdentityCredential;
|
|
33
|
+
};
|
|
34
|
+
response: {
|
|
35
|
+
report: IServiceDeploymentAdoptionReport;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface IRequest_Any_Cloudly_AdoptServiceDeployment
|
|
40
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
41
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
42
|
+
IRequest_Any_Cloudly_AdoptServiceDeployment
|
|
43
|
+
> {
|
|
44
|
+
method: 'adoptServiceDeployment';
|
|
45
|
+
request: IServiceDeploymentAdoptionInput & {
|
|
46
|
+
identity: IIdentityCredential;
|
|
47
|
+
};
|
|
48
|
+
response: {
|
|
49
|
+
service: IService;
|
|
50
|
+
result: IServiceDeploymentAdoptionResult;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
18
53
|
|
|
19
54
|
export interface IRequest_Any_Cloudly_GetDeploymentPreflight
|
|
20
55
|
extends plugins.typedrequestInterfaces.implementsTR<
|