@lossless.org/objectstorage 10.7.1 → 11.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.
@@ -109,7 +109,7 @@ root, run:
109
109
 
110
110
  ```sh
111
111
  node scripts/generate-native-notices.mjs
112
- pnpm exec tstest test/test.notices.node.ts --verbose --logfile
112
+ pnpm exec tsrepo test test/test.notices.node.ts --verbose --logfile
113
113
  ```
114
114
 
115
115
  The generator uses Cargo.lock and checksum-pinned local or Git license sources
@@ -2116,7 +2116,7 @@
2116
2116
  "zerocopy-derive@0.8.47": "third_party_licenses/zerocopy-derive-0.8.47-license.txt"
2117
2117
  },
2118
2118
  "files": {
2119
- ".smartconfig.json": "f6e76a718926bfb661e004f49ef0606cdecb48f773f74e2c38e224e29116dcfb",
2119
+ ".smartconfig.json": "36557e7545b45c38432fa291e11551c20cc0e54fdc6436a0797429e32d1e4d8d",
2120
2120
  "license.md": "bd1cbee3bf4a1ec7f4bb842e315cb0c296744e93050493607cb32f1570f1e878",
2121
2121
  "rust-toolchain.toml": "a721b38eb6b02c7b6cfba956549c18282a3cd28347954021fc1e508c7b27d15b",
2122
2122
  "rust/Cargo.lock": "4b1fb196bca6bec6c326dd822db4d5ca5eb0740fea0544100f9e9d8984187762",
@@ -2125,7 +2125,7 @@
2125
2125
  "rust/about.toml": "dc5dbae4e4b746e12c3a810cdc2473c380e7b9b3c0ecdd095d02693dbb8876b2",
2126
2126
  "rust/notices.sources.json": "7c98fe57f606700bb8372a890f8e49a1240385224680ec0877c3c623ab9cf598",
2127
2127
  "scripts/generate-native-notices.mjs": "e654c52e292778ca72f09e96420b18a49271a7c6612cdd2e2f4dbcd01dec1bcb",
2128
- "third-party-notices.md": "bf09c39634523d9794862464eaadcf911ad5373709db40a41c811fcff4f2cd59",
2128
+ "third-party-notices.md": "7e733804670e866e563b999ea600ddff72b0f8dee63552f223516ed88dc9afdd",
2129
2129
  "third_party_licenses/aho-corasick-1.1.4-license.txt": "0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f",
2130
2130
  "third_party_licenses/base64-0.22.1/LICENSE-MIT": "0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7",
2131
2131
  "third_party_licenses/cargo-dependencies.html": "8a007780ac193d1fc51ebe8ef7d302dd39e2426a887219049c4ac392786fbed0",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/objectstorage',
6
- version: '10.7.1',
6
+ version: '11.0.0',
7
7
  description: 'A Node.js TypeScript package to create a local S3-compatible storage server using mapped local directories for development and testing purposes.'
8
8
  }
package/ts/index.ts CHANGED
@@ -62,6 +62,11 @@ export interface ILimitsConfig {
62
62
  export interface IMultipartConfig {
63
63
  expirationDays?: number;
64
64
  cleanupIntervalMinutes?: number;
65
+ /**
66
+ * Open multipart uploads that the prefix credentials of one scope (bucket
67
+ * and prefix) may hold together (default 100). Counted in memory.
68
+ */
69
+ maxUploadsPerPrefixScope?: number;
65
70
  }
66
71
 
67
72
  /**
@@ -118,6 +123,11 @@ export interface IStorageConfig {
118
123
  export interface IErasureConfig {
119
124
  dataShards?: number;
120
125
  parityShards?: number;
126
+ /**
127
+ * Bytes per erasure-coded chunk, between 1 and 64 MiB (67,108,864 bytes);
128
+ * startup refuses a value outside that range, because every manifest
129
+ * refuses a larger chunk. Default 4,194,304 (4 MiB).
130
+ */
121
131
  chunkSizeBytes?: number;
122
132
  }
