@codefresh-io/service-base 7.4.1 → 8.0.1-alpha.1
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/index.js +7 -5
- package/infra/__tests__/smoke_test.spec.js +8 -3
- package/infra/config.js +5 -8
- package/infra/encryption/safe.js +4 -7
- package/infra/eventbus.js +81 -85
- package/infra/express.js +137 -127
- package/infra/global-logger.js +7 -0
- package/infra/helper.js +3 -4
- package/infra/index.js +22 -20
- package/infra/logging.js +29 -22
- package/infra/mongo.js +42 -22
- package/infra/process-events.js +24 -25
- package/infra/redis.js +27 -19
- package/infra/validation.js +6 -1
- package/package.json +20 -17
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
/* eslint-disable import/order */
|
|
3
3
|
const config = require('./infra/config');
|
|
4
4
|
const service = require('./infra');
|
|
5
5
|
const {
|
|
@@ -13,6 +13,7 @@ const express = require('express');
|
|
|
13
13
|
const monitor = require('@codefresh-io/cf-monitor');
|
|
14
14
|
const logging = require('./infra/logging');
|
|
15
15
|
const { openapi } = require('@codefresh-io/cf-openapi');
|
|
16
|
+
const expressInfra = require('./infra/express');
|
|
16
17
|
|
|
17
18
|
const OPTIONAL_COMPONENTS = {
|
|
18
19
|
mongo: { name: 'mongoClient' },
|
|
@@ -26,15 +27,15 @@ const exportedComponents = {
|
|
|
26
27
|
initService: service.init.bind(service),
|
|
27
28
|
stopService: service.stop.bind(service),
|
|
28
29
|
validation: require('./infra/validation'),
|
|
29
|
-
makeEndpoint:
|
|
30
|
+
makeEndpoint: expressInfra.makeEndpoint.bind(expressInfra),
|
|
30
31
|
getAuthenticatedEntity,
|
|
31
32
|
setAuthenticatedEntity,
|
|
32
33
|
request,
|
|
33
34
|
authEntity,
|
|
34
35
|
Promise,
|
|
35
36
|
express,
|
|
36
|
-
expressApp:
|
|
37
|
-
getLogger: logging.getLogger,
|
|
37
|
+
expressApp: expressInfra.expressApp,
|
|
38
|
+
getLogger: logging.getLogger.bind(logging),
|
|
38
39
|
config,
|
|
39
40
|
monitor,
|
|
40
41
|
};
|
|
@@ -50,6 +51,7 @@ enabledComponents.forEach((key) => {
|
|
|
50
51
|
throw new Error(`Component '${key}'' is dependent on component '${dependency}' which is missing.`);
|
|
51
52
|
}
|
|
52
53
|
});
|
|
54
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
53
55
|
exportedComponents[component.name || key] = require(`./infra/${key}`);
|
|
54
56
|
});
|
|
55
57
|
|
|
@@ -6,21 +6,26 @@ const mongoClient = require('../mongo');
|
|
|
6
6
|
* @type {MongoMemoryServer}
|
|
7
7
|
*/
|
|
8
8
|
let mongod;
|
|
9
|
+
|
|
9
10
|
async function clientSetupByUriAndDbName() {
|
|
10
11
|
const uri = mongod.getUri();
|
|
11
12
|
const { port, dbPath, dbName } = mongod.instanceInfo;
|
|
12
|
-
console.log(`using port ${port}`);
|
|
13
|
-
console.log(`dbPath ${dbPath}`);
|
|
14
|
-
console.log(`dbName ${dbName}`);
|
|
15
13
|
|
|
16
14
|
await mongoClient.init({ mongo: { uri, dbName } });
|
|
17
15
|
return { dbName, dbPath, uri, port };
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
describe('mongo init compatibility code', () => {
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
// Dummy warmup to download MongoDB binaries before running tests, to avoid timeout on the first test
|
|
21
|
+
const warmUpInstance = await MongoMemoryServer.create();
|
|
22
|
+
await warmUpInstance.stop();
|
|
23
|
+
}, 60 * 1000);
|
|
24
|
+
|
|
21
25
|
beforeEach(async () => {
|
|
22
26
|
mongod = await MongoMemoryServer.create();
|
|
23
27
|
});
|
|
28
|
+
|
|
24
29
|
afterEach(async () => {
|
|
25
30
|
await mongoClient.stop();
|
|
26
31
|
await mongod.stop();
|
package/infra/config.js
CHANGED
|
@@ -63,7 +63,9 @@ base.postgres = {
|
|
|
63
63
|
base.mongo = {
|
|
64
64
|
uri: process.env.MONGO_URI || `mongodb://${APPLICATION_DOMAIN}/${name}`,
|
|
65
65
|
/** @type {import('mongodb').MongoClientOptions} */
|
|
66
|
-
options: {
|
|
66
|
+
options: {
|
|
67
|
+
monitorCommands: true,
|
|
68
|
+
},
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
if (process.env.MTLS_CERT_PATH) {
|
|
@@ -110,7 +112,7 @@ base.logger = {
|
|
|
110
112
|
correlationId: () => {
|
|
111
113
|
try {
|
|
112
114
|
return getRequestId();
|
|
113
|
-
} catch
|
|
115
|
+
} catch {
|
|
114
116
|
return {};
|
|
115
117
|
}
|
|
116
118
|
},
|
|
@@ -121,18 +123,13 @@ base.logger = {
|
|
|
121
123
|
authEntity.activeAccount = _.omit(authEntity.activeAccount, 'features');
|
|
122
124
|
}
|
|
123
125
|
return authEntity;
|
|
124
|
-
} catch
|
|
126
|
+
} catch {
|
|
125
127
|
return {};
|
|
126
128
|
}
|
|
127
129
|
},
|
|
128
130
|
},
|
|
129
131
|
};
|
|
130
132
|
|
|
131
|
-
base.httpLogger = {
|
|
132
|
-
level: process.env.HTTP_LOGGER_LEVEL || 'debug',
|
|
133
|
-
format: 'dev',
|
|
134
|
-
};
|
|
135
|
-
|
|
136
133
|
base.httpQueryParser = {
|
|
137
134
|
allowPrototypes: true,
|
|
138
135
|
arrayLimit: process.env.URI_QUERY_ARRAY_MAX_LENGTH || 100,
|
package/infra/encryption/safe.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
const { randomUUID } = require('node:crypto');
|
|
2
1
|
const _ = require('lodash');
|
|
3
|
-
const
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
|
|
2
|
+
const crypto = require('node:crypto');
|
|
6
3
|
const config = require('../config');
|
|
7
4
|
const mongoClient = require('../mongo');
|
|
8
5
|
|
|
@@ -43,7 +40,7 @@ function getOrCreateSafe(safeId) {
|
|
|
43
40
|
|
|
44
41
|
const newSafe = {
|
|
45
42
|
_id: safeId,
|
|
46
|
-
key: (Buffer.from(randomUUID())).toString('base64'),
|
|
43
|
+
key: (Buffer.from(crypto.randomUUID())).toString('base64'),
|
|
47
44
|
};
|
|
48
45
|
return collection.insertOne(newSafe)
|
|
49
46
|
.then(() => new Safe(newSafe))
|
|
@@ -56,7 +53,7 @@ function getOrCreateSafe(safeId) {
|
|
|
56
53
|
// decrypt cipher text: try to decrypt with crypto library first
|
|
57
54
|
// if did not find CRYPTO_PREFIX prefix, try to use old triplesec decryption
|
|
58
55
|
Safe.prototype.read_crypto = function (ciphertext) { // eslint-disable-line
|
|
59
|
-
const deferred = Promise.
|
|
56
|
+
const deferred = Promise.withResolvers();
|
|
60
57
|
|
|
61
58
|
const key = Buffer.alloc(32, _.get(config, 'safe.secret'));
|
|
62
59
|
const iv = Buffer.alloc(16, this.safeModel.key);
|
|
@@ -85,7 +82,7 @@ Safe.prototype.read_crypto = function (ciphertext) { // eslint-disable-line
|
|
|
85
82
|
|
|
86
83
|
// encrypt text and add CRYPTO_PREFIX prefix (to mark the new encryption)
|
|
87
84
|
Safe.prototype.write_crypto = function (plaintext) { // eslint-disable-line
|
|
88
|
-
const deferred = Promise.
|
|
85
|
+
const deferred = Promise.withResolvers();
|
|
89
86
|
const key = Buffer.alloc(32, _.get(config, 'safe.secret'));
|
|
90
87
|
const iv = Buffer.alloc(16, this.safeModel.key);
|
|
91
88
|
|
package/infra/eventbus.js
CHANGED
|
@@ -1,118 +1,114 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
2
|
const eventBus = require('@codefresh-io/eventbus');
|
|
3
3
|
const monitor = require('@codefresh-io/cf-monitor');
|
|
4
|
-
const
|
|
4
|
+
const { globalLogger } = require('./global-logger');
|
|
5
5
|
|
|
6
6
|
class Eventbus {
|
|
7
|
+
#logger = globalLogger.child(this.constructor.name);
|
|
8
|
+
|
|
7
9
|
constructor() {
|
|
8
10
|
this.eventbusInitialized = false;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
+
* Starts the connection to eventbus
|
|
15
|
+
* @param {any} config
|
|
16
|
+
* @returns {Promise<Eventbus>}
|
|
14
17
|
*/
|
|
15
18
|
init(config) {
|
|
16
|
-
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
microServiceName: this.config.eventbus.serviceName,
|
|
43
|
-
});
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.#logger.info('Initializing Eventbus connection');
|
|
21
|
+
/** @type {PromiseWithResolvers<Eventbus>} */
|
|
22
|
+
const deferred = Promise.withResolvers();
|
|
23
|
+
|
|
24
|
+
// TODO a fallback for case where rabbitmq is not up. should be removed once rabbitmq is fully used
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
deferred.resolve(this);
|
|
27
|
+
}, 30000);
|
|
28
|
+
|
|
29
|
+
eventBus.init({
|
|
30
|
+
bus: {
|
|
31
|
+
url: this.config.eventbus.uri,
|
|
32
|
+
reconnectInterval: 5,
|
|
33
|
+
},
|
|
34
|
+
store: {
|
|
35
|
+
host: this.config.postgres.host,
|
|
36
|
+
port: this.config.postgres.port,
|
|
37
|
+
database: this.config.postgres.database,
|
|
38
|
+
user: this.config.postgres.user,
|
|
39
|
+
password: this.config.postgres.password,
|
|
40
|
+
ssl: this.config.postgres.ssl,
|
|
41
|
+
},
|
|
42
|
+
microServiceName: this.config.eventbus.serviceName,
|
|
43
|
+
});
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
eventBus.on('ready', () => {
|
|
46
|
+
this.#logger.info('Eventbus ready');
|
|
47
|
+
this.eventbusInitialized = true;
|
|
48
|
+
deferred.resolve(this);
|
|
49
|
+
});
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
51
|
+
eventBus.on('error', (err) => {
|
|
52
|
+
this.#logger.error('Eventbus error', err);
|
|
53
|
+
monitor.noticeError(new Error(`Eventbus error: ${err.stack}`));
|
|
54
|
+
});
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
});
|
|
56
|
+
return deferred.promise;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* stops the connection to eventbus
|
|
63
|
-
* @returns {
|
|
61
|
+
* @returns {Promise<void>}
|
|
64
62
|
*/
|
|
65
63
|
stop() {
|
|
66
|
-
|
|
67
|
-
if (!this.eventbusInitialized) {
|
|
68
|
-
return Promise.resolve();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const deferred = Promise.defer();
|
|
64
|
+
if (!this.eventbusInitialized) return Promise.resolve();
|
|
72
65
|
|
|
66
|
+
/** @type {PromiseWithResolvers<void>} */
|
|
67
|
+
const deferred = Promise.withResolvers();
|
|
73
68
|
eventBus.on('close', () => {
|
|
74
|
-
logger.info('Eventbus client closed');
|
|
69
|
+
this.#logger.info('Eventbus client closed');
|
|
75
70
|
deferred.resolve();
|
|
76
71
|
});
|
|
77
|
-
|
|
78
72
|
eventBus.quit();
|
|
79
|
-
|
|
80
73
|
return deferred.promise;
|
|
81
74
|
}
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* @param {unknown} eventName
|
|
79
|
+
* @param {unknown} handler
|
|
80
|
+
* @param {unknown} options
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
83
|
+
async subscribe(eventName, handler, options) {
|
|
84
|
+
const logger = this.#logger.child('subscribe');
|
|
85
|
+
const listener = await eventBus.subscribe(eventName, handler, options);
|
|
86
|
+
logger.info(`Subscribed to event "${eventName}"`);
|
|
87
|
+
listener.on('error', (/** @type {unknown} */ err) => {
|
|
88
|
+
logger.error(`Handler for event "${eventName}" failed`, err);
|
|
89
|
+
monitor.noticeError(err);
|
|
90
|
+
});
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
monitor.noticeError(error);
|
|
93
|
+
/**
|
|
94
|
+
* @param {unknown} eventName
|
|
95
|
+
* @param {unknown} data
|
|
96
|
+
* @param {boolean} [shouldReturnPromise]
|
|
97
|
+
* @returns {Promise<void>|void}
|
|
98
|
+
*/
|
|
99
|
+
// eslint-disable-next-line consistent-return
|
|
100
|
+
publish(eventName, data, shouldReturnPromise = false) {
|
|
101
|
+
const logger = this.#logger.child('publish');
|
|
102
|
+
logger.debug(`Publishing event "${eventName}"`);
|
|
103
|
+
const publishResultPromise = eventBus.publish(eventName, data);
|
|
104
|
+
|
|
105
|
+
if (shouldReturnPromise) return publishResultPromise;
|
|
106
|
+
|
|
107
|
+
publishResultPromise
|
|
108
|
+
.then(() => logger.debug(`Event "${eventName}" published successfully`))
|
|
109
|
+
.catch((error) => {
|
|
110
|
+
logger.error(`Failed to publish event "${eventName}"`, error);
|
|
111
|
+
monitor.noticeError(new Error(`Failed to publish event "${eventName}"`, { cause: error }));
|
|
116
112
|
});
|
|
117
113
|
}
|
|
118
114
|
}
|
package/infra/express.js
CHANGED
|
@@ -1,161 +1,171 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
const { httpLoggerMiddlewareFactory } = require('@codefresh-io/cf-telemetry/logs/express');
|
|
3
|
+
const { Logger } = require('@codefresh-io/cf-telemetry/logs');
|
|
2
4
|
const express = require('express');
|
|
3
5
|
const qs = require('qs');
|
|
4
6
|
const compression = require('compression');
|
|
5
7
|
const bodyParser = require('body-parser');
|
|
6
8
|
const methodOverride = require('method-override');
|
|
7
9
|
const cookieParser = require('cookie-parser');
|
|
8
|
-
const morgan = require('morgan');
|
|
9
10
|
const monitor = require('@codefresh-io/cf-monitor');
|
|
11
|
+
// @ts-ignore
|
|
10
12
|
const { newDomainMiddleware } = require('@codefresh-io/http-infra');
|
|
11
13
|
const { openapi } = require('@codefresh-io/cf-openapi');
|
|
12
14
|
const CFError = require('cf-errors');
|
|
15
|
+
const { globalLogger } = require('./global-logger');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {import('express').Application } ExpressApp
|
|
19
|
+
* @typedef {import('express').RequestHandler} RequestHandler
|
|
20
|
+
* @typedef {import('express').ErrorRequestHandler} ErrorRequestHandler
|
|
21
|
+
* @typedef {import('express').Request} Request
|
|
22
|
+
* @typedef {import('express').Response} Response
|
|
23
|
+
* @typedef {import('express').NextFunction} NextFunction
|
|
24
|
+
* @typedef {import('node:http').Server} HttpServer
|
|
25
|
+
*/
|
|
13
26
|
|
|
14
27
|
class Express {
|
|
28
|
+
#logger = globalLogger.child(this.constructor.name);
|
|
29
|
+
|
|
15
30
|
constructor() {
|
|
16
|
-
|
|
17
|
-
this.
|
|
31
|
+
/** @type {ExpressApp} */
|
|
32
|
+
this.expressApp = express();
|
|
33
|
+
/** @type {HttpServer|undefined} */
|
|
34
|
+
this.expressServer = undefined;
|
|
35
|
+
/** @type {boolean} */
|
|
18
36
|
this.healthy = true;
|
|
19
37
|
}
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
42
|
+
async stop() {
|
|
43
|
+
this.#logger.info('Stopping Express server');
|
|
44
|
+
if (!this.expressServer) return;
|
|
45
|
+
this.expressServer.close();
|
|
24
46
|
}
|
|
25
47
|
|
|
26
48
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
49
|
+
* Initializes the Express server.
|
|
50
|
+
* @param {any} config
|
|
51
|
+
* @param {any} createRoutes
|
|
52
|
+
* @param {any} [opt]
|
|
53
|
+
* @returns {Promise<void>}
|
|
29
54
|
*/
|
|
30
|
-
init(config, createRoutes, opt = {}) {
|
|
31
|
-
const logger = require('cf-logs').Logger('codefresh:infra:express'); // eslint-disable-line
|
|
32
|
-
this.logger = logger;
|
|
55
|
+
async init(config, createRoutes, opt = {}) {
|
|
33
56
|
this.options = opt;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.then((expressServer) => {
|
|
41
|
-
this.expressServer = expressServer;
|
|
42
|
-
})
|
|
43
|
-
.then(() => {
|
|
44
|
-
openapi.events().subscribe();
|
|
45
|
-
openapi.events().publish();
|
|
46
|
-
});
|
|
47
|
-
});
|
|
57
|
+
this.config = config;
|
|
58
|
+
this.createRoutes = createRoutes;
|
|
59
|
+
await this._create();
|
|
60
|
+
this.expressServer = await this._start(this.expressApp);
|
|
61
|
+
openapi.events().subscribe();
|
|
62
|
+
openapi.events().publish();
|
|
48
63
|
}
|
|
49
64
|
|
|
50
|
-
_create() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
async _create() {
|
|
66
|
+
/** Should be the first middleware */
|
|
67
|
+
this.expressApp.use(httpLoggerMiddlewareFactory(new Logger('http')));
|
|
68
|
+
|
|
69
|
+
this.expressApp.set('query parser', (/** @type {string} */ str) => qs.parse(str, this.config.httpQueryParser));
|
|
70
|
+
|
|
71
|
+
this.expressApp.use(newDomainMiddleware());
|
|
72
|
+
|
|
73
|
+
this.expressApp.use(cookieParser());
|
|
74
|
+
this.expressApp.use(compression());
|
|
75
|
+
|
|
76
|
+
openapi.endpoints().registerUtilityMiddleware(this.expressApp);
|
|
77
|
+
this.expressApp.use(bodyParser.json());
|
|
78
|
+
|
|
79
|
+
this.expressApp.use(bodyParser.urlencoded({ extended: true }));
|
|
80
|
+
this.expressApp.use(methodOverride());
|
|
81
|
+
|
|
82
|
+
openapi.endpoints().register(this.expressApp);
|
|
83
|
+
await this.createRoutes(this.expressApp);
|
|
84
|
+
|
|
85
|
+
this.expressApp.get('/api/ping', (req, res) => {
|
|
86
|
+
res.status(200).send();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.expressApp.get('/api/ready', (req, res) => {
|
|
90
|
+
if (this.options.isReady()) {
|
|
91
|
+
res.status(200).send();
|
|
92
|
+
} else {
|
|
93
|
+
res.status(503).send();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
this.expressApp.get('/api/health', (req, res) => {
|
|
98
|
+
if (this.options.isHealthy()) {
|
|
99
|
+
res.status(200).send();
|
|
100
|
+
} else {
|
|
101
|
+
res.status(503).send();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/** @type {ErrorRequestHandler} */
|
|
106
|
+
const errorHandler = (err, _req, res, next) => {
|
|
107
|
+
this.#logger.error('Request handler error', err);
|
|
108
|
+
|
|
109
|
+
if (res.headersSent) {
|
|
110
|
+
next(err);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (err instanceof CFError) {
|
|
115
|
+
if (!err.getFirstValue('recognized')) {
|
|
116
|
+
monitor.noticeError(err);
|
|
83
117
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
app.get('/api/health', (req, res) => {
|
|
100
|
-
if (this.options.isHealthy()) {
|
|
101
|
-
res.status(200).send();
|
|
102
|
-
} else {
|
|
103
|
-
res.status(503).send();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// the last error handler
|
|
108
|
-
app.use((err, req, res, next) => { // eslint-disable-line
|
|
109
|
-
logger.error(err.stack);
|
|
110
|
-
|
|
111
|
-
if (res.headersSent) {
|
|
112
|
-
return next(err);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (err instanceof CFError) {
|
|
116
|
-
if (!err.getFirstValue('recognized')) {
|
|
117
|
-
monitor.noticeError(err);
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
monitor.noticeError(err);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const statusCode = err.statusCode || 500;
|
|
124
|
-
// check if err object has overridden toString method
|
|
125
|
-
// before sending toString() response to prevent [object Object] responses
|
|
126
|
-
const message = err.toString === Object.prototype.toString
|
|
127
|
-
? (err.message || 'Internal server error')
|
|
128
|
-
: err.toString();
|
|
129
|
-
res.status(statusCode).send({ message });
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
});
|
|
118
|
+
} else {
|
|
119
|
+
monitor.noticeError(err);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const statusCode = err.statusCode || 500;
|
|
123
|
+
// check if err object has overridden toString method
|
|
124
|
+
// before sending toString() response to prevent [object Object] responses
|
|
125
|
+
const message = err.toString === Object.prototype.toString
|
|
126
|
+
? (err.message || 'Internal server error')
|
|
127
|
+
: err.toString();
|
|
128
|
+
res.status(statusCode).send({ message });
|
|
129
|
+
};
|
|
130
|
+
/** Should be the last middleware */
|
|
131
|
+
this.expressApp.use(errorHandler);
|
|
133
132
|
}
|
|
134
133
|
|
|
134
|
+
/**
|
|
135
|
+
* @param {ExpressApp} app
|
|
136
|
+
* @returns {Promise<HttpServer>}
|
|
137
|
+
*/
|
|
135
138
|
_start(app) {
|
|
136
|
-
|
|
139
|
+
this.#logger.info('Starting Express server');
|
|
140
|
+
|
|
137
141
|
openapi.dependencies().fetch();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
/** @type {PromiseWithResolvers<HttpServer>} */
|
|
143
|
+
const deferred = Promise.withResolvers();
|
|
144
|
+
const server = app.listen(this.config.port, (err) => {
|
|
145
|
+
if (err) {
|
|
146
|
+
this.#logger.error('Failed to start Express server', err);
|
|
147
|
+
deferred.reject(err);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.#logger.info(`Express server listening on port "${this.config.port}", in mode "${this.config.env}"`);
|
|
151
|
+
deferred.resolve(server);
|
|
148
152
|
});
|
|
153
|
+
return deferred.promise;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
/**
|
|
157
|
+
* @param {(req: Request, res: Response) => Promise<any>} fn
|
|
158
|
+
* @returns {RequestHandler}
|
|
159
|
+
*/
|
|
160
|
+
makeEndpoint(fn) {
|
|
161
|
+
this.#logger.debug(`Wrapping request handler "${fn?.name}" with error handling`);
|
|
162
|
+
return async function routeHandler(req, res, next) {
|
|
163
|
+
try {
|
|
164
|
+
const resp = await fn(req, res);
|
|
165
|
+
res.send(resp);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
next(error);
|
|
168
|
+
}
|
|
159
169
|
};
|
|
160
170
|
}
|
|
161
171
|
}
|
package/infra/helper.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const splitUriBySlash = (uri) => {
|
|
2
2
|
const parts = uri.split('/');
|
|
3
|
-
const lastIndex = parts.length - 1;
|
|
4
3
|
return {
|
|
5
|
-
prefix: parts.slice(0, -1)
|
|
6
|
-
|
|
7
|
-
dbName: parts[lastIndex].split('?')[0],
|
|
4
|
+
prefix: parts.slice(0, -1).join('/'),
|
|
5
|
+
dbName: parts.at(-1).split('?')[0],
|
|
8
6
|
};
|
|
9
7
|
};
|
|
10
8
|
|
|
11
9
|
const getDbNameFromUri = (uri) => splitUriBySlash(uri).dbName;
|
|
10
|
+
|
|
12
11
|
module.exports = { splitUriBySlash, getDbNameFromUri };
|
package/infra/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const monitor = require('@codefresh-io/cf-monitor');
|
|
2
2
|
|
|
3
3
|
monitor.init();
|
|
4
|
-
const
|
|
5
|
-
const cflogs = require('cf-logs');
|
|
4
|
+
const Bluebird = require('bluebird');
|
|
6
5
|
const { openapi } = require('@codefresh-io/cf-openapi');
|
|
7
6
|
const config = require('./config');
|
|
8
7
|
const eventbus = require('./eventbus');
|
|
@@ -12,10 +11,11 @@ const express = require('./express');
|
|
|
12
11
|
const logging = require('./logging');
|
|
13
12
|
const redis = require('./redis');
|
|
14
13
|
const { publishInterface, subscribeInterface } = require('./openapi-events');
|
|
15
|
-
|
|
16
|
-
let logger;
|
|
14
|
+
const { globalLogger } = require('./global-logger');
|
|
17
15
|
|
|
18
16
|
class Microservice {
|
|
17
|
+
#logger = globalLogger.child(this.constructor.name);
|
|
18
|
+
|
|
19
19
|
constructor() {
|
|
20
20
|
this._ready = false;
|
|
21
21
|
this._healthy = false;
|
|
@@ -30,26 +30,28 @@ class Microservice {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
markAsReady() {
|
|
33
|
-
logger.info('Service marked as ready');
|
|
33
|
+
this.#logger.info('Service marked as ready');
|
|
34
34
|
this._ready = true;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
markAsNotReady() {
|
|
38
|
-
logger.info('Service marked as not ready');
|
|
38
|
+
this.#logger.info('Service marked as not ready');
|
|
39
39
|
this._ready = false;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
markAsHealthy() {
|
|
43
|
-
logger.info('Service marked as healthy');
|
|
43
|
+
this.#logger.info('Service marked as healthy');
|
|
44
44
|
this._healthy = true;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
markAsUnhealthy() {
|
|
48
|
-
logger.info('Service marked as unhealthy');
|
|
48
|
+
this.#logger.info('Service marked as unhealthy');
|
|
49
49
|
this._healthy = false;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
init(initFn) {
|
|
53
|
+
const logger = this.#logger.child('init');
|
|
54
|
+
logger.info('Initializing service');
|
|
53
55
|
const enabledComponents = config.getConfigArray('enabledComponents');
|
|
54
56
|
const opt = {
|
|
55
57
|
isReady: this.isReady.bind(this),
|
|
@@ -57,7 +59,6 @@ class Microservice {
|
|
|
57
59
|
};
|
|
58
60
|
return logging.init(config)
|
|
59
61
|
.then(() => {
|
|
60
|
-
logger = cflogs.Logger('codefresh:infra:index');
|
|
61
62
|
let sigintCount = 0;
|
|
62
63
|
this._validateGraceTimers();
|
|
63
64
|
return processEvents.init(config)
|
|
@@ -91,7 +92,7 @@ class Microservice {
|
|
|
91
92
|
this.markAsHealthy();
|
|
92
93
|
})
|
|
93
94
|
.catch((err) => {
|
|
94
|
-
|
|
95
|
+
logger.error('Initialization error', err);
|
|
95
96
|
process.exit(1);
|
|
96
97
|
});
|
|
97
98
|
}
|
|
@@ -100,7 +101,8 @@ class Microservice {
|
|
|
100
101
|
// - first phase need to make sure to not accept any new requests/events
|
|
101
102
|
// - then a decent amount of time will be given to clear all on-going contexts
|
|
102
103
|
// - second phase will close all dependencies connections like mongo, postgres etc
|
|
103
|
-
stop() {
|
|
104
|
+
stop() {
|
|
105
|
+
const logger = this.#logger.child('stop');
|
|
104
106
|
const enabledComponents = config.getConfigArray('enabledComponents');
|
|
105
107
|
const gracePeriod = config.gracePeriodTimers.totalPeriod;
|
|
106
108
|
|
|
@@ -129,24 +131,24 @@ class Microservice {
|
|
|
129
131
|
logger.info('About to stop redis');
|
|
130
132
|
promises.push(redis.stop.bind(redis));
|
|
131
133
|
}
|
|
132
|
-
return
|
|
134
|
+
return Bluebird
|
|
133
135
|
.resolve()
|
|
134
136
|
.then(() => {
|
|
135
137
|
this.markAsNotReady();
|
|
136
138
|
logger.info(`Waiting more ${incomingHttpRequests} ms to accept more request`);
|
|
137
|
-
return
|
|
139
|
+
return Bluebird.resolve()
|
|
138
140
|
.delay(incomingHttpRequests);
|
|
139
141
|
})
|
|
140
142
|
.then(() => {
|
|
141
143
|
logger.info(`Waiting more ${ongoingHttpRequest} ms to process all ongoing requests`);
|
|
142
|
-
return
|
|
144
|
+
return Bluebird.resolve()
|
|
143
145
|
.delay(ongoingHttpRequest)
|
|
144
146
|
.then(() => express.stop());
|
|
145
147
|
})
|
|
146
148
|
.then(() => {
|
|
147
149
|
logger.info(`Setting up ${infraDependencies} ms to finish all core service connections timeout`);
|
|
148
|
-
return
|
|
149
|
-
.then(() =>
|
|
150
|
+
return Bluebird.resolve()
|
|
151
|
+
.then(() => Bluebird.all(promises))
|
|
150
152
|
.timeout(infraDependencies);
|
|
151
153
|
})
|
|
152
154
|
.timeout(gracePeriod)
|
|
@@ -155,21 +157,21 @@ class Microservice {
|
|
|
155
157
|
process.exit();
|
|
156
158
|
})
|
|
157
159
|
.catch((error) => {
|
|
158
|
-
|
|
160
|
+
this.#logger.error('Error during shutdown', error);
|
|
159
161
|
process.exit();
|
|
160
162
|
});
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
_validateGraceTimers() {
|
|
165
|
+
_validateGraceTimers() {
|
|
164
166
|
const gracePeriod = config.gracePeriodTimers.totalPeriod;
|
|
165
167
|
const incomingHttpRequests = config.gracePeriodTimers.secondsToAcceptAdditionalRequests;
|
|
166
168
|
const ongoingHttpRequest = config.gracePeriodTimers.secondsToProcessOngoingRequests;
|
|
167
169
|
const infraDependencies = config.gracePeriodTimers.secondsToCloseInfraConnections;
|
|
168
170
|
|
|
169
171
|
if (gracePeriod < (incomingHttpRequests + ongoingHttpRequest + infraDependencies)) {
|
|
170
|
-
const message = `Total grace period: ${gracePeriod}, the service needs at least ${incomingHttpRequests + ongoingHttpRequest + infraDependencies} to perform graceful
|
|
172
|
+
const message = `Total grace period: ${gracePeriod}, the service needs at least ${incomingHttpRequests + ongoingHttpRequest + infraDependencies} to perform graceful shutdown. Check service configuration`; // eslint-disable-line
|
|
171
173
|
if (!config.gracePeriodTimers.skipGraceTimersValidation) {
|
|
172
|
-
logger.warn(message);
|
|
174
|
+
this.#logger.warn(message);
|
|
173
175
|
} else {
|
|
174
176
|
throw new Error(message);
|
|
175
177
|
}
|
package/infra/logging.js
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
const { Logger } = require('@codefresh-io/cf-telemetry/logs');
|
|
2
3
|
const cflogs = require('cf-logs');
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
let logger;
|
|
4
|
+
const { globalLogger } = require('./global-logger');
|
|
6
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Legacy shim for logging.
|
|
8
|
+
* Feel free to remove once dependent services are using logger from `@codefresh-io/cf-telemetry` directly.
|
|
9
|
+
*/
|
|
7
10
|
class Logging {
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} scope
|
|
13
|
+
* @returns {Logger}
|
|
14
|
+
* @deprecated Import logger from `@codefresh-io/cf-telemetry` directly instead of this wrapper.
|
|
15
|
+
*/
|
|
16
|
+
getLogger(scope) { // eslint-disable-line
|
|
17
|
+
return new Logger(scope);
|
|
10
18
|
}
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
/**
|
|
21
|
+
* @param {any} config
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line class-methods-use-this
|
|
24
|
+
async init(config) {
|
|
25
|
+
// Init for legacy logger still used by some services. Feel free to remove if dependent services are not using `cf-logs` anymore.
|
|
26
|
+
cflogs.init(config.logger);
|
|
27
|
+
// override the default console.log
|
|
28
|
+
const consoleOverride = globalLogger.child('console-override');
|
|
29
|
+
console.debug = consoleOverride.debug.bind(consoleOverride);
|
|
30
|
+
console.log = consoleOverride.info.bind(consoleOverride);
|
|
31
|
+
console.warn = consoleOverride.warn.bind(consoleOverride);
|
|
32
|
+
console.error = consoleOverride.error.bind(consoleOverride);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
35
|
+
// eslint-disable-next-line no-empty-function, class-methods-use-this
|
|
36
|
+
async stop() {}
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
module.exports = new Logging();
|
package/infra/mongo.js
CHANGED
|
@@ -1,50 +1,70 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
const { MongoClient, ObjectId } = require('mongodb');
|
|
3
|
+
const { monitorMongoDBClient } = require('@codefresh-io/cf-telemetry/metrics/mongodb');
|
|
4
|
+
const { globalLogger } = require('./global-logger');
|
|
2
5
|
const { getDbNameFromUri } = require('./helper');
|
|
3
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} MongoConfig
|
|
9
|
+
* @property {string} uri MongoDB connection URI.
|
|
10
|
+
* @property {import('mongodb').MongoClientOptions} [options] MongoDB client options.
|
|
11
|
+
* @property {string} [dbName] Database name.
|
|
12
|
+
*
|
|
13
|
+
* @typedef {object} Config
|
|
14
|
+
* @property {MongoConfig} mongo MongoDB configuration.
|
|
15
|
+
*/
|
|
16
|
+
|
|
4
17
|
class Mongo {
|
|
18
|
+
#logger = globalLogger.child(this.constructor.name);
|
|
19
|
+
|
|
5
20
|
constructor() {
|
|
6
21
|
this.db = undefined;
|
|
7
22
|
this.ObjectId = ObjectId;
|
|
8
23
|
}
|
|
9
24
|
|
|
10
25
|
/**
|
|
11
|
-
*
|
|
26
|
+
* Initializes the MongoDB client and connects to the database.
|
|
27
|
+
* @param {Config} config
|
|
28
|
+
* @returns {Promise<void>}
|
|
12
29
|
*/
|
|
13
30
|
async init(config) {
|
|
14
|
-
const clientSettings = { ...config.mongo.options };
|
|
15
|
-
const logger = require('cf-logs').Logger('codefresh:infra:mongo'); // eslint-disable-line
|
|
16
|
-
this.logger = logger;
|
|
17
|
-
|
|
18
|
-
const { uri } = config.mongo;
|
|
19
|
-
const dbName = config.mongo.dbName || getDbNameFromUri(uri);
|
|
20
|
-
const client = new MongoClient(uri, clientSettings);
|
|
21
|
-
logger.info(`Mongo db name ${dbName}`);
|
|
22
|
-
|
|
23
31
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
this.#logger.info('Initializing MongoDB connection');
|
|
33
|
+
const { uri, options } = config.mongo;
|
|
34
|
+
const dbName = config.mongo.dbName || getDbNameFromUri(uri);
|
|
35
|
+
const client = new MongoClient(uri, options);
|
|
36
|
+
monitorMongoDBClient(client);
|
|
37
|
+
this.client = await client.connect();
|
|
38
|
+
this.db = this.client.db(dbName);
|
|
39
|
+
this.#logger.info('MongoDB client connected');
|
|
26
40
|
} catch (error) {
|
|
27
|
-
logger.error('
|
|
41
|
+
this.#logger.error('Unable to connect to MongoDB', error);
|
|
28
42
|
throw error;
|
|
29
43
|
}
|
|
30
|
-
|
|
31
|
-
this.client = client;
|
|
32
|
-
this.db = this.client.db(dbName);
|
|
33
|
-
logger.info('Mongo db initialized');
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
/**
|
|
37
|
-
*
|
|
47
|
+
* Closes the MongoDB client connection.
|
|
48
|
+
* @returns {Promise<void>}
|
|
38
49
|
*/
|
|
39
50
|
async stop() {
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
try {
|
|
52
|
+
this.#logger.info('Closing MongoDB connection');
|
|
53
|
+
await this.client?.close();
|
|
54
|
+
this.#logger.info('MongoDB connection closed');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.#logger.error('Unable to close MongoDB connection', error);
|
|
57
|
+
throw error;
|
|
42
58
|
}
|
|
43
|
-
await this.client.close();
|
|
44
59
|
}
|
|
45
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Returns the specified collection.
|
|
63
|
+
* @param {string} collectionName
|
|
64
|
+
* @returns {import('mongodb').Collection | undefined} It only returns `undefined` if the client was not initialized yet.
|
|
65
|
+
*/
|
|
46
66
|
collection(collectionName) {
|
|
47
|
-
return this.db
|
|
67
|
+
return this.db?.collection(collectionName);
|
|
48
68
|
}
|
|
49
69
|
}
|
|
50
70
|
|
package/infra/process-events.js
CHANGED
|
@@ -1,36 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
1
|
+
// @ts-check
|
|
2
|
+
const EventEmitter = require('node:events');
|
|
3
|
+
const { globalLogger } = require('./global-logger');
|
|
3
4
|
|
|
4
5
|
class ProcessEvents extends EventEmitter {
|
|
6
|
+
#logger = globalLogger.child(this.constructor.name);
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @param config
|
|
9
|
+
* Starts listening on process events
|
|
10
|
+
* @param {unknown} config
|
|
8
11
|
*/
|
|
9
|
-
init(config) {
|
|
10
|
-
|
|
11
|
-
return Promise.resolve()
|
|
12
|
-
.then(() => {
|
|
13
|
-
this.config = config;
|
|
12
|
+
async init(config) {
|
|
13
|
+
this.config = config;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// graceful shutdown
|
|
16
|
+
process.on('SIGTERM', () => {
|
|
17
|
+
this.#logger.info('SIGTERM received');
|
|
18
|
+
this.emit('SIGTERM');
|
|
19
|
+
});
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
process.on('SIGINT', () => {
|
|
22
|
+
this.#logger.info('SIGINT received');
|
|
23
|
+
this.emit('SIGINT');
|
|
24
|
+
});
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
process.on('unhandledRejection', (err) => {
|
|
27
|
+
this.#logger.error('Unhandled rejection', err);
|
|
28
|
+
});
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
30
|
+
process.on('uncaughtException', (err) => {
|
|
31
|
+
this.#logger.error('Uncaught Exception', err);
|
|
32
|
+
});
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
|
package/infra/redis.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
2
|
const monitor = require('@codefresh-io/cf-monitor');
|
|
3
3
|
const redis = require('redis');
|
|
4
|
+
const { globalLogger } = require('./global-logger');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Partial<import('redis').ClientOpts>} RedisConfig
|
|
8
|
+
* @typedef {object} Config
|
|
9
|
+
* @property {RedisConfig} redis Redis configuration.
|
|
10
|
+
*/
|
|
4
11
|
|
|
5
12
|
class Redis {
|
|
13
|
+
#logger = globalLogger.child(this.constructor.name);
|
|
14
|
+
|
|
6
15
|
constructor() {
|
|
7
16
|
this.client = undefined;
|
|
8
17
|
this.redisInitialized = false;
|
|
@@ -10,12 +19,14 @@ class Redis {
|
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* starts the connection to redis
|
|
13
|
-
* @
|
|
22
|
+
* @param {Config} config
|
|
23
|
+
* @returns {Promise<void>}
|
|
14
24
|
*/
|
|
15
25
|
init(config) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
this.#logger.info('Initializing Redis connection');
|
|
27
|
+
|
|
28
|
+
/** @type {PromiseWithResolvers<void>} */
|
|
29
|
+
const deferred = Promise.withResolvers();
|
|
19
30
|
|
|
20
31
|
// TODO a fallback for case where redis is not up. should be removed once redis is fully used
|
|
21
32
|
setTimeout(() => {
|
|
@@ -31,21 +42,21 @@ class Redis {
|
|
|
31
42
|
});
|
|
32
43
|
|
|
33
44
|
this.client.on('ready', () => {
|
|
34
|
-
logger.info('Redis client ready');
|
|
45
|
+
this.#logger.info('Redis client ready');
|
|
35
46
|
this.redisInitialized = true;
|
|
36
47
|
deferred.resolve();
|
|
37
48
|
});
|
|
38
49
|
|
|
39
50
|
this.client.on('connect', () => {
|
|
40
|
-
logger.info('Redis client connecting');
|
|
51
|
+
this.#logger.info('Redis client connecting');
|
|
41
52
|
});
|
|
42
53
|
|
|
43
54
|
this.client.on('reconnecting', () => {
|
|
44
|
-
logger.info('Redis client reconnecting');
|
|
55
|
+
this.#logger.info('Redis client reconnecting');
|
|
45
56
|
});
|
|
46
57
|
|
|
47
58
|
this.client.on('error', (error) => {
|
|
48
|
-
logger.error(error
|
|
59
|
+
this.#logger.error('Redis client error', error);
|
|
49
60
|
monitor.noticeError(error);
|
|
50
61
|
});
|
|
51
62
|
|
|
@@ -54,23 +65,20 @@ class Redis {
|
|
|
54
65
|
|
|
55
66
|
/**
|
|
56
67
|
* stops the connection to redis
|
|
57
|
-
* @returns {
|
|
68
|
+
* @returns {Promise<void>}
|
|
58
69
|
*/
|
|
59
|
-
stop() {
|
|
60
|
-
const logger = this.logger; // eslint-disable-line
|
|
70
|
+
async stop() {
|
|
61
71
|
if (!this.redisInitialized) {
|
|
62
72
|
return Promise.resolve();
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.client
|
|
68
|
-
logger.info('Redis client
|
|
75
|
+
/** @type {PromiseWithResolvers<void>} */
|
|
76
|
+
const deferred = Promise.withResolvers();
|
|
77
|
+
this.client?.on('end', () => {
|
|
78
|
+
this.#logger.info('Redis client connection closed');
|
|
69
79
|
deferred.resolve();
|
|
70
80
|
});
|
|
71
|
-
|
|
72
|
-
this.client.quit();
|
|
73
|
-
|
|
81
|
+
this.client?.quit();
|
|
74
82
|
return deferred.promise;
|
|
75
83
|
}
|
|
76
84
|
}
|
package/infra/validation.js
CHANGED
|
@@ -3,6 +3,9 @@ const Promise = require('bluebird');
|
|
|
3
3
|
const Joi = require('joi').extend(require('@wegolook/joi-objectid'));
|
|
4
4
|
const YAML = require('js-yaml');
|
|
5
5
|
const Ajv = require('ajv');
|
|
6
|
+
const { globalLogger } = require('./global-logger');
|
|
7
|
+
|
|
8
|
+
const logger = globalLogger.child('validation');
|
|
6
9
|
|
|
7
10
|
const customYamlJoi = Joi.extend((joi) => ({ // eslint-disable-line
|
|
8
11
|
name: 'yaml',
|
|
@@ -11,7 +14,8 @@ const customYamlJoi = Joi.extend((joi) => ({ // eslint-disable-line
|
|
|
11
14
|
try {
|
|
12
15
|
YAML.safeLoad(value);
|
|
13
16
|
return value;
|
|
14
|
-
} catch (
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.debug('YAML validation error', error);
|
|
15
19
|
return this.createError('yaml', { v: value }, state, options);
|
|
16
20
|
}
|
|
17
21
|
},
|
|
@@ -29,6 +33,7 @@ const customJsonSchemaStringJoi = Joi.extend((joi) => ({ // eslint-disable-line
|
|
|
29
33
|
ajv.compile(JSON.parse(value));
|
|
30
34
|
return value;
|
|
31
35
|
} catch (err) {
|
|
36
|
+
logger.debug('JSON Schema validation error', err);
|
|
32
37
|
return this.createError('jsonSchemaString.pre', { v: value, err: err.toString().replace('Error: schema is invalid: ', '') }, state, options); // eslint-disable-line max-len
|
|
33
38
|
}
|
|
34
39
|
},
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefresh-io/service-base",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.1-alpha.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"description": "",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cf-openapi": "./node_modules/cf-openapi/bin/cf-openapi"
|
|
8
8
|
},
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": "
|
|
10
|
+
"node": ">=22 <=24"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"index.js",
|
|
14
14
|
"infra/**/*"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "(find . -not -path './node_modules/*' -path '*/*.spec.js' | grep -v '__tests__' | NODE_ENV=test xargs mocha) && jest",
|
|
17
|
+
"test": "(find . -not -path './node_modules/*' -path '*/*.spec.js' | grep -v '__tests__' | NODE_ENV=test xargs mocha) && CF_TELEMETRY_LOGS_PRETTIFY=single-line jest",
|
|
18
18
|
"start": "node index.js",
|
|
19
|
-
"lint": "eslint
|
|
19
|
+
"lint": "eslint .",
|
|
20
20
|
"build": "echo 'No build step'"
|
|
21
21
|
},
|
|
22
22
|
"repository": {
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"url": "https://github.com/codefresh-io/service-base/issues"
|
|
30
30
|
},
|
|
31
31
|
"resolutions": {
|
|
32
|
-
"eslint/**/ansi-regex": "^4.0.0",
|
|
33
32
|
"js-yaml": "^3.13.1",
|
|
34
33
|
"json-schema": "^0.4.0",
|
|
35
34
|
"get-func-name": "^2.0.1"
|
|
@@ -48,7 +47,7 @@
|
|
|
48
47
|
"bluebird": "^3.5.3",
|
|
49
48
|
"body-parser": "^1.19.2",
|
|
50
49
|
"cf-errors": "^0.1.15",
|
|
51
|
-
"cf-logs": "^1.1.
|
|
50
|
+
"cf-logs": "^1.1.28",
|
|
52
51
|
"chai": "4.3.10",
|
|
53
52
|
"compression": "^1.7.4",
|
|
54
53
|
"cookie-parser": "^1.4.4",
|
|
@@ -57,25 +56,29 @@
|
|
|
57
56
|
"js-yaml": "^3.13.1",
|
|
58
57
|
"lodash": "^4.17.21",
|
|
59
58
|
"method-override": "^3.0.0",
|
|
60
|
-
"mongodb": "^6.
|
|
61
|
-
"
|
|
62
|
-
"proxyquire": "^1.8.0",
|
|
59
|
+
"mongodb": "^6.21.0",
|
|
60
|
+
"qs": "^6.14.1",
|
|
63
61
|
"queue": "^4.2.1",
|
|
64
|
-
"qs":"^6.14.1",
|
|
65
62
|
"redis": "^3.1.0",
|
|
66
63
|
"request": "2.88.2",
|
|
67
64
|
"request-promise": "4.2.6"
|
|
68
65
|
},
|
|
69
66
|
"devDependencies": {
|
|
70
|
-
"@
|
|
71
|
-
"eslint": "^
|
|
67
|
+
"@codefresh-io/cf-telemetry": "^3.9.1",
|
|
68
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
69
|
+
"eslint": "^9.32.0",
|
|
72
70
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
73
|
-
"eslint-
|
|
74
|
-
"eslint-plugin-
|
|
75
|
-
"eslint-plugin-
|
|
76
|
-
"
|
|
71
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
72
|
+
"eslint-plugin-import": "^2.32.0",
|
|
73
|
+
"eslint-plugin-jest": "^28.11.0",
|
|
74
|
+
"eslint-plugin-mocha": "^10.5.0",
|
|
75
|
+
"jest": "^30.2.0",
|
|
77
76
|
"mocha": "^8.2.1",
|
|
78
|
-
"mongodb-memory-server": "^
|
|
77
|
+
"mongodb-memory-server": "^11.0.1",
|
|
78
|
+
"proxyquire": "^1.8.0"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@codefresh-io/cf-telemetry": "^3.9.1"
|
|
79
82
|
},
|
|
80
83
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
81
84
|
}
|