@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.
@@ -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.0",
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 stores them only in this service's secret bundle.",
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
- "type": "randomHex",
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.0",
1453
+ "latestVersion": "1.1.1",
1330
1454
  "tags": [
1331
1455
  "serve.zone",
1332
1456
  "control-plane",
@@ -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-Za-z_][A-Za-z0-9_]*$/;
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
- configArg,
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(storageRequests, configArg, labelArg);
334
+ validateAllFilesystemMountsAndSecretTargets(
335
+ storageRequests,
336
+ appInputDestinations.declaredSecretPaths,
337
+ labelArg,
338
+ );
251
339
 
252
340
  return {
253
- schemaVersion: 1,
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
- configArg: TAppStoreVersionConfig,
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 declaredEnvironmentKeys = new Set<string>();
364
- for (const envVar of configArg.envVars || []) {
365
- declaredEnvironmentKeys.add(envVar.key);
366
- }
367
- for (const secretFile of configArg.secretFiles || []) {
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.IObjectStorageEnvironmentDelivery;
676
+ } as interfaces.platform.IObjectStorageLauncherEnvironmentDelivery;
596
677
  }
597
678
 
598
- if (delivery.type === 'secretFile') {
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 secretFiles`);
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: 'secretFile',
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 secretFile`);
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.bindingsV1);
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.objectStorageV1);
732
+ requiredFeatures.add(storageFeatureIds.objectStorageV2);
640
733
  }
641
734
  if (explicitRequestsArg.some((requestArg) => (
642
- requestArg.kind === 'objectStorage' && requestArg.delivery.type === 'secretFile'
735
+ requestArg.kind === 'objectStorage' && requestArg.delivery.type === 'file'
643
736
  ))) {
644
- requiredFeatures.add(storageFeatureIds.objectStorageSecretFileV1);
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
- configArg: TAppStoreVersionConfig,
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 === 'secretFile') {
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}: object-storage secret target '${secretPathArg}' overlaps a filesystem mount`);
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 {
@@ -33,7 +33,7 @@ export interface IAppStoreStorageLegacyCompatibility {
33
33
  }
34
34
 
35
35
  export interface INormalizedAppStoreStorageConfig {
36
- schemaVersion: 1;
36
+ schemaVersion: 2;
37
37
  storageClasses: Record<string, TAppStoreStorageClass>;
38
38
  storageRequests: TAppStoreStorageRequest[];
39
39
  requestOrigins: Record<string, TAppStoreStorageRequestOrigin>;