kuzzle 2.60.0-beta.4 → 2.60.0-beta.6
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/dist/lib/api/controllers/adminController.d.ts +7 -1
- package/dist/lib/api/controllers/adminController.js +9 -2
- package/dist/lib/api/funnel.js +12 -1
- package/dist/lib/api/httpRoutes.js +52 -20
- package/dist/lib/api/request/kuzzleRequest.d.ts +7 -2
- package/dist/lib/api/request/kuzzleRequest.js +20 -10
- package/dist/lib/api/request/requestContext.d.ts +1 -1
- package/dist/lib/api/request/requestResponse.js +2 -1
- package/dist/lib/config/index.js +60 -3
- package/dist/lib/core/backend/backend.d.ts +3 -2
- package/dist/lib/core/backend/internalLogger.d.ts +6 -0
- package/dist/lib/core/backend/internalLogger.js +6 -0
- package/dist/lib/core/network/router.d.ts +18 -0
- package/dist/lib/core/network/router.js +44 -10
- package/dist/lib/core/security/authTokenSettings.d.ts +21 -0
- package/dist/lib/core/security/authTokenSettings.js +61 -0
- package/dist/lib/core/security/tokenRepository.js +8 -14
- package/dist/lib/core/shared/KoncordeWrapper.d.ts +9 -0
- package/dist/lib/core/shared/KoncordeWrapper.js +8 -8
- package/dist/lib/core/storage/clientAdapter.js +7 -2
- package/dist/lib/kuzzle/Logger.d.ts +4 -0
- package/dist/lib/kuzzle/Logger.js +9 -2
- package/dist/lib/kuzzle/internalIndexHandler.js +4 -5
- package/dist/lib/kuzzle/kuzzle.d.ts +17 -0
- package/dist/lib/kuzzle/kuzzle.js +62 -0
- package/dist/lib/kuzzle/vault.d.ts +13 -0
- package/dist/lib/kuzzle/vault.js +25 -7
- package/dist/lib/service/storage/7/elasticsearch.d.ts +12 -0
- package/dist/lib/service/storage/7/elasticsearch.js +37 -15
- package/dist/lib/service/storage/8/elasticsearch.d.ts +12 -0
- package/dist/lib/service/storage/8/elasticsearch.js +37 -15
- package/dist/lib/types/config/KuzzleConfiguration.d.ts +7 -0
- package/dist/lib/types/config/SecurityConfiguration.d.ts +4 -3
- package/dist/lib/types/controllers/ControllerDefinition.d.ts +4 -4
- package/dist/lib/util/mutex.d.ts +3 -3
- package/dist/lib/util/mutex.js +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
|
@@ -35,14 +35,14 @@ class NormalizedFilterV3 {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
exports.NormalizedFilterV3 = NormalizedFilterV3;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Exposes Koncorde v4 through an interface emulating Koncorde v3, to prevent
|
|
40
|
+
* breaking changes in Kuzzle.
|
|
41
|
+
*
|
|
42
|
+
* This class should either be removed entirely in the next major version, or
|
|
43
|
+
* perhaps proposed in a compatibility mode to help clients migrate smoothly.
|
|
44
|
+
*
|
|
45
|
+
* @deprecated Use the `koncorde` package (v4) directly.
|
|
46
46
|
*/
|
|
47
47
|
class Koncorde {
|
|
48
48
|
constructor(options = {}) {
|
|
@@ -96,8 +96,13 @@ class ClientAdapter {
|
|
|
96
96
|
this.registerLockEvents();
|
|
97
97
|
// The cache and the locks were first loaded before this node joined the
|
|
98
98
|
// cluster: an index, a collection or a lock a peer added in between was
|
|
99
|
-
// broadcast before this node could hear it.
|
|
100
|
-
// cluster is joined
|
|
99
|
+
// broadcast before this node could hear it. `Kuzzle.start()` asks for this
|
|
100
|
+
// reload right after the cluster is joined, before the plugins' `init()`
|
|
101
|
+
// runs (#3044).
|
|
102
|
+
global.kuzzle.onAsk(`core:storage:${this.scope}:cache:reload`, () => this.reloadCache());
|
|
103
|
+
// Read again once the node goes live: a peer that joined after this node
|
|
104
|
+
// may have created indexes or collections before its own join (e.g. its
|
|
105
|
+
// internal index), and that broadcast reached no one.
|
|
101
106
|
global.kuzzle.onPipe("kuzzle:state:live", () => this.reloadCache());
|
|
102
107
|
// Global store events registration
|
|
103
108
|
/**
|
|
@@ -28,5 +28,9 @@ export declare class Logger extends KuzzleLogger {
|
|
|
28
28
|
verbose(message: string): void;
|
|
29
29
|
getLevel(): string;
|
|
30
30
|
setLevel(level: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Creates a child logger with the given namespace, appended to the parent's
|
|
33
|
+
* one. Its level follows the parent's until it is set on the child.
|
|
34
|
+
*/
|
|
31
35
|
child(namespace: string): Logger;
|
|
32
36
|
}
|
|
@@ -128,9 +128,16 @@ class Logger extends kuzzle_logger_1.KuzzleLogger {
|
|
|
128
128
|
setLevel(level) {
|
|
129
129
|
this.level = level;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Creates a child logger with the given namespace, appended to the parent's
|
|
133
|
+
* one. Its level follows the parent's until it is set on the child.
|
|
134
|
+
*/
|
|
131
135
|
child(namespace) {
|
|
132
|
-
//
|
|
133
|
-
|
|
136
|
+
// kuzzle-logger builds the child as a plain KuzzleLogger (no getLevel,
|
|
137
|
+
// setLevel, silly, verbose): give it this class's prototype so it is the
|
|
138
|
+
// Logger it is declared to be. Its state is all KuzzleLogger's, and the
|
|
139
|
+
// deprecation flags start unset, as on a new Logger.
|
|
140
|
+
return Object.setPrototypeOf(super.child(namespace), Logger.prototype);
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
exports.Logger = Logger;
|
|
@@ -57,6 +57,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
57
57
|
};
|
|
58
58
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
59
59
|
const bluebird_1 = __importDefault(require("bluebird"));
|
|
60
|
+
const authTokenSettings_1 = require("../core/security/authTokenSettings");
|
|
60
61
|
const store_1 = require("../core/shared/store");
|
|
61
62
|
const storeScopeEnum_1 = require("../core/storage/storeScopeEnum");
|
|
62
63
|
const specification_1 = require("../core/validation/specification");
|
|
@@ -191,11 +192,9 @@ class InternalIndexHandler extends store_1.Store {
|
|
|
191
192
|
await bluebird_1.default.all(promises);
|
|
192
193
|
}
|
|
193
194
|
async _initSecret() {
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
const { authToken, jwt } = global.kuzzle.config.security; // NOSONAR
|
|
198
|
-
const configSeed = authToken?.secret ?? jwt?.secret; // NOSONAR
|
|
195
|
+
// `jwt` is the deprecated spelling of `authToken`, read second so an
|
|
196
|
+
// existing configuration keeps working (see authTokenSetting).
|
|
197
|
+
const configSeed = (0, authTokenSettings_1.authTokenSetting)("secret");
|
|
199
198
|
let storedSeed = await this.exists("config", this._JWT_SECRET_ID);
|
|
200
199
|
if (!configSeed) {
|
|
201
200
|
if (!storedSeed) {
|
|
@@ -107,6 +107,23 @@ declare class Kuzzle extends KuzzleEventEmitter {
|
|
|
107
107
|
* it hands the application wrapper the implementation reads.
|
|
108
108
|
*/
|
|
109
109
|
start(application: unknown, options?: StartOptions): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* A node asked to stop while it starts must not go on taking cluster-wide
|
|
112
|
+
* locks or running the application's installations: if it is killed before
|
|
113
|
+
* its shutdown releases them, the other nodes wait for their TTL (#3045).
|
|
114
|
+
*/
|
|
115
|
+
private stopIfShuttingDown;
|
|
116
|
+
/**
|
|
117
|
+
* The shutdown began during the startup, which stopped (or failed because
|
|
118
|
+
* of it): no "Cannot start" error and no second shutdown, the one running
|
|
119
|
+
* keeps its exit code and ends the process.
|
|
120
|
+
*
|
|
121
|
+
* The returned promise never settles, as `withLock` once the process is
|
|
122
|
+
* exiting: rejecting would send the caller down its failed-start path
|
|
123
|
+
* (often a `process.exit(1)`, which would cut the shutdown short), and
|
|
124
|
+
* resolving would tell it that Kuzzle is running.
|
|
125
|
+
*/
|
|
126
|
+
private leaveStartupToShutdown;
|
|
110
127
|
/**
|
|
111
128
|
* Generates the node ID.
|
|
112
129
|
*
|
|
@@ -99,6 +99,17 @@ let _kuzzle = null;
|
|
|
99
99
|
* not keep a failed node alive, which a bare `process.exit(1)` never did
|
|
100
100
|
*/
|
|
101
101
|
const FAILURE_SHUTDOWN_TIMEOUT = 30000;
|
|
102
|
+
/**
|
|
103
|
+
* Stops the startup sequence once a shutdown has begun (#3045): `start()`
|
|
104
|
+
* catches it and leaves the shutdown to end the process. Internal: never
|
|
105
|
+
* handed to the caller of `start()`.
|
|
106
|
+
*/
|
|
107
|
+
class StartupInterruptedError extends Error {
|
|
108
|
+
constructor() {
|
|
109
|
+
super("Kuzzle is shutting down");
|
|
110
|
+
this.name = "StartupInterruptedError";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
102
113
|
Reflect.defineProperty(global, "kuzzle", {
|
|
103
114
|
configurable: true,
|
|
104
115
|
enumerable: false,
|
|
@@ -175,9 +186,11 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
175
186
|
await new cacheEngine_1.default().init();
|
|
176
187
|
await new storageEngine_1.default().init();
|
|
177
188
|
await new realtime_1.default().init();
|
|
189
|
+
this.stopIfShuttingDown();
|
|
178
190
|
await this.internalIndex.init();
|
|
179
191
|
await new security_1.default().init();
|
|
180
192
|
// This will init the cluster module if enabled
|
|
193
|
+
this.stopIfShuttingDown();
|
|
181
194
|
await this.initKuzzleNode();
|
|
182
195
|
this.vault = vault_1.default.load(options?.vaultKey, options?.secretsFile, this.config.vault?.newAlgorithm);
|
|
183
196
|
this.validation.init();
|
|
@@ -189,21 +202,25 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
189
202
|
// before opening connections to external users
|
|
190
203
|
await this.entryPoint.init();
|
|
191
204
|
await this.debugger.init();
|
|
205
|
+
this.stopIfShuttingDown();
|
|
192
206
|
this.pluginsManager.application = application;
|
|
193
207
|
const pluginImports = await this.pluginsManager.init(options?.plugins);
|
|
194
208
|
const imports = lodash_1.default.merge({}, pluginImports, options?.import);
|
|
195
209
|
// Authentification plugins must be loaded before users import to avoid
|
|
196
210
|
// credentials related error which would prevent Kuzzle from starting
|
|
211
|
+
this.stopIfShuttingDown();
|
|
197
212
|
await this.loadInitialState(imports, options?.support);
|
|
198
213
|
await this.ask("core:security:verify");
|
|
199
214
|
this.router.init();
|
|
200
215
|
this.log.info("[✔] Core components loaded");
|
|
216
|
+
this.stopIfShuttingDown();
|
|
201
217
|
await this.install(options?.installations);
|
|
202
218
|
// `application` is the very wrapper handed to the plugins manager six
|
|
203
219
|
// lines up, so reading the name back off the manager was a round trip
|
|
204
220
|
// through a getter that can answer `undefined`.
|
|
205
221
|
this.log.info(`[✔] Start "${application.name}" application`);
|
|
206
222
|
this.openApiManager = new openapi_1.OpenApiManager(application.openApi, this.config.http.routes, this.pluginsManager.routes);
|
|
223
|
+
this.stopIfShuttingDown();
|
|
207
224
|
// @deprecated
|
|
208
225
|
await this.pipe("kuzzle:start");
|
|
209
226
|
await this.pipe("kuzzle:state:live");
|
|
@@ -215,6 +232,9 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
215
232
|
this._state = kuzzleStateEnum_1.default.RUNNING;
|
|
216
233
|
}
|
|
217
234
|
catch (error) {
|
|
235
|
+
if (this._state === kuzzleStateEnum_1.default.SHUTTING_DOWN) {
|
|
236
|
+
return this.leaveStartupToShutdown(error);
|
|
237
|
+
}
|
|
218
238
|
this.log.error(`[X] Cannot start Kuzzle ${this.version}: ${error instanceof Error ? error.message : (0, node_util_1.inspect)(error)}`);
|
|
219
239
|
// A failed start cannot be retried (a second Kuzzle instance cannot be
|
|
220
240
|
// built), and what it did start keeps the process alive: in production,
|
|
@@ -229,6 +249,36 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
229
249
|
throw error;
|
|
230
250
|
}
|
|
231
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* A node asked to stop while it starts must not go on taking cluster-wide
|
|
254
|
+
* locks or running the application's installations: if it is killed before
|
|
255
|
+
* its shutdown releases them, the other nodes wait for their TTL (#3045).
|
|
256
|
+
*/
|
|
257
|
+
stopIfShuttingDown() {
|
|
258
|
+
if (this._state === kuzzleStateEnum_1.default.SHUTTING_DOWN) {
|
|
259
|
+
throw new StartupInterruptedError();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The shutdown began during the startup, which stopped (or failed because
|
|
264
|
+
* of it): no "Cannot start" error and no second shutdown, the one running
|
|
265
|
+
* keeps its exit code and ends the process.
|
|
266
|
+
*
|
|
267
|
+
* The returned promise never settles, as `withLock` once the process is
|
|
268
|
+
* exiting: rejecting would send the caller down its failed-start path
|
|
269
|
+
* (often a `process.exit(1)`, which would cut the shutdown short), and
|
|
270
|
+
* resolving would tell it that Kuzzle is running.
|
|
271
|
+
*/
|
|
272
|
+
leaveStartupToShutdown(error) {
|
|
273
|
+
let message = "[ℹ] Startup stopped: Kuzzle is shutting down";
|
|
274
|
+
// A step that failed because of the shutdown, rather than stopped by it
|
|
275
|
+
if (!(error instanceof StartupInterruptedError)) {
|
|
276
|
+
const reason = error instanceof Error ? error.message : (0, node_util_1.inspect)(error);
|
|
277
|
+
message += ` (${reason})`;
|
|
278
|
+
}
|
|
279
|
+
this.log.info(message);
|
|
280
|
+
return new Promise(() => undefined);
|
|
281
|
+
}
|
|
232
282
|
/**
|
|
233
283
|
* Generates the node ID.
|
|
234
284
|
*
|
|
@@ -238,6 +288,13 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
238
288
|
if (this.config.cluster.enabled) {
|
|
239
289
|
await new cluster_1.default().init();
|
|
240
290
|
this.log.info("[✔] Cluster initialized");
|
|
291
|
+
// The storage caches were loaded before the join: what a peer created
|
|
292
|
+
// in between was broadcast before this node could hear it. Read them
|
|
293
|
+
// again before the plugins' `init()` relies on them (#3044).
|
|
294
|
+
await Promise.all([
|
|
295
|
+
this.ask("core:storage:public:cache:reload"),
|
|
296
|
+
this.ask("core:storage:private:cache:reload"),
|
|
297
|
+
]);
|
|
241
298
|
}
|
|
242
299
|
else {
|
|
243
300
|
this.log.info("[X] Cluster disabled: single node mode.");
|
|
@@ -317,6 +374,9 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
317
374
|
// one must not start before they are recorded
|
|
318
375
|
await (0, distributedLock_1.withLock)("backend:installations", async () => {
|
|
319
376
|
for (const installation of installations) {
|
|
377
|
+
// The lock may come long after it was asked for, once the node
|
|
378
|
+
// holding it is done: by then, this one may be shutting down
|
|
379
|
+
this.stopIfShuttingDown();
|
|
320
380
|
const isAlreadyInstalled = await this.ask("core:storage:private:document:exist", "kuzzle", "installations", installation.id);
|
|
321
381
|
if (!isAlreadyInstalled) {
|
|
322
382
|
try {
|
|
@@ -508,6 +568,8 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
|
|
|
508
568
|
// cannot serialise, and the payload is built right above.
|
|
509
569
|
const importPayloadHash = (0, crypto_1.sha256)((0, json_stable_stringify_1.default)(importPayload) ?? "");
|
|
510
570
|
const importType = async () => {
|
|
571
|
+
// Same as for the installations: the lock may come after a SIGTERM
|
|
572
|
+
this.stopIfShuttingDown();
|
|
511
573
|
// Read the bookkeeping *after* the lock, never before: a read taken
|
|
512
574
|
// before acquiring it is not protected by it, and the holder that
|
|
513
575
|
// acted on a stale "not initialized" used to create a document the
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { Vault } from "kuzzle-vault";
|
|
2
|
+
/**
|
|
3
|
+
* The default secrets file, `config/secrets.enc.json` at the project root.
|
|
4
|
+
*
|
|
5
|
+
* Installed as an npm package (KaaF), Kuzzle lives under `node_modules/kuzzle`
|
|
6
|
+
* and the project root is above it: from `node_modules/kuzzle/dist/lib/kuzzle`
|
|
7
|
+
* since the build outputs to `dist/` (2.50.0), from
|
|
8
|
+
* `node_modules/kuzzle/lib/kuzzle` before. Anywhere else (a Kuzzle checkout),
|
|
9
|
+
* the file is the checkout's own `config/secrets.enc.json`.
|
|
10
|
+
*
|
|
11
|
+
* @param dirname - the directory of this module
|
|
12
|
+
*/
|
|
13
|
+
declare function defaultSecretsFile(dirname: string): string;
|
|
2
14
|
declare function load(vaultKey?: string, secretsFile?: string, useNewAlgorithm?: boolean): Vault;
|
|
3
15
|
declare const _default: {
|
|
16
|
+
defaultSecretsFile: typeof defaultSecretsFile;
|
|
4
17
|
load: typeof load;
|
|
5
18
|
};
|
|
6
19
|
export = _default;
|
package/dist/lib/kuzzle/vault.js
CHANGED
|
@@ -35,15 +35,33 @@ const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
|
|
|
35
35
|
// strict while being false for every read before that first assignment
|
|
36
36
|
// (TD-57, #2760).
|
|
37
37
|
let ENV_VAULT_KEY;
|
|
38
|
+
/**
|
|
39
|
+
* The default secrets file, `config/secrets.enc.json` at the project root.
|
|
40
|
+
*
|
|
41
|
+
* Installed as an npm package (KaaF), Kuzzle lives under `node_modules/kuzzle`
|
|
42
|
+
* and the project root is above it: from `node_modules/kuzzle/dist/lib/kuzzle`
|
|
43
|
+
* since the build outputs to `dist/` (2.50.0), from
|
|
44
|
+
* `node_modules/kuzzle/lib/kuzzle` before. Anywhere else (a Kuzzle checkout),
|
|
45
|
+
* the file is the checkout's own `config/secrets.enc.json`.
|
|
46
|
+
*
|
|
47
|
+
* @param dirname - the directory of this module
|
|
48
|
+
*/
|
|
49
|
+
function defaultSecretsFile(dirname) {
|
|
50
|
+
const dir = dirname.split(node_path_1.default.sep).join("/");
|
|
51
|
+
for (const installed of [
|
|
52
|
+
"/node_modules/kuzzle/dist/lib/kuzzle",
|
|
53
|
+
"/node_modules/kuzzle/lib/kuzzle",
|
|
54
|
+
]) {
|
|
55
|
+
if (dir.endsWith(installed)) {
|
|
56
|
+
return node_path_1.default.resolve(dirname.slice(0, dirname.length - installed.length + 1), "config/secrets.enc.json");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return node_path_1.default.resolve(dirname, "../../config/secrets.enc.json");
|
|
60
|
+
}
|
|
38
61
|
function load(vaultKey, secretsFile, useNewAlgorithm = false) {
|
|
39
|
-
// Using KaaF kuzzle is an npm package and is located under node_modules folder
|
|
40
|
-
// We need to get back to root folder of the project to get the secret file
|
|
41
|
-
const defaultEncryptedSecretsFile = __dirname.endsWith("/node_modules/kuzzle/lib/kuzzle")
|
|
42
|
-
? node_path_1.default.resolve(`${__dirname}/../../../../config/secrets.enc.json`)
|
|
43
|
-
: node_path_1.default.resolve(`${__dirname}/../../config/secrets.enc.json`);
|
|
44
62
|
const encryptedSecretsFile = secretsFile ||
|
|
45
63
|
process.env.KUZZLE_SECRETS_FILE ||
|
|
46
|
-
|
|
64
|
+
defaultSecretsFile(__dirname);
|
|
47
65
|
let key = vaultKey;
|
|
48
66
|
if ((0, isEmpty_1.default)(vaultKey) &&
|
|
49
67
|
(!(0, isEmpty_1.default)(process.env.KUZZLE_VAULT_KEY) || !(0, isEmpty_1.default)(ENV_VAULT_KEY))) {
|
|
@@ -71,5 +89,5 @@ function load(vaultKey, secretsFile, useNewAlgorithm = false) {
|
|
|
71
89
|
}
|
|
72
90
|
return vault;
|
|
73
91
|
}
|
|
74
|
-
module.exports = { load };
|
|
92
|
+
module.exports = { defaultSecretsFile, load };
|
|
75
93
|
//# sourceMappingURL=vault.js.map
|
|
@@ -452,6 +452,12 @@ export declare class ES7 {
|
|
|
452
452
|
mappings?: TypeMapping;
|
|
453
453
|
settings?: JSONObject;
|
|
454
454
|
}): Promise<null>;
|
|
455
|
+
/**
|
|
456
|
+
* Applies the mappings and settings of a `createCollection` call to a
|
|
457
|
+
* collection that already exists because another request created it,
|
|
458
|
+
* maybe so recently that its primary shards are still initializing.
|
|
459
|
+
*/
|
|
460
|
+
private _updateConcurrentlyCreatedCollection;
|
|
455
461
|
/**
|
|
456
462
|
* Waits until the primary shards of a collection are active, for at most
|
|
457
463
|
* `timeout`, and rejects if they are not by then.
|
|
@@ -462,6 +468,12 @@ export declare class ES7 {
|
|
|
462
468
|
* collection waits for its shards (`wait_for_active_shards`); a node that
|
|
463
469
|
* finds it already there calls this instead. It answers at once when the
|
|
464
470
|
* shards are already active.
|
|
471
|
+
*
|
|
472
|
+
* A `yellow` status alone does not wait: Elasticsearch reports a new index
|
|
473
|
+
* whose primaries are still initializing as `yellow` (#3074). Asking for as
|
|
474
|
+
* many active shard copies as the index has primaries does, and, unlike
|
|
475
|
+
* `wait_for_no_initializing_shards`, does not also wait for replicas that
|
|
476
|
+
* are recovering, which a collection with data may take longer to do.
|
|
465
477
|
*/
|
|
466
478
|
waitForCollectionShards(index: string, collection: string, timeout?: string): Promise<void>;
|
|
467
479
|
/**
|
|
@@ -1149,23 +1149,23 @@ class ES7 {
|
|
|
1149
1149
|
this._checkDynamicProperty(mappings);
|
|
1150
1150
|
const exists = await this.hasCollection(index, collection);
|
|
1151
1151
|
if (exists) {
|
|
1152
|
-
|
|
1153
|
-
// are still initializing
|
|
1154
|
-
await this.waitForCollectionShards(index, collection);
|
|
1155
|
-
return this.updateCollection(index, collection, { mappings, settings });
|
|
1152
|
+
return this._updateConcurrentlyCreatedCollection(index, collection, mappings, settings);
|
|
1156
1153
|
}
|
|
1157
1154
|
this._checkMappings(mappings);
|
|
1155
|
+
// Copies, not in-place changes: the race branch below must hand the
|
|
1156
|
+
// caller's own mappings and settings to `updateCollection`
|
|
1158
1157
|
requestBody.mappings = {
|
|
1159
1158
|
_meta: mappings._meta || this._config.commonMapping._meta,
|
|
1160
1159
|
dynamic: mappings.dynamic || this._config.commonMapping.dynamic,
|
|
1161
|
-
properties: lodash_1.default.merge(mappings.properties, this._config.commonMapping.properties),
|
|
1160
|
+
properties: lodash_1.default.merge({}, mappings.properties, this._config.commonMapping.properties),
|
|
1161
|
+
};
|
|
1162
|
+
requestBody.settings = {
|
|
1163
|
+
...settings,
|
|
1164
|
+
number_of_replicas: settings.number_of_replicas ||
|
|
1165
|
+
this._config.defaultSettings.number_of_replicas,
|
|
1166
|
+
number_of_shards: settings.number_of_shards ||
|
|
1167
|
+
this._config.defaultSettings.number_of_shards,
|
|
1162
1168
|
};
|
|
1163
|
-
settings.number_of_replicas =
|
|
1164
|
-
settings.number_of_replicas ||
|
|
1165
|
-
this._config.defaultSettings.number_of_replicas;
|
|
1166
|
-
settings.number_of_shards =
|
|
1167
|
-
settings.number_of_shards ||
|
|
1168
|
-
this._config.defaultSettings.number_of_shards;
|
|
1169
1169
|
try {
|
|
1170
1170
|
await this._client.indices.create(esRequest);
|
|
1171
1171
|
}
|
|
@@ -1174,9 +1174,9 @@ class ES7 {
|
|
|
1174
1174
|
"resource_already_exists_exception") {
|
|
1175
1175
|
// race condition: the indice has been created between the "exists"
|
|
1176
1176
|
// check above and this "create" attempt, by a request that may not
|
|
1177
|
-
// have waited for its shards yet
|
|
1178
|
-
|
|
1179
|
-
return
|
|
1177
|
+
// have waited for its shards yet. This request's mappings and
|
|
1178
|
+
// settings must still be applied, as if it had seen it exist
|
|
1179
|
+
return this._updateConcurrentlyCreatedCollection(index, collection, mappings, settings);
|
|
1180
1180
|
}
|
|
1181
1181
|
throw this._esWrapper.formatESError(error);
|
|
1182
1182
|
}
|
|
@@ -1195,6 +1195,15 @@ class ES7 {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
return null;
|
|
1197
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Applies the mappings and settings of a `createCollection` call to a
|
|
1200
|
+
* collection that already exists because another request created it,
|
|
1201
|
+
* maybe so recently that its primary shards are still initializing.
|
|
1202
|
+
*/
|
|
1203
|
+
async _updateConcurrentlyCreatedCollection(index, collection, mappings, settings) {
|
|
1204
|
+
await this.waitForCollectionShards(index, collection);
|
|
1205
|
+
return this.updateCollection(index, collection, { mappings, settings });
|
|
1206
|
+
}
|
|
1198
1207
|
/**
|
|
1199
1208
|
* Waits until the primary shards of a collection are active, for at most
|
|
1200
1209
|
* `timeout`, and rejects if they are not by then.
|
|
@@ -1205,12 +1214,25 @@ class ES7 {
|
|
|
1205
1214
|
* collection waits for its shards (`wait_for_active_shards`); a node that
|
|
1206
1215
|
* finds it already there calls this instead. It answers at once when the
|
|
1207
1216
|
* shards are already active.
|
|
1217
|
+
*
|
|
1218
|
+
* A `yellow` status alone does not wait: Elasticsearch reports a new index
|
|
1219
|
+
* whose primaries are still initializing as `yellow` (#3074). Asking for as
|
|
1220
|
+
* many active shard copies as the index has primaries does, and, unlike
|
|
1221
|
+
* `wait_for_no_initializing_shards`, does not also wait for replicas that
|
|
1222
|
+
* are recovering, which a collection with data may take longer to do.
|
|
1208
1223
|
*/
|
|
1209
1224
|
async waitForCollectionShards(index, collection, timeout = COLLECTION_SHARDS_TIMEOUT) {
|
|
1225
|
+
const alias = this._getAlias(index, collection);
|
|
1210
1226
|
try {
|
|
1227
|
+
const { body } = await this._client.indices.getSettings({
|
|
1228
|
+
index: alias,
|
|
1229
|
+
name: "index.number_of_shards",
|
|
1230
|
+
});
|
|
1231
|
+
const primaries = Object.values(body)[0]?.settings?.index?.number_of_shards;
|
|
1211
1232
|
await this._client.cluster.health({
|
|
1212
|
-
index:
|
|
1233
|
+
index: alias,
|
|
1213
1234
|
timeout,
|
|
1235
|
+
wait_for_active_shards: String(primaries ?? 1),
|
|
1214
1236
|
wait_for_status: "yellow",
|
|
1215
1237
|
});
|
|
1216
1238
|
}
|
|
@@ -443,6 +443,12 @@ export declare class ES8 {
|
|
|
443
443
|
mappings?: estypes.MappingTypeMapping;
|
|
444
444
|
settings?: estypes.IndicesIndexSettings;
|
|
445
445
|
}): Promise<null>;
|
|
446
|
+
/**
|
|
447
|
+
* Applies the mappings and settings of a `createCollection` call to a
|
|
448
|
+
* collection that already exists because another request created it,
|
|
449
|
+
* maybe so recently that its primary shards are still initializing.
|
|
450
|
+
*/
|
|
451
|
+
private _updateConcurrentlyCreatedCollection;
|
|
446
452
|
/**
|
|
447
453
|
* Waits until the primary shards of a collection are active, for at most
|
|
448
454
|
* `timeout`, and rejects if they are not by then.
|
|
@@ -453,6 +459,12 @@ export declare class ES8 {
|
|
|
453
459
|
* collection waits for its shards (`wait_for_active_shards`); a node that
|
|
454
460
|
* finds it already there calls this instead. It answers at once when the
|
|
455
461
|
* shards are already active.
|
|
462
|
+
*
|
|
463
|
+
* A `yellow` status alone does not wait: Elasticsearch reports a new index
|
|
464
|
+
* whose primaries are still initializing as `yellow` (#3074). Asking for as
|
|
465
|
+
* many active shard copies as the index has primaries does, and, unlike
|
|
466
|
+
* `wait_for_no_initializing_shards`, does not also wait for replicas that
|
|
467
|
+
* are recovering, which a collection with data may take longer to do.
|
|
456
468
|
*/
|
|
457
469
|
waitForCollectionShards(index: string, collection: string, timeout?: string): Promise<void>;
|
|
458
470
|
/**
|
|
@@ -1171,23 +1171,23 @@ class ES8 {
|
|
|
1171
1171
|
this._checkDynamicProperty(mappings);
|
|
1172
1172
|
const exists = await this.hasCollection(index, collection);
|
|
1173
1173
|
if (exists) {
|
|
1174
|
-
|
|
1175
|
-
// are still initializing
|
|
1176
|
-
await this.waitForCollectionShards(index, collection);
|
|
1177
|
-
return this.updateCollection(index, collection, { mappings, settings });
|
|
1174
|
+
return this._updateConcurrentlyCreatedCollection(index, collection, mappings, settings);
|
|
1178
1175
|
}
|
|
1179
1176
|
this._checkMappings(mappings);
|
|
1177
|
+
// Copies, not in-place changes: the race branch below must hand the
|
|
1178
|
+
// caller's own mappings and settings to `updateCollection`
|
|
1180
1179
|
esRequest.mappings = {
|
|
1181
1180
|
_meta: mappings._meta || this._config.commonMapping._meta,
|
|
1182
1181
|
dynamic: mappings.dynamic || this._config.commonMapping.dynamic,
|
|
1183
|
-
properties: lodash_1.default.merge(mappings.properties, this._config.commonMapping.properties),
|
|
1182
|
+
properties: lodash_1.default.merge({}, mappings.properties, this._config.commonMapping.properties),
|
|
1183
|
+
};
|
|
1184
|
+
esRequest.settings = {
|
|
1185
|
+
...settings,
|
|
1186
|
+
number_of_replicas: settings.number_of_replicas ||
|
|
1187
|
+
this._config.defaultSettings.number_of_replicas,
|
|
1188
|
+
number_of_shards: settings.number_of_shards ||
|
|
1189
|
+
this._config.defaultSettings.number_of_shards,
|
|
1184
1190
|
};
|
|
1185
|
-
settings.number_of_replicas =
|
|
1186
|
-
settings.number_of_replicas ||
|
|
1187
|
-
this._config.defaultSettings.number_of_replicas;
|
|
1188
|
-
settings.number_of_shards =
|
|
1189
|
-
settings.number_of_shards ||
|
|
1190
|
-
this._config.defaultSettings.number_of_shards;
|
|
1191
1191
|
try {
|
|
1192
1192
|
await this._client.indices.create(esRequest);
|
|
1193
1193
|
}
|
|
@@ -1196,9 +1196,9 @@ class ES8 {
|
|
|
1196
1196
|
"resource_already_exists_exception") {
|
|
1197
1197
|
// race condition: the indice has been created between the "exists"
|
|
1198
1198
|
// check above and this "create" attempt, by a request that may not
|
|
1199
|
-
// have waited for its shards yet
|
|
1200
|
-
|
|
1201
|
-
return
|
|
1199
|
+
// have waited for its shards yet. This request's mappings and
|
|
1200
|
+
// settings must still be applied, as if it had seen it exist
|
|
1201
|
+
return this._updateConcurrentlyCreatedCollection(index, collection, mappings, settings);
|
|
1202
1202
|
}
|
|
1203
1203
|
throw this._esWrapper.formatESError(error);
|
|
1204
1204
|
}
|
|
@@ -1217,6 +1217,15 @@ class ES8 {
|
|
|
1217
1217
|
}
|
|
1218
1218
|
return null;
|
|
1219
1219
|
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Applies the mappings and settings of a `createCollection` call to a
|
|
1222
|
+
* collection that already exists because another request created it,
|
|
1223
|
+
* maybe so recently that its primary shards are still initializing.
|
|
1224
|
+
*/
|
|
1225
|
+
async _updateConcurrentlyCreatedCollection(index, collection, mappings, settings) {
|
|
1226
|
+
await this.waitForCollectionShards(index, collection);
|
|
1227
|
+
return this.updateCollection(index, collection, { mappings, settings });
|
|
1228
|
+
}
|
|
1220
1229
|
/**
|
|
1221
1230
|
* Waits until the primary shards of a collection are active, for at most
|
|
1222
1231
|
* `timeout`, and rejects if they are not by then.
|
|
@@ -1227,12 +1236,25 @@ class ES8 {
|
|
|
1227
1236
|
* collection waits for its shards (`wait_for_active_shards`); a node that
|
|
1228
1237
|
* finds it already there calls this instead. It answers at once when the
|
|
1229
1238
|
* shards are already active.
|
|
1239
|
+
*
|
|
1240
|
+
* A `yellow` status alone does not wait: Elasticsearch reports a new index
|
|
1241
|
+
* whose primaries are still initializing as `yellow` (#3074). Asking for as
|
|
1242
|
+
* many active shard copies as the index has primaries does, and, unlike
|
|
1243
|
+
* `wait_for_no_initializing_shards`, does not also wait for replicas that
|
|
1244
|
+
* are recovering, which a collection with data may take longer to do.
|
|
1230
1245
|
*/
|
|
1231
1246
|
async waitForCollectionShards(index, collection, timeout = COLLECTION_SHARDS_TIMEOUT) {
|
|
1247
|
+
const alias = this._getAlias(index, collection);
|
|
1232
1248
|
try {
|
|
1249
|
+
const body = await this._client.indices.getSettings({
|
|
1250
|
+
index: alias,
|
|
1251
|
+
name: "index.number_of_shards",
|
|
1252
|
+
});
|
|
1253
|
+
const primaries = Object.values(body)[0]?.settings?.index?.number_of_shards;
|
|
1233
1254
|
await this._client.cluster.health({
|
|
1234
|
-
index:
|
|
1255
|
+
index: alias,
|
|
1235
1256
|
timeout,
|
|
1257
|
+
wait_for_active_shards: Number(primaries ?? 1),
|
|
1236
1258
|
wait_for_status: "yellow",
|
|
1237
1259
|
});
|
|
1238
1260
|
}
|
|
@@ -163,6 +163,13 @@ export interface IKuzzleConfiguration {
|
|
|
163
163
|
* Optional here for the reason given on {@link IKuzzleConfiguration.version}.
|
|
164
164
|
*/
|
|
165
165
|
allowAllOrigins?: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Derived at startup: the deprecated configuration keys the user set
|
|
168
|
+
* (today, those of "security.jwt"), logged as a warning.
|
|
169
|
+
*
|
|
170
|
+
* Optional here for the reason given on {@link IKuzzleConfiguration.version}.
|
|
171
|
+
*/
|
|
172
|
+
deprecatedSettings?: string[];
|
|
166
173
|
};
|
|
167
174
|
/**
|
|
168
175
|
* Collection specifications to apply at startup, by index then collection.
|
|
@@ -21,9 +21,10 @@ export type SecurityConfiguration = {
|
|
|
21
21
|
/**
|
|
22
22
|
* @deprecated Use `security.authToken` instead.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* v2.56.0 declared it:
|
|
24
|
+
* Each setting the configuration sets here applies unless `authToken` sets
|
|
25
|
+
* it too (`loadConfig()` resolves both sections to the values in effect,
|
|
26
|
+
* see `authTokenSetting`). Optional all the same, as v2.56.0 declared it:
|
|
27
|
+
* a required member breaks code that builds this type.
|
|
27
28
|
*/
|
|
28
29
|
jwt?: JSONObject;
|
|
29
30
|
/**
|
|
@@ -54,8 +54,8 @@ export type ControllerDefinition = {
|
|
|
54
54
|
handler: (request: KuzzleRequest) => Promise<any>;
|
|
55
55
|
/**
|
|
56
56
|
* Declare HTTP routes (optional).
|
|
57
|
-
*
|
|
58
|
-
*
|
|
57
|
+
* When omitted or empty, a default route is generated:
|
|
58
|
+
* `GET /_/<controller>/<action>`, both names in kebab-case.
|
|
59
59
|
*/
|
|
60
60
|
http?: HttpRoute[];
|
|
61
61
|
};
|
|
@@ -71,8 +71,8 @@ export type HttpRoute = {
|
|
|
71
71
|
verb: "get" | "head" | "post" | "put" | "delete" | "patch" | "options";
|
|
72
72
|
/**
|
|
73
73
|
* Route path.
|
|
74
|
-
* A route starting with `/`
|
|
75
|
-
*
|
|
74
|
+
* A route starting with `/` is used as is, any other route is prefixed
|
|
75
|
+
* with `/_/`.
|
|
76
76
|
*/
|
|
77
77
|
path: string;
|
|
78
78
|
/**
|
package/dist/lib/util/mutex.d.ts
CHANGED
|
@@ -86,9 +86,9 @@ export declare class Mutex {
|
|
|
86
86
|
*
|
|
87
87
|
* @return {Promise.<boolean>} True if the ressource has been unlocked before the `timeout`
|
|
88
88
|
*/
|
|
89
|
-
wait({ attemptDelay, timeout, }
|
|
90
|
-
attemptDelay?: number
|
|
91
|
-
timeout?: number
|
|
89
|
+
wait({ attemptDelay, timeout, }?: {
|
|
90
|
+
attemptDelay?: number;
|
|
91
|
+
timeout?: number;
|
|
92
92
|
}): Promise<boolean>;
|
|
93
93
|
/**
|
|
94
94
|
* Frees every lock this process holds, and stops it from taking new ones.
|
package/dist/lib/util/mutex.js
CHANGED
|
@@ -196,7 +196,7 @@ class Mutex {
|
|
|
196
196
|
*
|
|
197
197
|
* @return {Promise.<boolean>} True if the ressource has been unlocked before the `timeout`
|
|
198
198
|
*/
|
|
199
|
-
async wait({ attemptDelay = this.attemptDelay, timeout = this.timeout, }) {
|
|
199
|
+
async wait({ attemptDelay = this.attemptDelay, timeout = this.timeout, } = {}) {
|
|
200
200
|
let duration = 0;
|
|
201
201
|
let isLocked;
|
|
202
202
|
do {
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kuzzle",
|
|
3
3
|
"author": "The Kuzzle Team <support@kuzzle.io>",
|
|
4
|
-
"version": "2.60.0-beta.
|
|
4
|
+
"version": "2.60.0-beta.6",
|
|
5
5
|
"description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "rm -Rf ./dist && tsc && node -r ts-node/register/transpile-only ./bin/copy-binaries.ts",
|