@serve.zone/appstore 1.4.1 → 2.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/apps/cloudly/app.json +2 -2
- package/apps/cloudly/versions/1.1.1/config.json +120 -0
- package/apps/openbao/versions/1.0.0/config.json +9 -11
- package/appstore.json +1 -1
- package/changelog.md +18 -0
- package/dist_ts_client/classes.appstoreresolver.js +4 -34
- package/dist_ts_client/data.generated.js +139 -15
- 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 +138 -14
- package/ts_client/storage.ts +135 -49
- package/ts_client/types.ts +1 -1
|
@@ -34,10 +34,11 @@ 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.1",
|
|
38
38
|
"versions": [
|
|
39
39
|
"1.0.0",
|
|
40
|
-
"1.1.0"
|
|
40
|
+
"1.1.0",
|
|
41
|
+
"1.1.1"
|
|
41
42
|
],
|
|
42
43
|
"maintainer": "serve.zone",
|
|
43
44
|
"links": {
|
|
@@ -295,6 +296,131 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
295
296
|
},
|
|
296
297
|
"minOneboxVersion": "2.2.0"
|
|
297
298
|
},
|
|
299
|
+
"apps/cloudly/versions/1.1.1/config.json": {
|
|
300
|
+
"image": "code.foss.global/serve.zone/cloudly@sha256:510b3edb65eed00d3edce4d482f90c864b26b759e3c8f7605109516791fb47f9",
|
|
301
|
+
"targetPorts": [
|
|
302
|
+
{
|
|
303
|
+
"name": "web",
|
|
304
|
+
"port": 3000,
|
|
305
|
+
"protocol": "http",
|
|
306
|
+
"default": true
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
"envVars": [
|
|
310
|
+
{
|
|
311
|
+
"key": "SERVEZONE_ENVIRONMENT",
|
|
312
|
+
"value": "production",
|
|
313
|
+
"description": "Cloudly runtime environment.",
|
|
314
|
+
"required": true
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"key": "SERVEZONE_PUBLIC_ORIGIN",
|
|
318
|
+
"value": "https://${SERVICE_DOMAIN}",
|
|
319
|
+
"description": "Canonical public Cloudly origin including protocol and optional public port.",
|
|
320
|
+
"required": true
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"key": "SERVEZONE_LISTENPORT",
|
|
324
|
+
"value": "3000",
|
|
325
|
+
"description": "Internal Cloudly HTTP port inside the container.",
|
|
326
|
+
"required": true
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"key": "SERVEZONE_SSLMODE",
|
|
330
|
+
"value": "external",
|
|
331
|
+
"description": "Use external TLS termination through Onebox or dcrouter.",
|
|
332
|
+
"required": true
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"key": "MONGODB_URL",
|
|
336
|
+
"value": "${MONGODB_URI}",
|
|
337
|
+
"description": "Authenticated MongoDB connection URL provisioned by Onebox.",
|
|
338
|
+
"required": true
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"key": "MONGODB_NAME",
|
|
342
|
+
"value": "${MONGODB_DATABASE}",
|
|
343
|
+
"description": "MongoDB database name provisioned by Onebox.",
|
|
344
|
+
"required": true
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"key": "MONGODB_USER",
|
|
348
|
+
"value": "${MONGODB_USERNAME}",
|
|
349
|
+
"description": "MongoDB username provisioned by Onebox.",
|
|
350
|
+
"required": true
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"key": "MONGODB_PASS",
|
|
354
|
+
"value": "${MONGODB_PASSWORD}",
|
|
355
|
+
"description": "MongoDB password provisioned by Onebox.",
|
|
356
|
+
"required": true
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"key": "S3_ENDPOINT",
|
|
360
|
+
"value": "onebox-minio",
|
|
361
|
+
"description": "S3 endpoint host for the MinIO service provisioned by Onebox.",
|
|
362
|
+
"required": true
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"key": "S3_PORT",
|
|
366
|
+
"value": "9000",
|
|
367
|
+
"description": "S3 endpoint port for the MinIO service provisioned by Onebox.",
|
|
368
|
+
"required": true
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"key": "S3_USESSL",
|
|
372
|
+
"value": "false",
|
|
373
|
+
"description": "Use plain HTTP for internal MinIO traffic on the Onebox network.",
|
|
374
|
+
"required": true
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"key": "S3_BUCKET",
|
|
378
|
+
"value": "${S3_BUCKET}",
|
|
379
|
+
"description": "S3 bucket provisioned by Onebox for Cloudly's registry.",
|
|
380
|
+
"required": true
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"key": "S3_ACCESSKEY",
|
|
384
|
+
"value": "${S3_ACCESS_KEY}",
|
|
385
|
+
"description": "S3 access key provisioned by Onebox.",
|
|
386
|
+
"required": true
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"key": "S3_SECRETKEY",
|
|
390
|
+
"value": "${S3_SECRET_KEY}",
|
|
391
|
+
"description": "S3 secret key provisioned by Onebox.",
|
|
392
|
+
"required": true
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
"platformRequirements": {
|
|
396
|
+
"mongodb": true,
|
|
397
|
+
"s3": true
|
|
398
|
+
},
|
|
399
|
+
"platformOidc": {
|
|
400
|
+
"redirectPath": "/auth/oidc/callback",
|
|
401
|
+
"roles": [
|
|
402
|
+
{
|
|
403
|
+
"id": "admin",
|
|
404
|
+
"label": "Administrator",
|
|
405
|
+
"description": "Full Cloudly administration for this hosted instance."
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"id": "user",
|
|
409
|
+
"label": "User",
|
|
410
|
+
"description": "Standard authenticated Cloudly access for this hosted instance."
|
|
411
|
+
}
|
|
412
|
+
],
|
|
413
|
+
"environmentVariables": {
|
|
414
|
+
"issuerUrl": "SERVEZONE_PLATFORM_OIDC_ISSUER",
|
|
415
|
+
"clientId": "SERVEZONE_PLATFORM_OIDC_CLIENT_ID",
|
|
416
|
+
"clientSecret": "SERVEZONE_PLATFORM_OIDC_CLIENT_SECRET",
|
|
417
|
+
"redirectUri": "SERVEZONE_PLATFORM_OIDC_REDIRECT_URI",
|
|
418
|
+
"audience": "SERVEZONE_PLATFORM_OIDC_AUDIENCE"
|
|
419
|
+
},
|
|
420
|
+
"clientAuthenticationMethod": "client_secret_basic"
|
|
421
|
+
},
|
|
422
|
+
"minOneboxVersion": "2.2.0"
|
|
423
|
+
},
|
|
298
424
|
"apps/ghost/app.json": {
|
|
299
425
|
"id": "ghost",
|
|
300
426
|
"name": "Ghost",
|
|
@@ -680,24 +806,22 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
680
806
|
},
|
|
681
807
|
{
|
|
682
808
|
"key": "BAO_STATIC_SEAL_KEY",
|
|
683
|
-
"description": "Cloudly generates 32 random bytes and
|
|
809
|
+
"description": "Cloudly generates 32 random bytes and delivers them only to the OpenBao static-seal key file.",
|
|
684
810
|
"required": true,
|
|
685
811
|
"secret": true,
|
|
812
|
+
"delivery": {
|
|
813
|
+
"type": "file",
|
|
814
|
+
"targetPath": "/run/secrets/openbao-static-seal-key",
|
|
815
|
+
"uid": 100,
|
|
816
|
+
"gid": 1000,
|
|
817
|
+
"mode": 256
|
|
818
|
+
},
|
|
686
819
|
"generate": {
|
|
687
|
-
"
|
|
820
|
+
"encoding": "hex",
|
|
688
821
|
"bytes": 32
|
|
689
822
|
}
|
|
690
823
|
}
|
|
691
824
|
],
|
|
692
|
-
"secretFiles": [
|
|
693
|
-
{
|
|
694
|
-
"sourceKey": "BAO_STATIC_SEAL_KEY",
|
|
695
|
-
"targetPath": "/run/secrets/openbao-static-seal-key",
|
|
696
|
-
"uid": 100,
|
|
697
|
-
"gid": 1000,
|
|
698
|
-
"mode": 256
|
|
699
|
-
}
|
|
700
|
-
],
|
|
701
825
|
"volumes": [
|
|
702
826
|
{
|
|
703
827
|
"name": "openbao-data",
|
|
@@ -1326,7 +1450,7 @@ export const appStorePackageData: IAppStorePackageData = {
|
|
|
1326
1450
|
"description": "Multi-node serve.zone control plane for clusters, workload services, domains, and deployments.",
|
|
1327
1451
|
"category": "Dev Tools",
|
|
1328
1452
|
"iconName": "server",
|
|
1329
|
-
"latestVersion": "1.1.
|
|
1453
|
+
"latestVersion": "1.1.1",
|
|
1330
1454
|
"tags": [
|
|
1331
1455
|
"serve.zone",
|
|
1332
1456
|
"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>;
|