123
133
 
@@ -126,6 +136,13 @@ export interface IErasureConfig {
126
136
  */
127
137
  export interface IDriveConfig {
128
138
  paths: string[];
139
+ /**
140
+ * Accept drive roots that share a device (`st_dev`). Each drive root counts
141
+ * as one failure domain, so by default startup refuses two drive roots on
142
+ * one device. Set this only for dev and test clusters whose drive roots are
143
+ * directories on one filesystem. Default false.
144
+ */
145
+ allowSharedDevice?: boolean;
129
146
  }
130
147
 
131
148
  /**
@@ -140,6 +157,16 @@ export interface IClusterConfig {
140
157
  drives?: IDriveConfig;
141
158
  heartbeatIntervalMs?: number;
142
159
  heartbeatTimeoutMs?: number;
160
+ /**
161
+ * Longest a single GET may run, in milliseconds, from its start to its last
162
+ * byte, including the client's transfer time. A longer GET is aborted.
163
+ * Superseded and deleted object generations keep their shards for this
164
+ * deadline plus the smaller of the deadline and one hour, then cleanup frees
165
+ * them. Must be the same on every node: a mismatch can abort streams but
166
+ * never serves wrong bytes. Default 21,600,000 (6 hours); allowed from
167
+ * 60,000 (1 minute) to 604,800,000 (7 days).
168
+ */
169
+ getDeadlineMs?: number;
143
170
  }
144
171
 
145
172
  /**
@@ -341,6 +368,35 @@ export interface IBucketTenantMetadata {
341
368
  retention?: IBucketRetentionReceipt;
342
369
  }
343
370
 
371
+ export interface IPrefixCredentialInput {
372
+ bucketName: string;
373
+ /** Unreserved characters (RFC 3986) and `/`; ends with `/`, no leading `/`. */
374
+ prefix: string;
375
+ /** First node: lifetime in seconds, 1..43200. */
376
+ ttlSeconds?: number;
377
+ /** Fan-out to further nodes: the first node's expiresAt (epoch ms). */
378
+ expiresAt?: number;
379
+ /** Supply both together with expiresAt to install an existing credential on another node. */
380
+ accessKeyId?: string;
381
+ secretAccessKey?: string;
382
+ region?: string;
383
+ }
384
+
385
+ export interface IPrefixCredentialMetadata {
386
+ accessKeyId: string;
387
+ bucketName: string;
388
+ prefix: string;
389
+ region: string;
390
+ /** Epoch ms. */
391
+ createdAt: number;
392
+ /** Epoch ms; requests are refused from this instant and admitted ones end at it. */
393
+ expiresAt: number;
394
+ }
395
+
396
+ export interface IPrefixCredential extends IPrefixCredentialMetadata {
397
+ secretAccessKey: string;
398
+ }
399
+
344
400
  export interface IBucketRetentionPolicyInput {
345
401
  mode: 'compliance';
346
402
  policyId: string;
@@ -998,6 +1054,10 @@ export interface IObjectStorageHealth {
998
1054
  enabled: boolean;
999
1055
  credentialCount: number;
1000
1056
  tenantCredentialCount: number;
1057
+ prefixCredentialCount: number;
1058
+ prefixCredentialTombstoneCount: number;
1059
+ /** False when the prefix credential file is unreadable: every prefix credential is refused. */
1060
+ prefixCredentialStoreAvailable: boolean;
1001
1061
  };
1002
1062
  bucketCount: number;
1003
1063
  objectCount: number;
@@ -1062,6 +1122,55 @@ const normalizeResourceFenceError = (errorArg: unknown): Error => {
1062
1122
  );
1063
1123
  };
1064
1124
 
