@serve.zone/interfaces 32.26.0 → 32.27.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.
@@ -5,6 +5,7 @@ import type { IClusterNode, INodeAction, INodeMetricsSample, TNodeActionName } f
5
5
  import type { IDeployment } from '../data/deployment.js';
6
6
  import type { IIdentity } from '../data/user.js';
7
7
  import type { IPlatformObjectStorageEndpoint } from '../platform/objectstorage.js';
8
+ import type { IPlatformDatabaseEndpoint } from '../platform/database.js';
8
9
  import type {
9
10
  INodeCredentialMetadata,
10
11
  INodeCredentialMutationPreconditions,
@@ -315,6 +316,38 @@ export interface IReq_Any_Cloudly_SetClusterNodeCorestoreObjectStorageEndpoint e
315
316
  };
316
317
  }
317
318
 
319
+ /**
320
+ * An administrator states where a workload on one node reaches the node's Corestore database:
321
+ * `IClusterNode.data.corestoreDatabase`.
322
+ *
323
+ * A request of its own, separate from `setClusterNodeCorestoreEndpoint` and
324
+ * `setClusterNodeCorestoreObjectStorageEndpoint`: each sets one fact and never touches the others.
325
+ *
326
+ * Cloudly validates it with `validatePlatformDatabaseEndpoint` before it writes and takes the
327
+ * node's own real-write fence inside the writing transaction, as for the other two endpoints.
328
+ * `database: null` clears it, which is the same fact as never having set one: no database
329
+ * credential can be published for a binding on this node. A changed endpoint changes the binding
330
+ * request of every database binding placed on the node, so each is granted and published again,
331
+ * addressed to the new endpoint. The endpoint says where, never with what — no credential travels
332
+ * in this request or in the node document.
333
+ */
334
+ export interface IReq_Any_Cloudly_SetClusterNodeCorestoreDatabaseEndpoint extends plugins.typedrequestInterfaces.implementsTR<
335
+ plugins.typedrequestInterfaces.ITypedRequest,
336
+ IReq_Any_Cloudly_SetClusterNodeCorestoreDatabaseEndpoint
337
+ > {
338
+ method: 'setClusterNodeCorestoreDatabaseEndpoint';
339
+ request: {
340
+ identity: IIdentity;
341
+ nodeId: string;
342
+ /** The node's database endpoint, or `null` to state that it has none. */
343
+ database: IPlatformDatabaseEndpoint | null;
344
+ };
345
+ response: {
346
+ /** The node as it now stands, so a caller needs no second read to see what it set. */
347
+ node: IClusterNode;
348
+ };
349
+ }
350
+
318
351
  export interface IReq_Any_Cloudly_CreateNodeAction extends plugins.typedrequestInterfaces.implementsTR<
319
352
  plugins.typedrequestInterfaces.ITypedRequest,
320
353
  IReq_Any_Cloudly_CreateNodeAction
@@ -21,6 +21,11 @@ import {
21
21
  validateObjectStorageRetentionIntent,
22
22
  } from './platform/objectstorageretention.js';
23
23
  import { validatePlatformObjectStorageBucketName } from './platform/objectstorage.js';
