@serve.zone/appstore 1.4.2 → 2.1.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/apps/cloudly/app.json +2 -2
- package/apps/cloudly/versions/1.1.2/config.json +130 -0
- package/apps/openbao/versions/1.0.0/config.json +9 -11
- package/appstore.json +2 -2
- package/changelog.md +20 -0
- package/dist_ts_client/classes.appstoreresolver.js +4 -34
- package/dist_ts_client/data.generated.js +150 -16
- package/dist_ts_client/storage.js +91 -44
- package/dist_ts_client/types.d.ts +1 -1
- package/package.json +3 -3
- package/readme.md +36 -10
- package/ts_client/classes.appstoreresolver.ts +5 -35
- package/ts_client/data.generated.ts +149 -15
- package/ts_client/storage.ts +135 -49
- package/ts_client/types.ts +1 -1
|
@@ -34,11 +34,12 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
34
34
|
"description": "Multi-node serve.zone control plane for clusters, workload services, domains, and deployments.",
|
|
35
35
|
"category": "Dev Tools",
|
|
36
36
|
"iconName": "server",
|
|
37
|
-
"latestVersion": "1.1.
|
|
37
|
+
"latestVersion": "1.1.2",
|
|
38
38
|
"versions": [
|
|
39
39
|
"1.0.0",
|
|
40
40
|
"1.1.0",
|
|
41
|
-
"1.1.1"
|
|
41
|
+
"1.1.1",
|
|
42
|
+
"1.1.2"
|
|
42
43
|
],
|
|
43
44
|
"maintainer": "serve.zone",
|
|
44
45
|
"links": {
|
|
@@ -421,6 +422,141 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
421
422
|
},
|
|
422
423
|
"minOneboxVersion": "2.2.0"
|
|
423
424
|
},
|
|
425
|
+
"apps/cloudly/versions/1.1.2/config.json": {
|
|
426
|
+
"image": "code.foss.global/serve.zone/cloudly@sha256:de503daed88b058d285bf0d7fd3fc21f1a4b8cd93a50aa1ec32fe92a38b43390",
|
|
427
|
+
"targetPorts": [
|
|
428
|
+
{
|
|
429
|
+
"name": "web",
|
|
430
|
+
"port": 3000,
|
|
431
|
+
"protocol": "http",
|
|
432
|
+
"default": true
|
|
433
|
+
}
|
|
434
|
+
],
|
|
435
|
+
"envVars": [
|
|
436
|
+
{
|
|
437
|
+
"key": "SERVEZONE_ENVIRONMENT",
|
|
438
|
+
"value": "production",
|
|
439
|
+
"description": "Cloudly runtime environment.",
|
|
440
|
+
"required": true
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"key": "SERVEZONE_PUBLIC_ORIGIN",
|
|
444
|
+
"value": "https://${SERVICE_DOMAIN}",
|
|
445
|
+
"description": "Canonical public Cloudly origin including protocol and optional public port.",
|
|
446
|
+
"required": true
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"key": "SERVEZONE_LISTENPORT",
|
|
450
|
+
"value": "3000",
|
|
451
|
+
"description": "Internal Cloudly HTTP port inside the container.",
|
|
452
|
+
"required": true
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"key": "SERVEZONE_SSLMODE",
|
|
456
|
+
"value": "external",
|
|
457
|
+
"description": "Use external TLS termination through Onebox or dcrouter.",
|
|
458
|
+
"required": true
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
"key": "MONGODB_URL",
|
|
462
|
+
"value": "${MONGODB_URI}",
|
|
463
|
+
"description": "Authenticated MongoDB wire-protocol URL provisioned by Onebox Corestore.",
|
|
464
|
+
"required": true
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"key": "MONGODB_NAME",
|
|
468
|
+
"value": "${MONGODB_DATABASE}",
|
|
469
|
+
"description": "MongoDB wire-protocol database name provisioned by Onebox Corestore.",
|
|
470
|
+
"required": true
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
"key": "MONGODB_USER",
|
|
474
|
+
"value": "${MONGODB_USERNAME}",
|
|
475
|
+
"description": "MongoDB wire-protocol username provisioned by Onebox Corestore.",
|
|
476
|
+
"required": true
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"key": "MONGODB_PASS",
|
|
480
|
+
"value": "${MONGODB_PASSWORD}",
|
|
481
|
+
"description": "MongoDB wire-protocol password provisioned by Onebox Corestore.",
|
|
482
|
+
"required": true
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"key": "S3_ENDPOINT",
|
|
486
|
+
"value": "onebox-corestore",
|
|
487
|
+
"description": "S3 endpoint host provisioned by Onebox Corestore.",
|
|
488
|
+
"required": true
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"key": "S3_PORT",
|
|
492
|
+
"value": "9000",
|
|
493
|
+
"description": "S3 endpoint port provisioned by Onebox Corestore.",
|
|
494
|
+
"required": true
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"key": "S3_USESSL",
|
|
498
|
+
"value": "false",
|
|
499
|
+
"description": "Use plain HTTP for internal Corestore S3 traffic on the Onebox network.",
|
|
500
|
+
"required": true
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"key": "S3_BUCKET",
|
|
504
|
+
"value": "${S3_BUCKET}",
|
|
505
|
+
"description": "S3 bucket provisioned by Onebox Corestore for Cloudly's registry.",
|
|
506
|
+
"required": true
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"key": "S3_ACCESSKEY",
|
|
510
|
+
"value": "${S3_ACCESS_KEY}",
|
|
511
|
+
"description": "S3 access key provisioned by Onebox Corestore.",
|
|
512
|
+
"required": true
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"key": "S3_SECRETKEY",
|
|
516
|
+
"value": "${S3_SECRET_KEY}",
|
|
517
|
+
"description": "S3 secret key provisioned by Onebox Corestore.",
|
|
518
|
+
"required": true
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
"platformRequirements": {
|
|
522
|
+
"mongodb": true,
|
|
523
|
+
"s3": true
|
|
524
|
+
},
|
|
525
|
+
"platformOidc": {
|
|
526
|
+
"redirectPath": "/auth/oidc/callback",
|
|
527
|
+
"roles": [
|
|
528
|
+
{
|
|
529
|
+
"id": "admin",
|
|
530
|
+
"label": "Administrator",
|
|
531
|
+
"description": "Full Cloudly administration for this hosted instance."
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"id": "user",
|
|
535
|
+
"label": "User",
|
|
536
|
+
"description": "Standard authenticated Cloudly access for this hosted instance."
|
|
537
|
+
}
|
|
538
|
+
],
|
|
539
|
+
"environmentVariables": {
|
|
540
|
+
"issuerUrl": "SERVEZONE_PLATFORM_OIDC_ISSUER",
|
|
541
|
+
"clientId": "SERVEZONE_PLATFORM_OIDC_CLIENT_ID",
|
|
542
|
+
"clientSecret": "SERVEZONE_PLATFORM_OIDC_CLIENT_SECRET",
|
|
543
|
+
"redirectUri": "SERVEZONE_PLATFORM_OIDC_REDIRECT_URI",
|
|
544
|
+
"audience": "SERVEZONE_PLATFORM_OIDC_AUDIENCE"
|
|
545
|
+
},
|
|
546
|
+
"clientAuthenticationMethod": "client_secret_basic"
|
|
547
|
+
},
|
|
548
|
+
"minOneboxVersion": "7.1.0",
|
|
549
|
+
"changelog": "Upgrade Cloudly to 17.0.0 and run the Onebox-coordinated offline migration from legacy SecretGroup and SecretBundle persistence into the encrypted v2 secret runtime.",
|
|
550
|
+
"breaking": true,
|
|
551
|
+
"requiresManualReview": true,
|
|
552
|
+
"migrationRequired": true,
|
|
553
|
+
"backupBeforeUpgrade": true,
|
|
554
|
+
"healthCheck": {
|
|
555
|
+
"path": "/status",
|
|
556
|
+
"port": 3000,
|
|
557
|
+
"expectedStatus": 200
|
|
558
|
+
}
|
|
559
|
+
},
|
|
424
560
|
"apps/ghost/app.json": {
|
|
425
561
|
"id": "ghost",
|
|
426
562
|
"name": "Ghost",
|
|
@@ -806,24 +942,22 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
806
942
|
},
|
|
807
943
|
{
|
|
808
944
|
"key": "BAO_STATIC_SEAL_KEY",
|
|
809
|
-
"description": "Cloudly generates 32 random bytes and
|
|
945
|
+
"description": "Cloudly generates 32 random bytes and delivers them only to the OpenBao static-seal key file.",
|
|
810
946
|
"required": true,
|
|
811
947
|
"secret": true,
|
|
948
|
+
"delivery": {
|
|
949
|
+
"type": "file",
|
|
950
|
+
"targetPath": "/run/secrets/openbao-static-seal-key",
|
|
951
|
+
"uid": 100,
|
|
952
|
+
"gid": 1000,
|
|
953
|
+
"mode": 256
|
|
954
|
+
},
|
|
812
955
|
"generate": {
|
|
813
|
-
"
|
|
956
|
+
"encoding": "hex",
|
|
814
957
|
"bytes": 32
|
|
815
958
|
}
|
|
816
959
|
}
|
|
817
960
|
],
|
|
818
|
-
"secretFiles": [
|
|
819
|
-
{
|
|
820
|
-
"sourceKey": "BAO_STATIC_SEAL_KEY",
|
|
821
|
-
"targetPath": "/run/secrets/openbao-static-seal-key",
|
|
822
|
-
"uid": 100,
|
|
823
|
-
"gid": 1000,
|
|
824
|
-
"mode": 256
|
|
825
|
-
}
|
|
826
|
-
],
|
|
827
961
|
"volumes": [
|
|
828
962
|
{
|
|
829
963
|
"name": "openbao-data",
|
|
@@ -1244,7 +1378,7 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
1244
1378
|
},
|
|
1245
1379
|
"appstore.json": {
|
|
1246
1380
|
"schemaVersion": 1,
|
|
1247
|
-
"updatedAt": "2026-
|
|
1381
|
+
"updatedAt": "2026-08-04T00:00:00Z",
|
|
1248
1382
|
"apps": [
|
|
1249
1383
|
{
|
|
1250
1384
|
"id": "nginx",
|
|
@@ -1452,7 +1586,7 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
1452
1586
|
"description": "Multi-node serve.zone control plane for clusters, workload services, domains, and deployments.",
|
|
1453
1587
|
"category": "Dev Tools",
|
|
1454
1588
|
"iconName": "server",
|
|
1455
|
-
"latestVersion": "1.1.
|
|
1589
|
+
"latestVersion": "1.1.2",
|
|
1456
1590
|
"tags": [
|
|
1457
1591
|
"serve.zone",
|
|
1458
1592
|
"control-plane",
|
package/ts_client/storage.ts
CHANGED
|
@@ -11,7 +11,7 @@ const storageFeatureIds = interfaces.appstore.appStoreStorageFeatureIds;
|
|
|
11
11
|
const knownStorageFeatureIds = new Set<string>(Object.values(storageFeatureIds));
|
|
12
12
|
const storageClassKeyPattern = /^[A-Za-z][A-Za-z0-9-]{0,62}$/;
|
|
13
13
|
const storageRequestIdPattern = /^[a-z][a-z0-9-]{0,62}$/;
|
|
14
|
-
const environmentKeyPattern = /^[A-
|
|
14
|
+
const environmentKeyPattern = /^[A-Z_][A-Z0-9_]{0,252}$/;
|
|
15
15
|
const capacityPattern = /^([1-9][0-9]*)(KiB|MiB|GiB|TiB)$/;
|
|
16
16
|
const capacityMultipliers = {
|
|
17
17
|
KiB: 1024n,
|
|
@@ -26,7 +26,6 @@ const appStoreVersionConfigKeys = [
|
|
|
26
26
|
'image',
|
|
27
27
|
'port',
|
|
28
28
|
'containerArgs',
|
|
29
|
-
'secretFiles',
|
|
30
29
|
'targetPorts',
|
|
31
30
|
'domains',
|
|
32
31
|
'publicPortMappings',
|
|
@@ -55,6 +54,12 @@ const appStoreVersionConfigKeys = [
|
|
|
55
54
|
|
|
56
55
|
type TPlainRecord = Record<string, unknown>;
|
|
57
56
|
|
|
57
|
+
interface IAppInputDestinations {
|
|
58
|
+
declaredEnvironmentKeys: Set<string>;
|
|
59
|
+
runtimeEnvironmentKeys: Set<string>;
|
|
60
|
+
declaredSecretPaths: Set<string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
export function parseStorageCapacityQuantity(
|
|
59
64
|
quantityArg: unknown,
|
|
60
65
|
labelArg = 'storage capacity',
|
|
@@ -116,6 +121,78 @@ export function normalizeAppStoreStorageConfig(
|
|
|
116
121
|
return normalized;
|
|
117
122
|
}
|
|
118
123
|
|
|
124
|
+
function collectAppInputDestinations(
|
|
125
|
+
configArg: TAppStoreVersionConfig,
|
|
126
|
+
labelArg: string,
|
|
127
|
+
): IAppInputDestinations {
|
|
128
|
+
const declaredEnvironmentKeys = new Set<string>();
|
|
129
|
+
const runtimeEnvironmentOwners = new Map<string, string>();
|
|
130
|
+
const secretFileOwners = new Map<string, string>();
|
|
131
|
+
|
|
132
|
+
const addDestination = (
|
|
133
|
+
ownersArg: Map<string, string>,
|
|
134
|
+
destinationArg: string,
|
|
135
|
+
ownerArg: string,
|
|
136
|
+
kindArg: 'environment' | 'secret file',
|
|
137
|
+
): void => {
|
|
138
|
+
const existingOwner = ownersArg.get(destinationArg);
|
|
139
|
+
if (existingOwner) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Invalid ${labelArg}: ${kindArg} destination '${destinationArg}' is used by both ${existingOwner} and ${ownerArg}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
ownersArg.set(destinationArg, ownerArg);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
for (const declaration of configArg.envVars || []) {
|
|
148
|
+
declaredEnvironmentKeys.add(declaration.key);
|
|
149
|
+
const owner = `envVars.${declaration.key}`;
|
|
150
|
+
if (declaration.secret === true) {
|
|
151
|
+
if (declaration.delivery.type === 'launcher-environment') {
|
|
152
|
+
addDestination(
|
|
153
|
+
runtimeEnvironmentOwners,
|
|
154
|
+
declaration.delivery.variableName,
|
|
155
|
+
owner,
|
|
156
|
+
'environment',
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
addDestination(
|
|
160
|
+
secretFileOwners,
|
|
161
|
+
declaration.delivery.targetPath,
|
|
162
|
+
owner,
|
|
163
|
+
'secret file',
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
addDestination(runtimeEnvironmentOwners, declaration.key, owner, 'environment');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const platformOidc = configArg.platformOidc as unknown;
|
|
172
|
+
if (platformOidc && typeof platformOidc === 'object' && !Array.isArray(platformOidc)) {
|
|
173
|
+
const environmentVariables = (platformOidc as TPlainRecord).environmentVariables;
|
|
174
|
+
if (environmentVariables && typeof environmentVariables === 'object'
|
|
175
|
+
&& !Array.isArray(environmentVariables)) {
|
|
176
|
+
for (const [field, destination] of Object.entries(environmentVariables)) {
|
|
177
|
+
if (typeof destination === 'string') {
|
|
178
|
+
addDestination(
|
|
179
|
+
runtimeEnvironmentOwners,
|
|
180
|
+
destination,
|
|
181
|
+
`platformOidc.${field}`,
|
|
182
|
+
'environment',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
declaredEnvironmentKeys,
|
|
191
|
+
runtimeEnvironmentKeys: new Set(runtimeEnvironmentOwners.keys()),
|
|
192
|
+
declaredSecretPaths: new Set(secretFileOwners.keys()),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
119
196
|
function validateAndNormalizeStorageConfig(
|
|
120
197
|
configArg: TAppStoreVersionConfig,
|
|
121
198
|
labelArg: string,
|
|
@@ -125,6 +202,13 @@ function validateAndNormalizeStorageConfig(
|
|
|
125
202
|
appStoreVersionConfigKeys,
|
|
126
203
|
labelArg,
|
|
127
204
|
);
|
|
205
|
+
const environmentErrors = interfaces.appstore.validateAppStoreEnvironmentDeclarations(
|
|
206
|
+
configArg.envVars,
|
|
207
|
+
);
|
|
208
|
+
if (environmentErrors.length > 0) {
|
|
209
|
+
throw new Error(`Invalid ${labelArg}: ${environmentErrors.join('; ')}`);
|
|
210
|
+
}
|
|
211
|
+
const appInputDestinations = collectAppInputDestinations(configArg, labelArg);
|
|
128
212
|
validatePlatformRequirements(configArg.platformRequirements, labelArg);
|
|
129
213
|
const explicitClassInput = configArg.storageClasses;
|
|
130
214
|
const explicitRequestInput = configArg.storageRequests;
|
|
@@ -140,7 +224,7 @@ function validateAndNormalizeStorageConfig(
|
|
|
140
224
|
const explicitRequests = validateExplicitStorageRequests(
|
|
141
225
|
explicitRequestInput,
|
|
142
226
|
storageClasses,
|
|
143
|
-
|
|
227
|
+
appInputDestinations,
|
|
144
228
|
labelArg,
|
|
145
229
|
);
|
|
146
230
|
const requiredFeatureIds = validateFeatureDeclaration(configArg, explicitRequests, labelArg);
|
|
@@ -227,7 +311,7 @@ function validateAndNormalizeStorageConfig(
|
|
|
227
311
|
accessMode: 'readWrite',
|
|
228
312
|
reclaimPolicy: 'retain',
|
|
229
313
|
delivery: {
|
|
230
|
-
type: 'environment',
|
|
314
|
+
type: 'launcher-environment',
|
|
231
315
|
keys: {
|
|
232
316
|
endpoint: 'S3_ENDPOINT',
|
|
233
317
|
bucket: 'S3_BUCKET',
|
|
@@ -247,10 +331,14 @@ function validateAndNormalizeStorageConfig(
|
|
|
247
331
|
};
|
|
248
332
|
}
|
|
249
333
|
|
|
250
|
-
validateAllFilesystemMountsAndSecretTargets(
|
|
334
|
+
validateAllFilesystemMountsAndSecretTargets(
|
|
335
|
+
storageRequests,
|
|
336
|
+
appInputDestinations.declaredSecretPaths,
|
|
337
|
+
labelArg,
|
|
338
|
+
);
|
|
251
339
|
|
|
252
340
|
return {
|
|
253
|
-
schemaVersion:
|
|
341
|
+
schemaVersion: 2,
|
|
254
342
|
storageClasses,
|
|
255
343
|
storageRequests,
|
|
256
344
|
requestOrigins: Object.fromEntries(requestOriginEntries),
|
|
@@ -290,7 +378,7 @@ function validateStorageClasses(
|
|
|
290
378
|
if (preferred[requirementKey] !== undefined
|
|
291
379
|
&& preferred[requirementKey] !== required[requirementKey]) {
|
|
292
380
|
throw new Error(
|
|
293
|
-
`Invalid ${classLabel}: preferred.${requirementKey} conflicts with required.${requirementKey}`,
|
|
381
|
+
`Invalid ${classLabel}: preferred.${String(requirementKey)} conflicts with required.${String(requirementKey)}`,
|
|
294
382
|
);
|
|
295
383
|
}
|
|
296
384
|
}
|
|
@@ -349,7 +437,7 @@ function validateClassRequirement(
|
|
|
349
437
|
function validateExplicitStorageRequests(
|
|
350
438
|
requestsArg: TAppStoreVersionConfig['storageRequests'],
|
|
351
439
|
classesArg: Record<string, interfaces.appstore.IAppStoreStorageClass>,
|
|
352
|
-
|
|
440
|
+
appInputDestinationsArg: IAppInputDestinations,
|
|
353
441
|
labelArg: string,
|
|
354
442
|
): interfaces.appstore.TAppStoreStorageRequest[] {
|
|
355
443
|
if (requestsArg === undefined) return [];
|
|
@@ -360,22 +448,12 @@ function validateExplicitStorageRequests(
|
|
|
360
448
|
const requestIds = new Set<string>();
|
|
361
449
|
const usedClassKeys = new Set<string>();
|
|
362
450
|
const deliveryEnvironmentKeys = new Set<string>();
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
declaredEnvironmentKeys.add(secretFile.sourceKey);
|
|
369
|
-
}
|
|
370
|
-
for (const oidcEnvironmentKey of Object.values(
|
|
371
|
-
configArg.platformOidc?.environmentVariables || {},
|
|
372
|
-
)) {
|
|
373
|
-
declaredEnvironmentKeys.add(oidcEnvironmentKey);
|
|
374
|
-
}
|
|
451
|
+
const {
|
|
452
|
+
declaredEnvironmentKeys,
|
|
453
|
+
runtimeEnvironmentKeys,
|
|
454
|
+
declaredSecretPaths,
|
|
455
|
+
} = appInputDestinationsArg;
|
|
375
456
|
const deliverySecretPaths = new Set<string>();
|
|
376
|
-
const declaredSecretPaths = new Set((configArg.secretFiles || []).map(
|
|
377
|
-
(entryArg) => entryArg.targetPath,
|
|
378
|
-
));
|
|
379
457
|
|
|
380
458
|
const requests = requestsArg.map((candidateArg, indexArg) => {
|
|
381
459
|
const requestLabel = `${labelArg} storageRequests[${indexArg}]`;
|
|
@@ -462,6 +540,7 @@ function validateExplicitStorageRequests(
|
|
|
462
540
|
candidate.delivery,
|
|
463
541
|
requestLabel,
|
|
464
542
|
declaredEnvironmentKeys,
|
|
543
|
+
runtimeEnvironmentKeys,
|
|
465
544
|
deliveryEnvironmentKeys,
|
|
466
545
|
declaredSecretPaths,
|
|
467
546
|
deliverySecretPaths,
|
|
@@ -552,13 +631,14 @@ function validateObjectStorageDelivery(
|
|
|
552
631
|
deliveryArg: unknown,
|
|
553
632
|
requestLabelArg: string,
|
|
554
633
|
declaredEnvironmentKeysArg: Set<string>,
|
|
634
|
+
runtimeEnvironmentKeysArg: Set<string>,
|
|
555
635
|
deliveryEnvironmentKeysArg: Set<string>,
|
|
556
636
|
declaredSecretPathsArg: Set<string>,
|
|
557
637
|
deliverySecretPathsArg: Set<string>,
|
|
558
638
|
): interfaces.platform.TObjectStorageDelivery {
|
|
559
639
|
const deliveryLabel = `${requestLabelArg}.delivery`;
|
|
560
640
|
const delivery = assertPlainRecord(deliveryArg, deliveryLabel);
|
|
561
|
-
if (delivery.type === 'environment') {
|
|
641
|
+
if (delivery.type === 'launcher-environment') {
|
|
562
642
|
assertExactKeys(delivery, ['type', 'keys'], deliveryLabel);
|
|
563
643
|
const keys = assertPlainRecord(delivery.keys, `${deliveryLabel}.keys`);
|
|
564
644
|
assertExactKeys(keys, [
|
|
@@ -578,7 +658,8 @@ function validateObjectStorageDelivery(
|
|
|
578
658
|
if (localKeys.has(targetKeyArg)) {
|
|
579
659
|
throw new Error(`Invalid ${deliveryLabel}: environment key '${targetKeyArg}' is duplicated`);
|
|
580
660
|
}
|
|
581
|
-
if (declaredEnvironmentKeysArg.has(targetKeyArg)
|
|
661
|
+
if (declaredEnvironmentKeysArg.has(targetKeyArg)
|
|
662
|
+
|| runtimeEnvironmentKeysArg.has(targetKeyArg)) {
|
|
582
663
|
throw new Error(
|
|
583
664
|
`Invalid ${deliveryLabel}: environment key '${targetKeyArg}' collides with an app runtime input`,
|
|
584
665
|
);
|
|
@@ -590,37 +671,49 @@ function validateObjectStorageDelivery(
|
|
|
590
671
|
deliveryEnvironmentKeysArg.add(targetKeyArg);
|
|
591
672
|
}
|
|
592
673
|
return {
|
|
593
|
-
type: 'environment',
|
|
674
|
+
type: 'launcher-environment',
|
|
594
675
|
keys: { ...keys },
|
|
595
|
-
} as interfaces.platform.
|
|
676
|
+
} as interfaces.platform.IObjectStorageLauncherEnvironmentDelivery;
|
|
596
677
|
}
|
|
597
678
|
|
|
598
|
-
if (delivery.type === '
|
|
599
|
-
assertExactKeys(delivery, ['type', 'targetPath', 'format'], deliveryLabel);
|
|
679
|
+
if (delivery.type === 'file') {
|
|
680
|
+
assertExactKeys(delivery, ['type', 'targetPath', 'format', 'uid', 'gid', 'mode'], deliveryLabel);
|
|
600
681
|
if (delivery.format !== 'servezone-object-storage-v1') {
|
|
601
682
|
throw new Error(`Invalid ${deliveryLabel}: secret file format is not supported`);
|
|
602
683
|
}
|
|
603
684
|
if (typeof delivery.targetPath !== 'string'
|
|
604
685
|
|| interfaces.data.normalizeServiceAbsolutePath(delivery.targetPath) !== delivery.targetPath
|
|
605
|
-
|| !/^\/run\/secrets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(delivery.targetPath)
|
|
606
|
-
|| delivery.targetPath === '/run/secrets/secret.json') {
|
|
686
|
+
|| !/^\/run\/secrets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(delivery.targetPath)) {
|
|
607
687
|
throw new Error(`Invalid ${deliveryLabel}: targetPath must be a unique canonical file below /run/secrets`);
|
|
608
688
|
}
|
|
609
689
|
if (declaredSecretPathsArg.has(delivery.targetPath)) {
|
|
610
|
-
throw new Error(`Invalid ${deliveryLabel}: targetPath collides with
|
|
690
|
+
throw new Error(`Invalid ${deliveryLabel}: targetPath collides with an app secret delivery`);
|
|
611
691
|
}
|
|
612
692
|
if (deliverySecretPathsArg.has(delivery.targetPath)) {
|
|
613
693
|
throw new Error(`Invalid ${deliveryLabel}: targetPath is used by another storage request`);
|
|
614
694
|
}
|
|
695
|
+
for (const identityField of ['uid', 'gid'] as const) {
|
|
696
|
+
if (!Number.isSafeInteger(delivery[identityField])
|
|
697
|
+
|| (delivery[identityField] as number) < 0
|
|
698
|
+
|| (delivery[identityField] as number) > 2_147_483_647) {
|
|
699
|
+
throw new Error(`Invalid ${deliveryLabel}: ${identityField} must be a bounded numeric identity`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (delivery.mode !== 0o400 && delivery.mode !== 0o440) {
|
|
703
|
+
throw new Error(`Invalid ${deliveryLabel}: mode must be 0400 or 0440`);
|
|
704
|
+
}
|
|
615
705
|
deliverySecretPathsArg.add(delivery.targetPath);
|
|
616
706
|
return {
|
|
617
|
-
type: '
|
|
707
|
+
type: 'file',
|
|
618
708
|
targetPath: delivery.targetPath,
|
|
619
709
|
format: 'servezone-object-storage-v1',
|
|
710
|
+
uid: delivery.uid as number,
|
|
711
|
+
gid: delivery.gid as number,
|
|
712
|
+
mode: delivery.mode as 0o400 | 0o440,
|
|
620
713
|
};
|
|
621
714
|
}
|
|
622
715
|
|
|
623
|
-
throw new Error(`Invalid ${deliveryLabel}: type must be environment or
|
|
716
|
+
throw new Error(`Invalid ${deliveryLabel}: type must be launcher-environment or file`);
|
|
624
717
|
}
|
|
625
718
|
|
|
626
719
|
function validateFeatureDeclaration(
|
|
@@ -630,18 +723,18 @@ function validateFeatureDeclaration(
|
|
|
630
723
|
): TAppStoreStorageFeatureId[] {
|
|
631
724
|
const requiredFeatures = new Set<TAppStoreStorageFeatureId>();
|
|
632
725
|
if (explicitRequestsArg.length > 0) {
|
|
633
|
-
requiredFeatures.add(storageFeatureIds.
|
|
726
|
+
requiredFeatures.add(storageFeatureIds.bindingsV2);
|
|
634
727
|
}
|
|
635
728
|
if (explicitRequestsArg.some((requestArg) => requestArg.kind === 'filesystem')) {
|
|
636
729
|
requiredFeatures.add(storageFeatureIds.filesystemV1);
|
|
637
730
|
}
|
|
638
731
|
if (explicitRequestsArg.some((requestArg) => requestArg.kind === 'objectStorage')) {
|
|
639
|
-
requiredFeatures.add(storageFeatureIds.
|
|
732
|
+
requiredFeatures.add(storageFeatureIds.objectStorageV2);
|
|
640
733
|
}
|
|
641
734
|
if (explicitRequestsArg.some((requestArg) => (
|
|
642
|
-
requestArg.kind === 'objectStorage' && requestArg.delivery.type === '
|
|
735
|
+
requestArg.kind === 'objectStorage' && requestArg.delivery.type === 'file'
|
|
643
736
|
))) {
|
|
644
|
-
requiredFeatures.add(storageFeatureIds.
|
|
737
|
+
requiredFeatures.add(storageFeatureIds.objectStorageFileV2);
|
|
645
738
|
}
|
|
646
739
|
|
|
647
740
|
const declaredFeatures = configArg.requiresFeatures || [];
|
|
@@ -757,7 +850,7 @@ function createLegacyVolumeRequestId(
|
|
|
757
850
|
|
|
758
851
|
function validateAllFilesystemMountsAndSecretTargets(
|
|
759
852
|
requestsArg: interfaces.appstore.TAppStoreStorageRequest[],
|
|
760
|
-
|
|
853
|
+
declaredSecretPathsArg: Set<string>,
|
|
761
854
|
labelArg: string,
|
|
762
855
|
): void {
|
|
763
856
|
const mountPaths: string[] = [];
|
|
@@ -766,22 +859,15 @@ function validateAllFilesystemMountsAndSecretTargets(
|
|
|
766
859
|
if (requestArg.kind === 'filesystem') {
|
|
767
860
|
assertNoPathCollision(requestArg.mountPath, mountPaths, `${labelArg} request '${requestArg.id}'`);
|
|
768
861
|
mountPaths.push(requestArg.mountPath);
|
|
769
|
-
} else if (requestArg.delivery.type === '
|
|
862
|
+
} else if (requestArg.delivery.type === 'file') {
|
|
770
863
|
objectSecretPaths.push(requestArg.delivery.targetPath);
|
|
771
864
|
}
|
|
772
865
|
}
|
|
773
|
-
for (const secretPathArg of objectSecretPaths) {
|
|
866
|
+
for (const secretPathArg of [...declaredSecretPathsArg, ...objectSecretPaths]) {
|
|
774
867
|
if (mountPaths.some((mountPathArg) => pathsOverlap(secretPathArg, mountPathArg))) {
|
|
775
|
-
throw new Error(`Invalid ${labelArg}:
|
|
868
|
+
throw new Error(`Invalid ${labelArg}: secret target '${secretPathArg}' overlaps a filesystem mount`);
|
|
776
869
|
}
|
|
777
870
|
}
|
|
778
|
-
const secretFileErrors = interfaces.data.validateServiceSecretFiles(
|
|
779
|
-
configArg.secretFiles,
|
|
780
|
-
mountPaths,
|
|
781
|
-
);
|
|
782
|
-
if (secretFileErrors.length > 0) {
|
|
783
|
-
throw new Error(`Invalid ${labelArg}: ${secretFileErrors.join('; ')}`);
|
|
784
|
-
}
|
|
785
871
|
}
|
|
786
872
|
|
|
787
873
|
function assertNoPathCollision(pathArg: string, pathsArg: string[], labelArg: string): void {
|
package/ts_client/types.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface IAppStoreStorageLegacyCompatibility {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export interface INormalizedAppStoreStorageConfig {
|
|
36
|
-
schemaVersion:
|
|
36
|
+
schemaVersion: 2;
|
|
37
37
|
storageClasses: Record<string, TAppStoreStorageClass>;
|
|
38
38
|
storageRequests: TAppStoreStorageRequest[];
|
|
39
39
|
requestOrigins: Record<string, TAppStoreStorageRequestOrigin>;
|