1125
+ export const objectStorageCredentialErrorCodes = [
1126
+ 'ECREDENTIAL_AUTH_DISABLED',
1127
+ 'ECREDENTIAL_INVALID_PREFIX',
1128
+ 'ECREDENTIAL_INVALID_TTL',
1129
+ 'ECREDENTIAL_INVALID_EXPIRY',
1130
+ 'ECREDENTIAL_INVALID_KEY',
1131
+ 'ECREDENTIAL_NO_SUCH_BUCKET',
1132
+ 'ECREDENTIAL_BUCKET_UNAVAILABLE',
1133
+ 'ECREDENTIAL_CONFLICT',
1134
+ 'ECREDENTIAL_REVOKED',
1135
+ 'ECREDENTIAL_NOT_PREFIX',
1136
+ 'ECREDENTIAL_LIMIT',
1137
+ 'ECREDENTIAL_PERSISTENCE',
1138
+ 'ECREDENTIAL_STORE_UNAVAILABLE',
1139
+ ] as const;
1140
+
1141
+ export type TObjectStorageCredentialErrorCode =
1142
+ (typeof objectStorageCredentialErrorCodes)[number];
1143
+
1144
+ export class ObjectStorageCredentialError extends Error {
1145
+ public readonly code: TObjectStorageCredentialErrorCode;
1146
+
1147
+ public constructor(
1148
+ codeArg: TObjectStorageCredentialErrorCode,
1149
+ messageArg: string,
1150
+ causeArg?: unknown,
1151
+ ) {
1152
+ super(`${codeArg}: ${messageArg}`, { cause: causeArg });
1153
+ this.name = 'ObjectStorageCredentialError';
1154
+ this.code = codeArg;
1155
+ }
1156
+ }
1157
+
1158
+ const normalizeCredentialError = (errorArg: unknown): Error => {
1159
+ const error = errorArg instanceof Error ? errorArg : new Error(String(errorArg));
1160
+ const match = /(ECREDENTIAL_[A-Z_]+):\s*(.*)$/s.exec(error.message);
1161
+ if (
1162
+ !match ||
1163
+ !objectStorageCredentialErrorCodes.includes(match[1] as TObjectStorageCredentialErrorCode)
1164
+ ) {
1165
+ return error;
1166
+ }
1167
+ return new ObjectStorageCredentialError(
1168
+ match[1] as TObjectStorageCredentialErrorCode,
1169
+ match[2],
1170
+ error,
1171
+ );
1172
+ };
1173
+
1065
1174
  export interface IObjectStorageMetrics {
1066
1175
  bucketCount: number;
1067
1176
  objectCount: number;
@@ -1119,6 +1228,8 @@ export interface IClusterRepairHealth {
1119
1228
  lastDurationMs?: number;
1120
1229
  shardsChecked?: number;
1121
1230
  shardsHealed?: number;
1231
+ /** Pending shards (writes that failed at publication) the last scan left pending. */
1232
+ shardsPending?: number;
1122
1233
  failed?: number;
1123
1234
  lastError?: string;
1124
1235
  }
@@ -1140,6 +1251,35 @@ export interface IClusterHealth {
1140
1251
  repairs?: IClusterRepairHealth;
1141
1252
  }
1142
1253
 
1254
+ /**
1255
+ * The result of one cluster healing scan, as `ObjectStorage.runHealingScan()`
1256
+ * returns it. Times are milliseconds since the Unix epoch.
1257
+ */
1258
+ export interface IClusterHealingScanResult {
1259
+ /** When the scan started; always after the request arrived. */
1260
+ startedAt: number;
1261
+ completedAt: number;
1262
+ durationMs: number;
1263
+ /** Shard placements checked at their drives (placements on offline nodes are not). */
1264
+ shardsChecked: number;
1265
+ /** Shards written: missing placements rebuilt and pending shards completed. */
1266
+ shardsHealed: number;
1267
+ /** Pending shards (writes that failed at publication) this scan left pending. */
1268
+ shardsPending: number;
1269
+ /** Failed checks, rebuilds, writes and manifest stores. */
1270
+ failed: number;
1271
+ }
1272
+
1273
+ export interface IRunHealingScanOptions {
1274
+ /**
1275
+ * How long to wait for the scan, in milliseconds. Defaults to the bridge's
1276
+ * request timeout (300,000). The engine cannot cancel a scan: one that
1277
+ * outlasts this keeps running, its result is discarded, and a later call
1278
+ * joins the next scan that starts after it.
1279
+ */
1280
+ timeoutMs?: number;
1281
+ }
1282
+
1143
1283
  /**
1144
1284
  * Default configuration values
1145
1285
  */
@@ -1185,6 +1325,7 @@ const DEFAULT_CONFIG: IObjectStorageConfig = {
1185
1325
  multipart: {
1186
1326
  expirationDays: 7,
1187
1327
  cleanupIntervalMinutes: 60,
1328
+ maxUploadsPerPrefixScope: 100,
1188
1329
  },
1189
1330
  };
1190
1331
 
@@ -1313,6 +1454,10 @@ function createAccessKeyId(): string {
1313
1454
  return `SS${plugins.crypto.randomBytes(10).toString('hex').toUpperCase()}`;
1314
1455
  }
1315
1456
 
1457
+ function createPrefixAccessKeyId(): string {
1458
+ return `SP${plugins.crypto.randomBytes(10).toString('hex').toUpperCase()}`;
1459
+ }
1460
+
1316
1461
  function createSecretAccessKey(): string {
1317
1462
  return plugins.crypto.randomBytes(32).toString('hex');
1318
1463
  }
@@ -1441,7 +1586,29 @@ type TRustStorageCommands = {
1441
1586
  listBucketSummaries: { params: {}; result: IBucketSummary[] };
1442
1587
  listCredentials: { params: {}; result: IStorageCredentialMetadata[] };
1443
1588
  replaceCredentials: { params: { credentials: IStorageCredential[] }; result: {} };
1589
+ createPrefixCredential: {
1590
+ params: {
1591
+ bucketName: string;
1592
+ prefix: string;
1593
+ accessKeyId: string;
1594
+ secretAccessKey: string;
1595
+ region?: string;
1596
+ ttlSeconds?: number;
1597
+ expiresAt?: number;
1598
+ };
1599
+ result: IPrefixCredentialMetadata;
1600
+ };
1601
+ revokePrefixCredential: { params: { accessKeyId: string }; result: { revoked: boolean } };
1602
+ listPrefixCredentials: {
1603
+ params: { bucketName?: string };
1604
+ result: IPrefixCredentialMetadata[];
1605
+ };
1606
+ getPrefixCredentialStatus: {
1607
+ params: {};
1608
+ result: { available: boolean; credentialCount: number; tombstoneCount: number };
1609
+ };
1444
1610
  getClusterHealth: { params: {}; result: IClusterHealth };
1611
+ runHealingScan: { params: {}; result: IClusterHealingScanResult };
1445
1612
  };
1446
1613
 
1447
1614
  /**
@@ -1730,7 +1897,7 @@ export class ObjectStorage {
1730
1897
  });
1731
1898
  return this.buildBucketTenantDescriptor(credential, tenantArg.bucketName);
1732
1899
  } catch (error) {
1733
- throw normalizeResourceFenceError(error);
1900
+ throw normalizeCredentialError(normalizeResourceFenceError(error));
1734
1901
  }
1735
1902
  }
1736
1903
 
@@ -1745,7 +1912,7 @@ export class ObjectStorage {
1745
1912
  });
1746
1913
  return this.buildBucketTenantDescriptor(credential, tenantArg.bucketName);
1747
1914
  } catch (error) {
1748
- throw normalizeResourceFenceError(error);
1915
+ throw normalizeCredentialError(normalizeResourceFenceError(error));
1749
1916
  }
1750
1917
  }
1751
1918
 
@@ -1754,7 +1921,7 @@ export class ObjectStorage {
1754
1921
  try {
1755
1922
  await this.bridge.sendCommand('deleteBucketTenant', tenantArg);
1756
1923
  } catch (error) {
1757
- throw normalizeResourceFenceError(error);
1924
+ throw normalizeCredentialError(normalizeResourceFenceError(error));
1758
1925
  }
1759
1926
  }
1760
1927
 
@@ -1771,7 +1938,7 @@ export class ObjectStorage {
1771
1938
  });
1772
1939
  return this.buildBucketTenantDescriptor(credential, tenantArg.bucketName);
1773
1940
  } catch (error) {
1774
- throw normalizeResourceFenceError(error);
1941
+ throw normalizeCredentialError(normalizeResourceFenceError(error));
1775
1942
  }
1776
1943
  }
1777
1944
 
@@ -1790,6 +1957,95 @@ export class ObjectStorage {
1790
1957
  return this.buildBucketTenantDescriptor(credential, optionsArg.bucketName);
1791
1958
  }
1792
1959
 
1960
+ /**
1961
+ * Creates a credential confined to one key prefix of one bucket until its
1962
+ * expiry. The first node takes `ttlSeconds`; further nodes of a fan-out take
1963
+ * the first result's `accessKeyId`, `secretAccessKey` and `expiresAt`. The
1964
+ * secret is returned only here and never kept by this instance.
1965
+ */
1966
+ public async createPrefixCredential(
1967
+ inputArg: IPrefixCredentialInput,
1968
+ ): Promise<IPrefixCredential> {
1969
+ const hasTtl = inputArg.ttlSeconds !== undefined;
1970
+ const hasExpiresAt = inputArg.expiresAt !== undefined;
1971
+ if (hasTtl === hasExpiresAt) {
1972
+ throw new ObjectStorageCredentialError(
1973
+ 'ECREDENTIAL_INVALID_TTL',
1974
+ 'Exactly one of ttlSeconds and expiresAt is required.',
1975
+ );
1976
+ }
1977
+ if (hasTtl && (!Number.isSafeInteger(inputArg.ttlSeconds) || inputArg.ttlSeconds! < 1)) {
1978
+ throw new ObjectStorageCredentialError(
1979
+ 'ECREDENTIAL_INVALID_TTL',
1980
+ 'ttlSeconds must be an integer from 1 to 43200.',
1981
+ );
1982
+ }
1983
+ if (hasExpiresAt && !Number.isSafeInteger(inputArg.expiresAt)) {
1984
+ throw new ObjectStorageCredentialError(
1985
+ 'ECREDENTIAL_INVALID_EXPIRY',
1986
+ 'expiresAt must be an integer epoch millisecond.',
1987
+ );
1988
+ }
1989
+ const hasAccessKeyId = inputArg.accessKeyId !== undefined;
1990
+ const hasSecretAccessKey = inputArg.secretAccessKey !== undefined;
1991
+ if (hasAccessKeyId !== hasSecretAccessKey) {
1992
+ throw new ObjectStorageCredentialError(
1993
+ 'ECREDENTIAL_INVALID_KEY',
1994
+ 'accessKeyId and secretAccessKey must be supplied together.',
1995
+ );
1996
+ }
1997
+ if (hasAccessKeyId && !hasExpiresAt) {
1998
+ throw new ObjectStorageCredentialError(
1999
+ 'ECREDENTIAL_INVALID_KEY',
2000
+ 'Key material is accepted only with expiresAt, to install an existing credential.',
2001
+ );
2002
+ }
2003
+ const secretAccessKey = inputArg.secretAccessKey ?? createSecretAccessKey();
2004
+ try {
2005
+ const metadata = await this.bridge.sendCommand('createPrefixCredential', {
2006
+ bucketName: inputArg.bucketName,
2007
+ prefix: inputArg.prefix,
2008
+ accessKeyId: inputArg.accessKeyId ?? createPrefixAccessKeyId(),
2009
+ secretAccessKey,
2010
+ region: inputArg.region || this.config.server.region,
2011
+ ...(hasTtl ? { ttlSeconds: inputArg.ttlSeconds } : { expiresAt: inputArg.expiresAt }),
2012
+ });
2013
+ return { ...metadata, secretAccessKey };
2014
+ } catch (error) {
2015
+ throw normalizeCredentialError(error);
2016
+ }
2017
+ }
2018
+
2019
+ /**
2020
+ * Revokes a prefix credential on this node. An unknown ID is recorded as
2021
+ * revoked too, so a later create for it is refused; `revoked` is true only
2022
+ * when a live credential was removed.
2023
+ */
2024
+ public async revokePrefixCredential(optionsArg: {
2025
+ accessKeyId: string;
2026
+ }): Promise<{ revoked: boolean }> {
2027
+ try {
2028
+ return await this.bridge.sendCommand('revokePrefixCredential', {
2029
+ accessKeyId: optionsArg.accessKeyId,
2030
+ });
2031
+ } catch (error) {
2032
+ throw normalizeCredentialError(error);
2033
+ }
2034
+ }
2035
+
2036
+ /** Lists this node's live prefix credentials, without secrets. */
2037
+ public async listPrefixCredentials(
2038
+ optionsArg: { bucketName?: string } = {},
2039
+ ): Promise<IPrefixCredentialMetadata[]> {
2040
+ try {
2041
+ return await this.bridge.sendCommand('listPrefixCredentials', {
2042
+ ...(optionsArg.bucketName !== undefined ? { bucketName: optionsArg.bucketName } : {}),
2043
+ });
2044
+ } catch (error) {
2045
+ throw normalizeCredentialError(error);
2046
+ }
2047
+ }
2048
+
1793
2049
  public async getBucketRetentionCapability(): Promise<IBucketRetentionCapability> {
1794
2050
  return this.bridge.sendCommand('getBucketRetentionCapability', {});
1795
2051
  }
@@ -1864,7 +2120,7 @@ export class ObjectStorage {
1864
2120
  try {
1865
2121
  return await this.bridge.sendCommand('deleteBucket', optionsArg);
1866
2122
  } catch (error) {
1867
- throw normalizeResourceFenceError(error);
2123
+ throw normalizeCredentialError(normalizeResourceFenceError(error));
1868
2124
  }
1869
2125
  }
1870
2126
 
@@ -1914,7 +2170,11 @@ export class ObjectStorage {
1914
2170
  * if this override introduces ownership or credential-material drift.
1915
2171
  */
1916
2172
  public async replaceCredentials(credentials: IStorageCredential[]): Promise<void> {
1917
- await this.bridge.sendCommand('replaceCredentials', { credentials });
2173
+ try {
2174
+ await this.bridge.sendCommand('replaceCredentials', { credentials });
2175
+ } catch (error) {
2176
+ throw normalizeCredentialError(error);
2177
+ }
1918
2178
  this.config.auth.credentials = credentials.map((credential) => ({ ...credential }));
1919
2179
  }
1920
2180
 
@@ -1922,6 +2182,27 @@ export class ObjectStorage {
1922
2182
  return this.bridge.sendCommand('getClusterHealth', {});
1923
2183
  }
1924
2184
 
2185
+ /**
2186
+ * Runs a healing scan on this cluster node and resolves with its result.
2187
+ * The scan covers the manifests this node coordinated. The request joins
2188
+ * the next scan that starts after it arrives, never one already running,
2189
+ * and requests that arrive together share one scan. It rejects in
2190
+ * standalone mode, when the scan fails (for example without a majority of
2191
+ * the cluster, or when a bucket's manifests cannot be listed), when 16
2192
+ * requests already wait for a scan to start, and when the engine shuts down
2193
+ * before the scan completes. The engine answers other management calls
2194
+ * while the scan runs.
2195
+ */
2196
+ public async runHealingScan(
2197
+ optionsArg: IRunHealingScanOptions = {},
2198
+ ): Promise<IClusterHealingScanResult> {
2199
+ return this.bridge.sendCommand(
2200
+ 'runHealingScan',
2201
+ {},
2202
+ optionsArg.timeoutMs === undefined ? undefined : { timeoutMs: optionsArg.timeoutMs },
2203
+ );
2204
+ }
2205
+
1925
2206
  public async getHealth(): Promise<IObjectStorageHealth> {
1926
2207
  if (!this.running) {
1927
2208
  return {
@@ -1933,6 +2214,9 @@ export class ObjectStorage {
1933
2214
  enabled: this.config.auth.enabled,
1934
2215
  credentialCount: this.config.auth.credentials.length,
1935
2216
  tenantCredentialCount: 0,
2217
+ prefixCredentialCount: 0,
2218
+ prefixCredentialTombstoneCount: 0,
2219
+ prefixCredentialStoreAvailable: false,
1936
2220
  },
1937
2221
  bucketCount: 0,
1938
2222
  objectCount: 0,
@@ -1947,13 +2231,15 @@ export class ObjectStorage {
1947
2231
  };
1948
2232
  }
1949
2233
 
1950
- const [stats, credentials, tenants, cluster, resourceFencing] = await Promise.all([
1951
- this.getStorageStats(),
1952
- this.listCredentials(),
1953
- this.config.auth.enabled ? this.listBucketTenants() : Promise.resolve([]),
1954
- this.getClusterHealth(),
1955
- this.getResourceFencingCapability(),
1956
- ]);
2234
+ const [stats, credentials, tenants, prefixCredentials, cluster, resourceFencing] =
2235
+ await Promise.all([
2236
+ this.getStorageStats(),
2237
+ this.listCredentials(),
2238
+ this.config.auth.enabled ? this.listBucketTenants() : Promise.resolve([]),
2239
+ this.bridge.sendCommand('getPrefixCredentialStatus', {}),
2240
+ this.getClusterHealth(),
2241
+ this.getResourceFencingCapability(),
2242
+ ]);
1957
2243
  return {
1958
2244
  ok: true,
1959
2245
  running: true,
@@ -1963,6 +2249,9 @@ export class ObjectStorage {
1963
2249
  enabled: this.config.auth.enabled,
1964
2250
  credentialCount: credentials.length,
1965
2251
  tenantCredentialCount: tenants.length,
2252
+ prefixCredentialCount: prefixCredentials.credentialCount,
2253
+ prefixCredentialTombstoneCount: prefixCredentials.tombstoneCount,
2254
+ prefixCredentialStoreAvailable: prefixCredentials.available,
1966
2255
  },
1967
2256
  bucketCount: stats.bucketCount,
1968
2257
  objectCount: stats.totalObjectCount,
@@ -2002,6 +2291,15 @@ export class ObjectStorage {
2002
2291
  '# HELP objectstorage_tenant_credentials_total Scoped bucket tenant credential count.',
2003
2292
  '# TYPE objectstorage_tenant_credentials_total gauge',
2004
2293
  `objectstorage_tenant_credentials_total ${health.auth.tenantCredentialCount}`,
2294
+ '# HELP objectstorage_prefix_credentials_total Live prefix credential count.',
2295
+ '# TYPE objectstorage_prefix_credentials_total gauge',
2296
+ `objectstorage_prefix_credentials_total ${health.auth.prefixCredentialCount}`,
2297
+ '# HELP objectstorage_prefix_credential_tombstones_total Unexpired prefix credential revocations.',
2298
+ '# TYPE objectstorage_prefix_credential_tombstones_total gauge',
2299
+ `objectstorage_prefix_credential_tombstones_total ${health.auth.prefixCredentialTombstoneCount}`,
2300
+ '# HELP objectstorage_prefix_credential_store_available Prefix credential store readable.',
2301
+ '# TYPE objectstorage_prefix_credential_store_available gauge',
2302
+ `objectstorage_prefix_credential_store_available ${health.auth.prefixCredentialStoreAvailable ? 1 : 0}`,
2005
2303
  '# HELP objectstorage_cluster_enabled Cluster mode enabled.',
2006
2304
  '# TYPE objectstorage_cluster_enabled gauge',
2007
2305
  `objectstorage_cluster_enabled ${clusterEnabled ? 1 : 0}`,