24
+ import type { IPlatformDatabaseEndpoint } from './platform/database.js';
25
+ import {
26
+ formatPlatformDatabaseEndpointHost,
27
+ validatePlatformDatabaseEndpoint,
28
+ } from './platform/database.js';
24
29
  import {
25
30
  canonicalizeStrictJson,
26
31
  createCanonicalJsonSha256Hex,
@@ -415,6 +420,14 @@ interface ICorestoreCredentialBindingRequestBase {
415
420
  export type TCorestoreCredentialBindingRequest =
416
421
  | ICorestoreCredentialBindingRequestBase & {
417
422
  capability: 'database';
423
+ /**
424
+ * Where a workload reaches the database: the `corestoreDatabase` of the node the binding is
425
+ * placed on. Corestore addresses the binding's connection values to it, and it is part of
426
+ * the request digest, so a changed endpoint is a changed request that is granted and
427
+ * published again. A request without it names no address a workload can reach; whether one
428
+ * is admitted is the provider's decision, never a default this contract supplies.
429
+ */
430
+ endpoint?: IPlatformDatabaseEndpoint;
418
431
  }
419
432
  | ICorestoreCredentialBindingRequestBase & {
420
433
  capability: 'objectstorage';
@@ -441,7 +454,7 @@ export const validateCorestoreCredentialBindingRequest = (
441
454
  'environment',
442
455
  ];
443
456
  if (requestArg.capability === 'database') {
444
- if (!hasExactKeys(requestArg, baseKeys)) {
457
+ if (!hasExactKeys(requestArg, baseKeys, ['endpoint'])) {
445
458
  return ['corestore database credential binding request must use its exact schema'];
446
459
  }
447
460
  } else if (requestArg.capability === 'objectstorage') {
@@ -462,6 +475,12 @@ export const validateCorestoreCredentialBindingRequest = (
462
475
  || requestArg.environment !== corestoreCredentialEnvironment) {
463
476
  errors.push('corestore credential binding request constants must be canonical');
464
477
  }
478
+ if (requestArg.capability === 'database' && requestArg.endpoint !== undefined) {
479
+ errors.push(...validatePlatformDatabaseEndpoint(
480
+ requestArg.endpoint,
481
+ 'corestore database credential binding request endpoint',
482
+ ));
483
+ }
465
484
  if (requestArg.capability === 'objectstorage') {
466
485
  errors.push(...validatePlatformObjectStorageBucketName(
467
486
  requestArg.bucketName,
@@ -489,6 +508,9 @@ export const createCorestoreCredentialBindingRequestDigestInput = (
489
508
  providerConfigId: requestArg.providerConfigId,
490
509
  managementScope: requestArg.managementScope,
491
510
  environment: requestArg.environment,
511
+ ...(requestArg.capability === 'database' && requestArg.endpoint
512
+ ? { endpoint: { host: requestArg.endpoint.host, port: requestArg.endpoint.port } }
513
+ : {}),
492
514
  ...(requestArg.capability === 'objectstorage'
493
515
  ? {
494
516
  bucketName: requestArg.bucketName,
@@ -727,6 +749,91 @@ export const validateCorestoreDatabaseCredentialMaterial = (
727
749
  return errors;
728
750
  };
729
751
 
752
+ /** What a database binding's connection values are made of, besides where the database is. */
753
+ export interface ICorestoreDatabaseCredentialIdentity {
754
+ databaseName: string;
755
+ username: string;
756
+ password: string;
757
+ }
758
+
759
+ /**
760
+ * The connection URI of one database binding, addressed to `endpointArg`:
761
+ * `mongodb://<username>:<password>@<host>:<port>/<databaseName>?authSource=<databaseName>&directConnection=true`,
762
+ * the username, password and database name URI-component encoded, an IPv6 host in brackets.
763
+ * This is the one form Corestore writes and Cloudly checks, so the two can never disagree.
764
+ */
765
+ export const createCorestoreDatabaseUri = (
766
+ endpointArg: IPlatformDatabaseEndpoint,
767
+ identityArg: ICorestoreDatabaseCredentialIdentity,
768
+ ): string => {
769
+ const endpointErrors = validatePlatformDatabaseEndpoint(endpointArg);
770
+ if (endpointErrors.length > 0) throw new Error(endpointErrors[0]);
771
+ const query = new URLSearchParams({
772
+ authSource: identityArg.databaseName,
773
+ directConnection: 'true',
774
+ });
775
+ return `mongodb://${encodeURIComponent(identityArg.username)}:`
776
+ + `${encodeURIComponent(identityArg.password)}@`
777
+ + `${formatPlatformDatabaseEndpointHost(endpointArg.host)}:${endpointArg.port}`
778
+ + `/${encodeURIComponent(identityArg.databaseName)}?${query.toString()}`;
779
+ };
780
+
781
+ /**
782
+ * The eleven connection values of one database binding, addressed to `endpointArg`: the three URI
783
+ * aliases from `createCorestoreDatabaseUri()`, `MONGODB_HOST` the endpoint host without brackets,
784
+ * `MONGODB_PORT` its port in decimal, and the database, username and password under both alias
785
+ * families. Throws the first reason the result would not be valid material; nothing is defaulted.
786
+ */
787
+ export const createCorestoreDatabaseCredentialValues = (
788
+ endpointArg: IPlatformDatabaseEndpoint,
789
+ identityArg: ICorestoreDatabaseCredentialIdentity,
790
+ ): TCorestoreDatabaseCredentialValues => {
791
+ const uri = createCorestoreDatabaseUri(endpointArg, identityArg);
792
+ const values: TCorestoreDatabaseCredentialValues = {
793
+ MONGODB_URI: uri,
794
+ MONGODB_URL: uri,
795
+ MONGO_URL: uri,
796
+ MONGODB_HOST: endpointArg.host,
797
+ MONGODB_PORT: String(endpointArg.port),
798
+ MONGODB_DATABASE: identityArg.databaseName,
799
+ MONGO_DBNAME: identityArg.databaseName,
800
+ MONGODB_USERNAME: identityArg.username,
801
+ MONGO_DBUSER: identityArg.username,
802
+ MONGODB_PASSWORD: identityArg.password,
803
+ MONGO_DBPASS: identityArg.password,
804
+ };
805
+ const errors = validateCorestoreDatabaseCredentialMaterial({ capability: 'database', values });
806
+ if (errors.length > 0) throw new Error(errors[0]);
807
+ return values;
808
+ };
809
+
810
+ /**
811
+ * Validates database material and that it is addressed to `endpointArg`: every value is the one
812
+ * `createCorestoreDatabaseCredentialValues()` derives from the endpoint and the material's own
813
+ * database name, username and password. Material a provider addressed to any other host or port —
814
+ * its own public host above all — is refused by name.
815
+ */
816
+ export const validateCorestoreDatabaseCredentialMaterialEndpoint = (
817
+ materialArg: unknown,
818
+ endpointArg: IPlatformDatabaseEndpoint,
819
+ ): string[] => {
820
+ const errors = validateCorestoreDatabaseCredentialMaterial(materialArg);
821
+ if (errors.length > 0) return errors;
822
+ const endpointErrors = validatePlatformDatabaseEndpoint(endpointArg);
823
+ if (endpointErrors.length > 0) return endpointErrors;
824
+ const values = (materialArg as ICorestoreDatabaseCredentialPlaintextMaterial).values;
825
+ const expected = createCorestoreDatabaseCredentialValues(endpointArg, {
826
+ databaseName: values.MONGODB_DATABASE,
827
+ username: values.MONGODB_USERNAME,
828
+ password: values.MONGODB_PASSWORD,
829
+ });
830
+ const mismatched = corestoreDatabaseCredentialKeys
831
+ .filter((keyArg) => values[keyArg] !== expected[keyArg]);
832
+ return mismatched.length > 0
833
+ ? [`corestore database credential ${mismatched.join(', ')} must be addressed to the binding endpoint`]
834
+ : [];
835
+ };
836
+
730
837
  export const validateCorestoreObjectStorageCredentialMaterial = async (
731
838
  materialArg: unknown,
732
839
  retentionExpectationArg?: IObjectStorageRetentionEvidenceExpectation,